SlideShare a Scribd company logo
Angular 컴포넌트 대화법
Jeado Ko
+jeado.ko (고재도)
haibane84@gmail.com
- “Google Developer Expert” WebTech
- “Kakao Bank 빅데이터 파트” Developer
질문이 있습니다!
컴포넌트
● 명세specification
를 가진 재사용할 수 있는reusable
소프트웨어 구성요소 (위키피디아)
● 웹 애플리케이션의 기본 구성요소로 HTML 요소들을 포함
● 독립된 구성요소로 뷰와 로직으로 구성됨
● 컴포넌트들은 단방향 트리형태로 구성되고 최상위 루트 컴포넌트가 존재
Public API
사진 출처: https://v1.vuejs.org/guide/overview.html
컴포넌트 개요
Angular의 Hello World 컴포넌트
import { Component } from '@angular/core' ;
@Component({
selector: 'my-hello-world' ,
template: '<h1>{{title}}</h1>' ,
styles: ['h1 { color: red }' ]
})
export class HelloWorldComponent {
title = 'Hello World!!' ;
}
컴포넌트 계층구조간 커뮤니케이션
부모
컴포넌트
자식
컴포넌트
부모
컴포넌트
자식
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
컴포넌트 계층구조간 커뮤니케이션
부모
컴포넌트
자식
컴포넌트
부모
컴포넌트
자식
컴포넌트
컴포넌트 커뮤니케이션 (부모 → 자식)
부모
컴포넌트
자식
컴포넌트
TodosComponent
TodoComponent
● 자식컴포넌트에서 부모가 전달할 속성에 @Input() 데코레이터를 사용
컴포넌트 커뮤니케이션 (부모 → 자식)
import {Component, Input, OnInit} from '@angular/core';
import {Todo} from '../../share/todo.model';
@Component({
selector: 'app-todo',
template: `
<input type="checkbox" [checked]="todo.done"> <label>{{ todo.text }}</label>
`,
styles: [`...`] // 생략
})
export class TodoComponent {
@Input() todo: Todo;
constructor() { }
}
● 부모 컴포넌트에서는 속성 바인딩을 통해 데이터 전달
컴포넌트 커뮤니케이션 (부모 → 자식)
<!-- todos.component.html 일부 -->
<div *ngFor="let todo of todos" >
<app-todo [todo]="todo"></app-todo>
</div>
// todos.compotonent.ts
@Component({
selector: 'app-todos' ,
templateUrl: './todos.component.html' ,
styleUrls: ['./todos.component.css' ]
})
export class TodosComponent implements OnInit {
todos: Todo[];
constructor () {
this.todos = [
{ done: false, text: '운동하기' },
{ done: true, text: '공부하기'}
];
}
|
● 자식 컴포넌트에서 @Input을 Getter/Setter에 사용
컴포넌트 커뮤니케이션 (부모 → 자식)
@Component({
// 생략
})
export class TodoComponent {
private _todo: Todo;
get todo(): Todo { return this._todo; }
@Input()
set todo(v: Todo) {
this._todo = v;
v.text += " !!!";
}
constructor() {}
}
● 부모컴포넌트에서 자식 컴포넌트 인스턴스를 @ViewChild()로 가져옴
컴포넌트 커뮤니케이션 (부모 → 자식)
<!-- todos.component.html 일부 -->
<div class="title">
<app-title></app-title>
<h2>{{ today | date:'M월 d일' }}</h2>
</div>
<!-- todos.component.ts 일부 -->
export class TodosComponent implements OnInit {
// 생략
@ViewChild(TitleComponent) titleComp :TitleComponent;
ngOnInit() {
this.titleComp.text = '나의 하루'
}
}
컴포넌트 커뮤니케이션 (자식 → 부모)
부모
컴포넌트
자식
컴포넌트
TodosComponent
AddTodoComponent
● 자식컴포넌트에서 EventEmitter를 통해 부모가 전달 받을 이벤트를 발생하는
속성에 @Output() 데코레이터를 사용
컴포넌트 커뮤니케이션 (자식 → 부모)
@Component({
selector: 'app-add-todo' ,
template: `<button (click)="btnClicked(newText)">+</button>
<input type="text" placeholder=" 할 일 추가" [(ngModel)]="newText">` ,
styles: ['...'] // 생략
})
export class AddTodoComponent {
@Output() onTodoAdded = new EventEmitter();
newText: string;
constructor () { }
btnClicked(newText: string) {
this.onTodoAdded .emit(newText);
this.newText = '';
}
}
● 부모 컴포넌트는 $event로 이벤트의 데이터를 전달 받음
컴포넌트 커뮤니케이션 (자식 → 부모)
<!-- todos.component.html 일부 -->
<div>
<app-add-todo (onTodoAdded)="addTodo($event)"></app-add-todo>
</div>
<!-- todos.component.ts 일부 -->
export class TodosComponent {
// 생략
addTodo(text: string) {
this.todos.push({done : false, text});
}
}
● 자식컴포넌트에서 부모컴포넌트를 주입받음
컴포넌트 커뮤니케이션 (자식 → 부모)
@Component({
selector: 'app-add-todo' ,
template: `<button (click)="btnClicked(newText)">+</button>
<input type="text" placeholder=" 할 일 추가" [(ngModel)]="newText">` ,
styles: ['...'] // 생략
})
export class AddTodoComponent {
@Output() onTodoAdded = new EventEmitter ();
newText: string;
constructor(private todosComponent: TodosComponent) { }
btnClicked(newText: string) {
// this.onTodoAdded.emit(newText);
this.todosComponent.addTodo(newText);
this.newText = '';
}
}
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
AppComponent
CartComponentHomeComponent
ProductComponent ProductComponent
라우터를 연결하고 다른 모듈을 넣었다면?!
<!-- app.component.html -->
<app-drawer #drawer>
<app-cart (onClose)="drawer.close()"></fc-cart>
</app-drawer>
<app-navi></app-navi>
<main [ngClass]="{'m-t-main': !isHome}">
<router-outlet></router-outlet>
</main>
AppComponent CartComponent
HomeComponent
ProductComponent ProductComponent
RouterOutlet
App Module
Home Module
Route
{
path: 'home',
component: HomeComponent
}
서비스를 활용한 커뮤니케이션
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
서비스
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
서비스
● CartService를 통하여 카트아이템 배열CartItem[]
을 구독
CartComponent
@Component({
selector: 'app-cart',
templateUrl: './cart.component.html' ,
styleUrls: ['./cart.component.css' ]
})
export class CartComponent {
cart: CartItem[] = [];
constructor (private cartService : CartService ) {
this.cartService .cartItems
.subscribe(v => this.cart = v)
}
remove(cartItem: CartItem) {
this.cartService .remove(cartItem);
}
// 생략
}
Observable<CartItem[]>
● CartService를 통하여 카트아이템 배열CartItem[]
을 구독
HomeComponent
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
constructor( private cartService: CartService) { }
addCart(product: Product) {
this.cartService.addCart(product);
}
// 생략
}
● BehaviorSubject를 이용하여 로컬스토리지의 초기 로드값이나 마지막 값을 발행
CartService (1)
@Injectable()
export class CartService {
private _items: CartItem[] = [];
private cartSubject: BehaviorSubject<CartItem[]>;
public cartItems: Observable<CartItem[]>;
constructor() {
const itemJson = localStorage.getItem(storageKey)
if (itemJson) this._items = JSON.parse(itemJson);
this.cartSubject = new BehaviorSubject(this._items);
this.cartItems = this.cartSubject.asObservable();
}
// 생략
}
CartService (2)
@Injectable()
export class CartService {
// 생략
addCart(product: Product) {
const foundProduct = this._items.find(c => c.product.id === product.id);
if (foundProduct) foundProduct.counts += 1;
else this._items.push({ product, counts: 1 });
this.updateLocalStorage(this._items);
this.cartSubject.next(this._items);
}
private updateLocalStorage(cartItems: CartItem[]) {
localStorage.setItem(storageKey, JSON.stringify(cartItems));
}
}
CartService (3)
@Injectable()
export class CartService {
// 생략
remove(cartItem: CartItem) {
const foudnItem = this.cart.find(v => v.product.id === cartItem.product.id)
if (foudnItem && foudnItem.counts > 1) {
foudnItem.counts -= 1;
} else {
const index = this.cart.indexOf(foudnItem);
this.cart.splice(index, 1);
}
this.updateLocalStorage();
this.cartSubject.next(this.cart);
}
}
하지만 서비스와 컴포넌트가 아주 많아지면?
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
서비스
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
자바스크립트 앱을 위한
예측가능한 상태
컨테이너
PREDICTABLE STATE
CONTAINER
FOR JAVASCRIPT APPS
WITHOUT REDUX
● 컴포넌트간 직접적인 통신
(속성 바인딩, eventEmitter 활용)
WITH REDUX
● 컴포넌트간의 직접 통신이 없다.
● 스토어를 통한 단 하나의 상태로
관리
REDUX Store
1. 컴포넌트에서 액션action
을
보냄dispatch
2. 스토어는 변화를 적용한다.
3. 컴포넌트는 관련된 상태를
전달받는다. (subscribe에 의해서)
REDUX Reducer
(state, action) => state
Actions
Reducers
Store
View
(Component)
subscribe
change state dispatch
angular-reduxreact-redux ngrx
● @ngrx - Reactive Extensions for Angular
ngrx (https://ngrx.github.io/)
참고 : https://gist.github.com/btroncone/a6e4347326749f938510
Setup
● npm install @ngrx/store --save후 StoreModule 모듈 임포트
import { NgModule } from '@angular/core'
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter';
@NgModule({
imports: [
BrowserModule,
StoreModule.forRoot({ counter: counterReducer }) // ActionReducerMap 전달
]
})
export class AppModule {}
Reducer (counter.reducer.ts)
import { Action } from'@ngrx/store';
import * as CounterActions from './coutner.actions';
export function counterReducer(state: number = 0, action: CounterActions.All): number {
switch(action.type) {
case CounterActions.INCREMENT:
return state + 1;
case CounterActions.DECREMENT:
return state - 1;
case CounterActions.RESET:
return action.payload
default:
return state;
}
}
Action (counter.actions.ts)
import { Action } from '@ngrx/store';
export const INCREMENT = '[Counter] Increment';
export const DECREMENT = '[Counter] Decrement';
export const RESET = '[Counter] Reset';
export class Increment implements Action {
readonly type = INCREMENT;
}
export class Decrement implements Action {
readonly type = DECREMENT;
}
export class Reset implements Action {
readonly type = RESET;
constructor(public payload: number) {}
}
export type All = Increment | Decrement | Reset;
Component
export interface AppState { counter: number }
@Component({
selector: 'my-app',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
`})
export class CounterComponent {
counter: Observable<number>;
constructor (private store: Store<AppState>) {
this.counter = store.select('counter');
}
increment(){ this.store.dispatch(new Counter.Increment()); }
decrement(){ this.store.dispatch(new Counter.Decrement()); }
reset(){ this.store.dispatch(new Counter.Reset(1)); }
}
DashboardComponent
TableComponentGraphComponent
AppModule
import { StoreModule } from '@ngrx/store';
import * as fromRoot from './store';
@NgModule({
imports: [
CommonModule,
StoreModule.forRoot(fromRoot.reducers, {initialState: fromRoot.getInitialState()}),
],
providers: [ // 생략 ],
declarations: []
})
export class AppModule { }
import * as fromSales from './sales/sales.reducer' ;
import * as fromOrder from './order/order.reducer' ;
export interface AppState {
sales: fromSales.State;
orders: fromOrder.OrderState;
}
export const initialState : AppState = {
sales: fromSales.initialState ,
orders: fromOrder.initialState
};
export function getInitialState (): AppState {
return initialState ;
}
export const reducers: ActionReducerMap <AppState> = {
sales: fromSales.reducer,
orders: fromOrder.reducer
};
store/index.ts
import { Order } from '../../models/order.model';
import * as moment from 'moment';
export interface OrderState {
orders: Order[];
selectedOrder: Order;
from: Date;
to: Date;
}
export const initialState: OrderState = {
orders: [],
selectedOrder: null,
from: moment().toDate(),
to: moment().startOf('day').toDate()
};
order/order.reducer.ts
export const SEARCH_ORDERS = '[Order] SEARCH_ORDERS';
export const SELECT_ORDER = '[Order] SELECT_ORDER';
export const RESET = '[Order] RESET';
export class SearchOrder implements Action {
readonly type = SEARCH_ORDERS;
}
export class SelectOrder implements Action {
readonly type = SELECT_ORDER;
constructor(public order: Order) {}
}
export class Reset implements Action {
readonly type = RESET;
}
export type All = ReqSearchOrders | SearchOrder | SelectOrder | Reset;
order/order.action.ts
import * as orderActions from './order.action';
export function reducer(state = initialState, action: orderActions.All): OrderState {
switch (action.type) {
case orderActions.SEARCH_ORDERS:
return Object.assign({}, state, { orders: [ /* 오더 데이터 생략… */ ]);
case orderActions.SELECT_ORDER:
return Object.assign({}, state, { selectedOrder: action.order });
case orderActions.RESET:
return Object.assign({}, state, { selectedOrder: null });
default:
return state;
}
}
order/order.reducer.ts
@Component({
selector: 'app-table' ,
templateUrl: './table.component.html' ,
styleUrls: ['./table.component.scss' ]
})
export class TableComponent implements OnInit {
orders: Observable<Order[]>;
selectedOrder : Order;
constructor (private store: Store<AppState>) {
this.orders = store.select(v => v.orders.orders)
this.store.select(v => v.orders.selectedOrder )
.subscribe(v => this.selectedOrder = v);
}
select(p: Order) {
if (p === this.selectedOrder ) {
this.store.dispatch(new orderActions .Reset());
} else {
this.store.dispatch(new orderActions .SelectOrder (p));
}
}
}
TableComponent
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.store.dispatch(new SearchOrders())
this.store.dispatch(new GetTotalSales())
}
}
DashboardComponent
코드에 박힌
데이터 말고 실제
서비스는 어떻게
하는데…
@ngrx/effects
출처: https://blog.nextzy.me/manage-action-flow-in-ngrx-with-ngrx-effects-1fda3fa06c2f
Actions
Reducers
Store
View
(Component)
subscribe
change state
dispatch
Effect
@ngrx/effects flow
Service
subscribe
dispatch
export const REQ_SEARCH_ORDERS = '[Order] REQ_SEARCH_ORDERS';
export const REQ_SEARCH_SUCCESS = '[Order] REQ_SEARCH_SUCCESS';
export const SELECT_ORDER = '[Order] SELECT_ORDER';
export const RESET = '[Order] RESET';
export class ReqSearchOrders implements Action {
readonly type = REQ_SEARCH_ORDERS;
constructor(public orderNumber?: string) {}
}
export class ReqSearchSuccess implements Action {
readonly type = REQ_SEARCH_SUCCESS;
constructor(public orders: Order[]) {}
}
// 생략
order/order.action.ts
export function reducer(state = initialState, action: orderActions.All): OrderState {
switch (action.type) {
case orderActions.REQ_SEARCH_ORDERS:
return state;
case orderActions.REQ_SEARCH_SUCCESS:
return Object.assign({}, state, { orders: action.orders });
case orderActions.SELECT_ORDER:
return Object.assign({}, state, { selectedOrder: action.order });
case orderActions.RESET:
return Object.assign({}, state, { selectedOrder: null });
default:
return state;
}
}
order/order.reducer.ts
import { Effect, Actions } from '@ngrx/effects';
import * as orderAction from './order.action';
@Injectable()
export class OrderEffects {
@Effect()
request$: Observable<Action> = this.actions$
.ofType<orderAction.ReqSearchOrders>(orderAction.REQ_SEARCH_ORDERS)
.map(v => v.orderNumber)
.mergeMap(num => {
return this.orderService.getOrders(num)
.map((orders: Order[]) => new orderAction.ReqSearchSuccess(orders))
.catch(() => Observable.of(new orderAction.ReqSearchSuccess([])));
});
constructor(private actions$: Actions, private orderService: OrderService) { }
}
order/order.effects.ts
import * as salesAction from '../sales/sales.action';
@Effect()
forward$: Observable<Action> = this.actions$
.ofType<orderAction.SelectOrder|orderAction.Reset>(orderAction.SELECT_ORDER, orderAction.RESET)
.map((a) => {
if (a instanceof orderAction.SelectOrder) {
return a.order.name;
} else {
return null;
}
})
.map(name => new salesAction.ReqGivenItemSales(name));
order/order.effects.ts
import * as salesAction from '../sales/sales.action';
@Effect()
forward$: Observable<Action> = this.actions$
.ofType<orderAction.SelectOrder|orderAction.Reset>(orderAction.SELECT_ORDER, orderAction.RESET)
.map((a) => {
if (a instanceof orderAction.SelectOrder) {
return a.order.name;
} else {
return null;
}
})
.map(name => new salesAction.ReqGivenItemSales(name));
order/order.effects.ts
// TableComponent
select(p: Order) {
if (p === this.selectedOrder) {
this.store.dispatch(new orderActions.Reset());
} else {
this.store.dispatch(new orderActions.SelectOrder(p));
}
}
}
sales/sales.effects.ts
@Injectable()
export class SalesEffects {
@Effect()
requestByItem$: Observable<Action> = this.actions$
.ofType<ReqGivenItemSales>(salesAction.REQ_GIVEN_ITEM_SALES)
.map(v => v.itemNum)
.mergeMap(itemName => {
return this.selesService.getSales(itemName)
.map((sales: Sales[]) => new salesAction.ReqGetSalesSuccess(sales))
.catch(() => Observable.of(new salesAction.ReqGetSalesSuccess([])));
});
constructor(private actions$: Actions, private selesService: SalesService) { }
}
Sidebar
모듈!
라우팅이
존재!
sidebar-routing.module.ts
const routes: Routes = [{
path: 'events',
component: EventListComponent
}, {
path: 'events/:num',
component: EventDetailComponent
}, {
path: '',
redirectTo: 'events'
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SidebarRoutingModule { }
@ngrx/router-store
/**
* Payload of ROUTER_NAVIGATION.
*/
export declare type RouterNavigationPayload<T> = {
routerState: T;
event: RoutesRecognized;
};
/**
* An action dispatched when the router navigates.
*/
export declare type RouterNavigationAction<T = RouterStateSnapshot> = {
type: typeof ROUTER_NAVIGATION;
payload: RouterNavigationPayload<T>;
};
EventListComponent
@Component({
selector: 'app-event-list',
templateUrl: './event-list.component.html',
styleUrls: ['./event-list.component.scss']
})
export class EventListComponent {
orderEvents: Observable<OrderEvent[]>
selectedOrderEvent: OrderEvent;
constructor(private store: Store<AppState>, private route: ActivatedRoute) {
this.orderEvents = store.select(v => v.events.orderEvents)
}
}
event-list.component.html 일부
<ul class="sidebar-list">
<li *ngFor="let event of orderEvents | async" [routerLink]="[event.orderNumber]">
<a>
<span class="label label-primary pull-right">NEW</span>
<h4>주문번호: {{event.orderNumber}}</h4>
{{event.text}}
<div class="small">수량: {{event.salesNumber}}</div>
<div class="small text-muted m-t-xs">판매시간 - {{event.date | date:'medium'}}</div>
</a>
</li>
</ul>
EventEffects는
에디터에서 보겠습니다.
감사합니다.
● https://css-tricks.com/learning-react-redux/
● https://github.com/ngrx/platform
● https://gist.github.com/btroncone/a6e4347326749f938510
● https://blog.nextzy.me/manage-action-flow-in-ngrx-with-ngrx-effects-1fda3fa06c2f
reference

More Related Content

What's hot

IndexedDB - Querying and Performance
IndexedDB - Querying and PerformanceIndexedDB - Querying and Performance
IndexedDB - Querying and Performance
Parashuram N
 
Sane Async Patterns
Sane Async PatternsSane Async Patterns
Sane Async Patterns
TrevorBurnham
 
The AngularJS way
The AngularJS wayThe AngularJS way
The AngularJS way
Boyan Mihaylov
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
Wei Ru
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsJquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
EPAM Systems
 
Modules and injector
Modules and injectorModules and injector
Modules and injector
Eyal Vardi
 
AngularJS: an introduction
AngularJS: an introductionAngularJS: an introduction
AngularJS: an introduction
Luigi De Russis
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todaygerbille
 
Vaadin Components @ Angular U
Vaadin Components @ Angular UVaadin Components @ Angular U
Vaadin Components @ Angular U
Joonas Lehtinen
 
AngularJS Framework
AngularJS FrameworkAngularJS Framework
AngularJS Framework
Barcamp Saigon
 
22 j query1
22 j query122 j query1
22 j query1
Fajar Baskoro
 
Advanced Tips & Tricks for using Angular JS
Advanced Tips & Tricks for using Angular JSAdvanced Tips & Tricks for using Angular JS
Advanced Tips & Tricks for using Angular JS
Simon Guest
 
Angular js
Angular jsAngular js
Angular js
Manav Prasad
 
AngularJS Basics with Example
AngularJS Basics with ExampleAngularJS Basics with Example
AngularJS Basics with Example
Sergey Bolshchikov
 
AngularJS Internal
AngularJS InternalAngularJS Internal
AngularJS Internal
Eyal Vardi
 
AngularJs
AngularJsAngularJs
AngularJs
syam kumar kk
 
AngularJS Directives
AngularJS DirectivesAngularJS Directives
AngularJS Directives
Eyal Vardi
 
AngularJS
AngularJSAngularJS
AngularJS
LearningTech
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJS
Visual Engineering
 
AngularJS in 60ish Minutes
AngularJS in 60ish MinutesAngularJS in 60ish Minutes
AngularJS in 60ish Minutes
Dan Wahlin
 

What's hot (20)

IndexedDB - Querying and Performance
IndexedDB - Querying and PerformanceIndexedDB - Querying and Performance
IndexedDB - Querying and Performance
 
Sane Async Patterns
Sane Async PatternsSane Async Patterns
Sane Async Patterns
 
The AngularJS way
The AngularJS wayThe AngularJS way
The AngularJS way
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsJquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
 
Modules and injector
Modules and injectorModules and injector
Modules and injector
 
AngularJS: an introduction
AngularJS: an introductionAngularJS: an introduction
AngularJS: an introduction
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of today
 
Vaadin Components @ Angular U
Vaadin Components @ Angular UVaadin Components @ Angular U
Vaadin Components @ Angular U
 
AngularJS Framework
AngularJS FrameworkAngularJS Framework
AngularJS Framework
 
22 j query1
22 j query122 j query1
22 j query1
 
Advanced Tips & Tricks for using Angular JS
Advanced Tips & Tricks for using Angular JSAdvanced Tips & Tricks for using Angular JS
Advanced Tips & Tricks for using Angular JS
 
Angular js
Angular jsAngular js
Angular js
 
AngularJS Basics with Example
AngularJS Basics with ExampleAngularJS Basics with Example
AngularJS Basics with Example
 
AngularJS Internal
AngularJS InternalAngularJS Internal
AngularJS Internal
 
AngularJs
AngularJsAngularJs
AngularJs
 
AngularJS Directives
AngularJS DirectivesAngularJS Directives
AngularJS Directives
 
AngularJS
AngularJSAngularJS
AngularJS
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJS
 
AngularJS in 60ish Minutes
AngularJS in 60ish MinutesAngularJS in 60ish Minutes
AngularJS in 60ish Minutes
 

Viewers also liked

Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Kris Jeong
 
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
Jae Sung Park
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
SeungYong Oh
 
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
Jae Sung Park
 
로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법
Jeongsang Baek
 
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기 [데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
choi kyumin
 

Viewers also liked (6)

Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
 
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
[SOSCON 2017] 네이버의 FE 오픈소스: jindo에서 billboard.js까지
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
 
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
[DEVIEW 2017] 14일만에 GitHub 스타 1K 받은 차트 오픈소스 개발기
 
로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법로그 기깔나게 잘 디자인하는 법
로그 기깔나게 잘 디자인하는 법
 
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기 [데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
[데이터야놀자2107] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
 

Similar to [FEConf Korea 2017]Angular 컴포넌트 대화법

Angular2 + rxjs
Angular2 + rxjsAngular2 + rxjs
Angular2 + rxjs
Christoffer Noring
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app exampleAngular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app example
Alexey Frolov
 
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Ontico
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
Evangelia Mitsopoulou
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs
[T]echdencias
 
Commit University - Exploring Angular 2
Commit University - Exploring Angular 2Commit University - Exploring Angular 2
Commit University - Exploring Angular 2
Commit University
 
Angular JS2 Training Session #2
Angular JS2 Training Session #2Angular JS2 Training Session #2
Angular JS2 Training Session #2
Paras Mendiratta
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next Framework
Commit University
 
React render props
React render propsReact render props
React render props
Saikat Samanta
 
React outbox
React outboxReact outbox
React outbox
Angela Lehru
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdf
NuttavutThongjor1
 
React hooks
React hooksReact hooks
React hooks
Assaf Gannon
 
Enhance react app with patterns - part 1: higher order component
Enhance react app with patterns - part 1: higher order componentEnhance react app with patterns - part 1: higher order component
Enhance react app with patterns - part 1: higher order component
Yao Nien Chung
 
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Frost
 
React redux
React reduxReact redux
React redux
Michel Perez
 
Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018
Robert Herbst
 
React 16: new features and beyond
React 16: new features and beyondReact 16: new features and beyond
React 16: new features and beyond
Artjoker
 
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
Rob Tweed
 
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Luciano Mammino
 
Gutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
Riad Benguella
 

Similar to [FEConf Korea 2017]Angular 컴포넌트 대화법 (20)

Angular2 + rxjs
Angular2 + rxjsAngular2 + rxjs
Angular2 + rxjs
 
Angular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app exampleAngular2: Quick overview with 2do app example
Angular2: Quick overview with 2do app example
 
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs
 
Commit University - Exploring Angular 2
Commit University - Exploring Angular 2Commit University - Exploring Angular 2
Commit University - Exploring Angular 2
 
Angular JS2 Training Session #2
Angular JS2 Training Session #2Angular JS2 Training Session #2
Angular JS2 Training Session #2
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next Framework
 
React render props
React render propsReact render props
React render props
 
React outbox
React outboxReact outbox
React outbox
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdf
 
React hooks
React hooksReact hooks
React hooks
 
Enhance react app with patterns - part 1: higher order component
Enhance react app with patterns - part 1: higher order componentEnhance react app with patterns - part 1: higher order component
Enhance react app with patterns - part 1: higher order component
 
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
 
React redux
React reduxReact redux
React redux
 
Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018
 
React 16: new features and beyond
React 16: new features and beyondReact 16: new features and beyond
React 16: new features and beyond
 
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
EWD 3 Training Course Part 39: Building a React.js application with QEWD, Part 3
 
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
Universal JS Web Applications with React - Web Summer Camp 2017, Rovinj (Work...
 
Gutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
 

Recently uploaded

GraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge GraphGraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge Graph
Guy Korland
 
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptxIOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
Abida Shariff
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
Product School
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Inflectra
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
ThousandEyes
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi
Fwdays
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
Product School
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
Ralf Eggert
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
DianaGray10
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
Elena Simperl
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
91mobiles
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Jeffrey Haguewood
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Tobias Schneck
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
Jemma Hussein Allen
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
BookNet Canada
 

Recently uploaded (20)

GraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge GraphGraphRAG is All You need? LLM & Knowledge Graph
GraphRAG is All You need? LLM & Knowledge Graph
 
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptxIOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 

[FEConf Korea 2017]Angular 컴포넌트 대화법

  • 2. +jeado.ko (고재도) haibane84@gmail.com - “Google Developer Expert” WebTech - “Kakao Bank 빅데이터 파트” Developer
  • 5. ● 명세specification 를 가진 재사용할 수 있는reusable 소프트웨어 구성요소 (위키피디아) ● 웹 애플리케이션의 기본 구성요소로 HTML 요소들을 포함 ● 독립된 구성요소로 뷰와 로직으로 구성됨 ● 컴포넌트들은 단방향 트리형태로 구성되고 최상위 루트 컴포넌트가 존재 Public API 사진 출처: https://v1.vuejs.org/guide/overview.html 컴포넌트 개요
  • 6. Angular의 Hello World 컴포넌트 import { Component } from '@angular/core' ; @Component({ selector: 'my-hello-world' , template: '<h1>{{title}}</h1>' , styles: ['h1 { color: red }' ] }) export class HelloWorldComponent { title = 'Hello World!!' ; }
  • 7. 컴포넌트 계층구조간 커뮤니케이션 부모 컴포넌트 자식 컴포넌트 부모 컴포넌트 자식 컴포넌트 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 손주 컴포넌트 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트
  • 9.
  • 10. 컴포넌트 커뮤니케이션 (부모 → 자식) 부모 컴포넌트 자식 컴포넌트 TodosComponent TodoComponent
  • 11. ● 자식컴포넌트에서 부모가 전달할 속성에 @Input() 데코레이터를 사용 컴포넌트 커뮤니케이션 (부모 → 자식) import {Component, Input, OnInit} from '@angular/core'; import {Todo} from '../../share/todo.model'; @Component({ selector: 'app-todo', template: ` <input type="checkbox" [checked]="todo.done"> <label>{{ todo.text }}</label> `, styles: [`...`] // 생략 }) export class TodoComponent { @Input() todo: Todo; constructor() { } }
  • 12. ● 부모 컴포넌트에서는 속성 바인딩을 통해 데이터 전달 컴포넌트 커뮤니케이션 (부모 → 자식) <!-- todos.component.html 일부 --> <div *ngFor="let todo of todos" > <app-todo [todo]="todo"></app-todo> </div> // todos.compotonent.ts @Component({ selector: 'app-todos' , templateUrl: './todos.component.html' , styleUrls: ['./todos.component.css' ] }) export class TodosComponent implements OnInit { todos: Todo[]; constructor () { this.todos = [ { done: false, text: '운동하기' }, { done: true, text: '공부하기'} ]; } |
  • 13. ● 자식 컴포넌트에서 @Input을 Getter/Setter에 사용 컴포넌트 커뮤니케이션 (부모 → 자식) @Component({ // 생략 }) export class TodoComponent { private _todo: Todo; get todo(): Todo { return this._todo; } @Input() set todo(v: Todo) { this._todo = v; v.text += " !!!"; } constructor() {} }
  • 14. ● 부모컴포넌트에서 자식 컴포넌트 인스턴스를 @ViewChild()로 가져옴 컴포넌트 커뮤니케이션 (부모 → 자식) <!-- todos.component.html 일부 --> <div class="title"> <app-title></app-title> <h2>{{ today | date:'M월 d일' }}</h2> </div> <!-- todos.component.ts 일부 --> export class TodosComponent implements OnInit { // 생략 @ViewChild(TitleComponent) titleComp :TitleComponent; ngOnInit() { this.titleComp.text = '나의 하루' } }
  • 15. 컴포넌트 커뮤니케이션 (자식 → 부모) 부모 컴포넌트 자식 컴포넌트 TodosComponent AddTodoComponent
  • 16. ● 자식컴포넌트에서 EventEmitter를 통해 부모가 전달 받을 이벤트를 발생하는 속성에 @Output() 데코레이터를 사용 컴포넌트 커뮤니케이션 (자식 → 부모) @Component({ selector: 'app-add-todo' , template: `<button (click)="btnClicked(newText)">+</button> <input type="text" placeholder=" 할 일 추가" [(ngModel)]="newText">` , styles: ['...'] // 생략 }) export class AddTodoComponent { @Output() onTodoAdded = new EventEmitter(); newText: string; constructor () { } btnClicked(newText: string) { this.onTodoAdded .emit(newText); this.newText = ''; } }
  • 17. ● 부모 컴포넌트는 $event로 이벤트의 데이터를 전달 받음 컴포넌트 커뮤니케이션 (자식 → 부모) <!-- todos.component.html 일부 --> <div> <app-add-todo (onTodoAdded)="addTodo($event)"></app-add-todo> </div> <!-- todos.component.ts 일부 --> export class TodosComponent { // 생략 addTodo(text: string) { this.todos.push({done : false, text}); } }
  • 18. ● 자식컴포넌트에서 부모컴포넌트를 주입받음 컴포넌트 커뮤니케이션 (자식 → 부모) @Component({ selector: 'app-add-todo' , template: `<button (click)="btnClicked(newText)">+</button> <input type="text" placeholder=" 할 일 추가" [(ngModel)]="newText">` , styles: ['...'] // 생략 }) export class AddTodoComponent { @Output() onTodoAdded = new EventEmitter (); newText: string; constructor(private todosComponent: TodosComponent) { } btnClicked(newText: string) { // this.onTodoAdded.emit(newText); this.todosComponent.addTodo(newText); this.newText = ''; } }
  • 20.
  • 22. 라우터를 연결하고 다른 모듈을 넣었다면?! <!-- app.component.html --> <app-drawer #drawer> <app-cart (onClose)="drawer.close()"></fc-cart> </app-drawer> <app-navi></app-navi> <main [ngClass]="{'m-t-main': !isHome}"> <router-outlet></router-outlet> </main>
  • 23. AppComponent CartComponent HomeComponent ProductComponent ProductComponent RouterOutlet App Module Home Module Route { path: 'home', component: HomeComponent }
  • 24. 서비스를 활용한 커뮤니케이션 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 서비스 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 손주 컴포넌트 서비스
  • 25. ● CartService를 통하여 카트아이템 배열CartItem[] 을 구독 CartComponent @Component({ selector: 'app-cart', templateUrl: './cart.component.html' , styleUrls: ['./cart.component.css' ] }) export class CartComponent { cart: CartItem[] = []; constructor (private cartService : CartService ) { this.cartService .cartItems .subscribe(v => this.cart = v) } remove(cartItem: CartItem) { this.cartService .remove(cartItem); } // 생략 } Observable<CartItem[]>
  • 26. ● CartService를 통하여 카트아이템 배열CartItem[] 을 구독 HomeComponent @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent { constructor( private cartService: CartService) { } addCart(product: Product) { this.cartService.addCart(product); } // 생략 }
  • 27. ● BehaviorSubject를 이용하여 로컬스토리지의 초기 로드값이나 마지막 값을 발행 CartService (1) @Injectable() export class CartService { private _items: CartItem[] = []; private cartSubject: BehaviorSubject<CartItem[]>; public cartItems: Observable<CartItem[]>; constructor() { const itemJson = localStorage.getItem(storageKey) if (itemJson) this._items = JSON.parse(itemJson); this.cartSubject = new BehaviorSubject(this._items); this.cartItems = this.cartSubject.asObservable(); } // 생략 }
  • 28. CartService (2) @Injectable() export class CartService { // 생략 addCart(product: Product) { const foundProduct = this._items.find(c => c.product.id === product.id); if (foundProduct) foundProduct.counts += 1; else this._items.push({ product, counts: 1 }); this.updateLocalStorage(this._items); this.cartSubject.next(this._items); } private updateLocalStorage(cartItems: CartItem[]) { localStorage.setItem(storageKey, JSON.stringify(cartItems)); } }
  • 29. CartService (3) @Injectable() export class CartService { // 생략 remove(cartItem: CartItem) { const foudnItem = this.cart.find(v => v.product.id === cartItem.product.id) if (foudnItem && foudnItem.counts > 1) { foudnItem.counts -= 1; } else { const index = this.cart.indexOf(foudnItem); this.cart.splice(index, 1); } this.updateLocalStorage(); this.cartSubject.next(this.cart); } }
  • 30. 하지만 서비스와 컴포넌트가 아주 많아지면? 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 서비스 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 손주 컴포넌트 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스
  • 31.
  • 32. 자바스크립트 앱을 위한 예측가능한 상태 컨테이너 PREDICTABLE STATE CONTAINER FOR JAVASCRIPT APPS
  • 33. WITHOUT REDUX ● 컴포넌트간 직접적인 통신 (속성 바인딩, eventEmitter 활용)
  • 34. WITH REDUX ● 컴포넌트간의 직접 통신이 없다. ● 스토어를 통한 단 하나의 상태로 관리
  • 35. REDUX Store 1. 컴포넌트에서 액션action 을 보냄dispatch 2. 스토어는 변화를 적용한다. 3. 컴포넌트는 관련된 상태를 전달받는다. (subscribe에 의해서)
  • 39. ● @ngrx - Reactive Extensions for Angular ngrx (https://ngrx.github.io/)
  • 40.
  • 42. Setup ● npm install @ngrx/store --save후 StoreModule 모듈 임포트 import { NgModule } from '@angular/core' import { StoreModule } from '@ngrx/store'; import { counterReducer } from './counter'; @NgModule({ imports: [ BrowserModule, StoreModule.forRoot({ counter: counterReducer }) // ActionReducerMap 전달 ] }) export class AppModule {}
  • 43. Reducer (counter.reducer.ts) import { Action } from'@ngrx/store'; import * as CounterActions from './coutner.actions'; export function counterReducer(state: number = 0, action: CounterActions.All): number { switch(action.type) { case CounterActions.INCREMENT: return state + 1; case CounterActions.DECREMENT: return state - 1; case CounterActions.RESET: return action.payload default: return state; } }
  • 44. Action (counter.actions.ts) import { Action } from '@ngrx/store'; export const INCREMENT = '[Counter] Increment'; export const DECREMENT = '[Counter] Decrement'; export const RESET = '[Counter] Reset'; export class Increment implements Action { readonly type = INCREMENT; } export class Decrement implements Action { readonly type = DECREMENT; } export class Reset implements Action { readonly type = RESET; constructor(public payload: number) {} } export type All = Increment | Decrement | Reset;
  • 45. Component export interface AppState { counter: number } @Component({ selector: 'my-app', template: ` <button (click)="increment()">Increment</button> <div>Current Count: {{ counter | async }}</div> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset Counter</button> `}) export class CounterComponent { counter: Observable<number>; constructor (private store: Store<AppState>) { this.counter = store.select('counter'); } increment(){ this.store.dispatch(new Counter.Increment()); } decrement(){ this.store.dispatch(new Counter.Decrement()); } reset(){ this.store.dispatch(new Counter.Reset(1)); } }
  • 46.
  • 48. AppModule import { StoreModule } from '@ngrx/store'; import * as fromRoot from './store'; @NgModule({ imports: [ CommonModule, StoreModule.forRoot(fromRoot.reducers, {initialState: fromRoot.getInitialState()}), ], providers: [ // 생략 ], declarations: [] }) export class AppModule { }
  • 49. import * as fromSales from './sales/sales.reducer' ; import * as fromOrder from './order/order.reducer' ; export interface AppState { sales: fromSales.State; orders: fromOrder.OrderState; } export const initialState : AppState = { sales: fromSales.initialState , orders: fromOrder.initialState }; export function getInitialState (): AppState { return initialState ; } export const reducers: ActionReducerMap <AppState> = { sales: fromSales.reducer, orders: fromOrder.reducer }; store/index.ts
  • 50. import { Order } from '../../models/order.model'; import * as moment from 'moment'; export interface OrderState { orders: Order[]; selectedOrder: Order; from: Date; to: Date; } export const initialState: OrderState = { orders: [], selectedOrder: null, from: moment().toDate(), to: moment().startOf('day').toDate() }; order/order.reducer.ts
  • 51. export const SEARCH_ORDERS = '[Order] SEARCH_ORDERS'; export const SELECT_ORDER = '[Order] SELECT_ORDER'; export const RESET = '[Order] RESET'; export class SearchOrder implements Action { readonly type = SEARCH_ORDERS; } export class SelectOrder implements Action { readonly type = SELECT_ORDER; constructor(public order: Order) {} } export class Reset implements Action { readonly type = RESET; } export type All = ReqSearchOrders | SearchOrder | SelectOrder | Reset; order/order.action.ts
  • 52. import * as orderActions from './order.action'; export function reducer(state = initialState, action: orderActions.All): OrderState { switch (action.type) { case orderActions.SEARCH_ORDERS: return Object.assign({}, state, { orders: [ /* 오더 데이터 생략… */ ]); case orderActions.SELECT_ORDER: return Object.assign({}, state, { selectedOrder: action.order }); case orderActions.RESET: return Object.assign({}, state, { selectedOrder: null }); default: return state; } } order/order.reducer.ts
  • 53. @Component({ selector: 'app-table' , templateUrl: './table.component.html' , styleUrls: ['./table.component.scss' ] }) export class TableComponent implements OnInit { orders: Observable<Order[]>; selectedOrder : Order; constructor (private store: Store<AppState>) { this.orders = store.select(v => v.orders.orders) this.store.select(v => v.orders.selectedOrder ) .subscribe(v => this.selectedOrder = v); } select(p: Order) { if (p === this.selectedOrder ) { this.store.dispatch(new orderActions .Reset()); } else { this.store.dispatch(new orderActions .SelectOrder (p)); } } } TableComponent
  • 54. @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.scss'] }) export class DashboardComponent implements OnInit { constructor(private store: Store<AppState>) { } ngOnInit() { this.store.dispatch(new SearchOrders()) this.store.dispatch(new GetTotalSales()) } } DashboardComponent
  • 55. 코드에 박힌 데이터 말고 실제 서비스는 어떻게 하는데…
  • 58. export const REQ_SEARCH_ORDERS = '[Order] REQ_SEARCH_ORDERS'; export const REQ_SEARCH_SUCCESS = '[Order] REQ_SEARCH_SUCCESS'; export const SELECT_ORDER = '[Order] SELECT_ORDER'; export const RESET = '[Order] RESET'; export class ReqSearchOrders implements Action { readonly type = REQ_SEARCH_ORDERS; constructor(public orderNumber?: string) {} } export class ReqSearchSuccess implements Action { readonly type = REQ_SEARCH_SUCCESS; constructor(public orders: Order[]) {} } // 생략 order/order.action.ts
  • 59. export function reducer(state = initialState, action: orderActions.All): OrderState { switch (action.type) { case orderActions.REQ_SEARCH_ORDERS: return state; case orderActions.REQ_SEARCH_SUCCESS: return Object.assign({}, state, { orders: action.orders }); case orderActions.SELECT_ORDER: return Object.assign({}, state, { selectedOrder: action.order }); case orderActions.RESET: return Object.assign({}, state, { selectedOrder: null }); default: return state; } } order/order.reducer.ts
  • 60. import { Effect, Actions } from '@ngrx/effects'; import * as orderAction from './order.action'; @Injectable() export class OrderEffects { @Effect() request$: Observable<Action> = this.actions$ .ofType<orderAction.ReqSearchOrders>(orderAction.REQ_SEARCH_ORDERS) .map(v => v.orderNumber) .mergeMap(num => { return this.orderService.getOrders(num) .map((orders: Order[]) => new orderAction.ReqSearchSuccess(orders)) .catch(() => Observable.of(new orderAction.ReqSearchSuccess([]))); }); constructor(private actions$: Actions, private orderService: OrderService) { } } order/order.effects.ts
  • 61.
  • 62. import * as salesAction from '../sales/sales.action'; @Effect() forward$: Observable<Action> = this.actions$ .ofType<orderAction.SelectOrder|orderAction.Reset>(orderAction.SELECT_ORDER, orderAction.RESET) .map((a) => { if (a instanceof orderAction.SelectOrder) { return a.order.name; } else { return null; } }) .map(name => new salesAction.ReqGivenItemSales(name)); order/order.effects.ts
  • 63. import * as salesAction from '../sales/sales.action'; @Effect() forward$: Observable<Action> = this.actions$ .ofType<orderAction.SelectOrder|orderAction.Reset>(orderAction.SELECT_ORDER, orderAction.RESET) .map((a) => { if (a instanceof orderAction.SelectOrder) { return a.order.name; } else { return null; } }) .map(name => new salesAction.ReqGivenItemSales(name)); order/order.effects.ts // TableComponent select(p: Order) { if (p === this.selectedOrder) { this.store.dispatch(new orderActions.Reset()); } else { this.store.dispatch(new orderActions.SelectOrder(p)); } } }
  • 64. sales/sales.effects.ts @Injectable() export class SalesEffects { @Effect() requestByItem$: Observable<Action> = this.actions$ .ofType<ReqGivenItemSales>(salesAction.REQ_GIVEN_ITEM_SALES) .map(v => v.itemNum) .mergeMap(itemName => { return this.selesService.getSales(itemName) .map((sales: Sales[]) => new salesAction.ReqGetSalesSuccess(sales)) .catch(() => Observable.of(new salesAction.ReqGetSalesSuccess([]))); }); constructor(private actions$: Actions, private selesService: SalesService) { } }
  • 66. sidebar-routing.module.ts const routes: Routes = [{ path: 'events', component: EventListComponent }, { path: 'events/:num', component: EventDetailComponent }, { path: '', redirectTo: 'events' }]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class SidebarRoutingModule { }
  • 68. /** * Payload of ROUTER_NAVIGATION. */ export declare type RouterNavigationPayload<T> = { routerState: T; event: RoutesRecognized; }; /** * An action dispatched when the router navigates. */ export declare type RouterNavigationAction<T = RouterStateSnapshot> = { type: typeof ROUTER_NAVIGATION; payload: RouterNavigationPayload<T>; };
  • 69. EventListComponent @Component({ selector: 'app-event-list', templateUrl: './event-list.component.html', styleUrls: ['./event-list.component.scss'] }) export class EventListComponent { orderEvents: Observable<OrderEvent[]> selectedOrderEvent: OrderEvent; constructor(private store: Store<AppState>, private route: ActivatedRoute) { this.orderEvents = store.select(v => v.events.orderEvents) } }
  • 70. event-list.component.html 일부 <ul class="sidebar-list"> <li *ngFor="let event of orderEvents | async" [routerLink]="[event.orderNumber]"> <a> <span class="label label-primary pull-right">NEW</span> <h4>주문번호: {{event.orderNumber}}</h4> {{event.text}} <div class="small">수량: {{event.salesNumber}}</div> <div class="small text-muted m-t-xs">판매시간 - {{event.date | date:'medium'}}</div> </a> </li> </ul>
  • 73. ● https://css-tricks.com/learning-react-redux/ ● https://github.com/ngrx/platform ● https://gist.github.com/btroncone/a6e4347326749f938510 ● https://blog.nextzy.me/manage-action-flow-in-ngrx-with-ngrx-effects-1fda3fa06c2f reference