SlideShare une entreprise Scribd logo
1  sur  73
Télécharger pour lire hors ligne
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!!' ;
}
컴포넌트 계층구조간 커뮤니케이션
부모
컴포넌트
자식
컴포넌트
부모
컴포넌트
자식
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
컴포넌트 계층구조간 커뮤니케이션
부모
컴포넌트
자식
컴포넌트
부모
컴포넌트
자식
컴포넌트
[FEConf Korea 2017]Angular 컴포넌트 대화법
컴포넌트 커뮤니케이션 (부모 → 자식)
부모
컴포넌트
자식
컴포넌트
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 = '';
}
}
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
[FEConf Korea 2017]Angular 컴포넌트 대화법
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);
}
}
하지만 서비스와 컴포넌트가 아주 많아지면?
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
서비스
부모 컴포넌트
자식
컴포넌트
자식
컴포넌트
손주
컴포넌트
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
서비스
[FEConf Korea 2017]Angular 컴포넌트 대화법
자바스크립트 앱을 위한
예측가능한 상태
컨테이너
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/)
[FEConf Korea 2017]Angular 컴포넌트 대화법
참고 : 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)); }
}
[FEConf Korea 2017]Angular 컴포넌트 대화법
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
[FEConf Korea 2017]Angular 컴포넌트 대화법
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

Contenu connexe

Tendances

IndexedDB - Querying and Performance
IndexedDB - Querying and PerformanceIndexedDB - Querying and Performance
IndexedDB - Querying and PerformanceParashuram N
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJSWei 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 BasicsEPAM Systems
 
Modules and injector
Modules and injectorModules and injector
Modules and injectorEyal Vardi
 
AngularJS: an introduction
AngularJS: an introductionAngularJS: an introduction
AngularJS: an introductionLuigi 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 UJoonas Lehtinen
 
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 JSSimon Guest
 
AngularJS Internal
AngularJS InternalAngularJS Internal
AngularJS InternalEyal Vardi
 
AngularJS Directives
AngularJS DirectivesAngularJS Directives
AngularJS DirectivesEyal Vardi
 
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 ReactJSVisual Engineering
 
AngularJS in 60ish Minutes
AngularJS in 60ish MinutesAngularJS in 60ish Minutes
AngularJS in 60ish MinutesDan Wahlin
 

Tendances (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
 

En vedette

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
 

En vedette (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] 강남 출근길에 판교/정자역에 내릴 사람 예측하기
 

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

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 exampleAlexey 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 applicationsEvangelia 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 2Commit University
 
Angular JS2 Training Session #2
Angular JS2 Training Session #2Angular JS2 Training Session #2
Angular JS2 Training Session #2Paras Mendiratta
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next FrameworkCommit University
 
angular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfangular fundamentals.pdf angular fundamentals.pdf
angular fundamentals.pdf angular fundamentals.pdfNuttavutThongjor1
 
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 componentYao 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.0Frost
 
Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018Advanced React Component Patterns - ReactNext 2018
Advanced React Component Patterns - ReactNext 2018Robert Herbst
 
React 16: new features and beyond
React 16: new features and beyondReact 16: new features and beyond
React 16: new features and beyondArtjoker
 
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 3Rob 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éutilisablesRiad Benguella
 

Similaire à [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
 

Dernier

Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...DianaGray10
 
Comparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioComparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioChristian Posta
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8DianaGray10
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6DianaGray10
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?IES VE
 
Computer 10: Lesson 10 - Online Crimes and Hazards
Computer 10: Lesson 10 - Online Crimes and HazardsComputer 10: Lesson 10 - Online Crimes and Hazards
Computer 10: Lesson 10 - Online Crimes and HazardsSeth Reyes
 
Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Adtran
 
COMPUTER 10 Lesson 8 - Building a Website
COMPUTER 10 Lesson 8 - Building a WebsiteCOMPUTER 10 Lesson 8 - Building a Website
COMPUTER 10 Lesson 8 - Building a Websitedgelyza
 
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve Decarbonization
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve DecarbonizationUsing IESVE for Loads, Sizing and Heat Pump Modeling to Achieve Decarbonization
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve DecarbonizationIES VE
 
Introduction to Matsuo Laboratory (ENG).pptx
Introduction to Matsuo Laboratory (ENG).pptxIntroduction to Matsuo Laboratory (ENG).pptx
Introduction to Matsuo Laboratory (ENG).pptxMatsuo Lab
 
NIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopNIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopBachir Benyammi
 
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdf
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdf
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdfJamie (Taka) Wang
 
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDEADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDELiveplex
 
Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Brian Pichman
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsSafe Software
 
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019IES VE
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-pyJamie (Taka) Wang
 
AI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarAI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarPrecisely
 

Dernier (20)

Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
 
20230104 - machine vision
20230104 - machine vision20230104 - machine vision
20230104 - machine vision
 
Comparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and IstioComparing Sidecar-less Service Mesh from Cilium and Istio
Comparing Sidecar-less Service Mesh from Cilium and Istio
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6
 
20150722 - AGV
20150722 - AGV20150722 - AGV
20150722 - AGV
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?
 
Computer 10: Lesson 10 - Online Crimes and Hazards
Computer 10: Lesson 10 - Online Crimes and HazardsComputer 10: Lesson 10 - Online Crimes and Hazards
Computer 10: Lesson 10 - Online Crimes and Hazards
 
Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™Meet the new FSP 3000 M-Flex800™
Meet the new FSP 3000 M-Flex800™
 
COMPUTER 10 Lesson 8 - Building a Website
COMPUTER 10 Lesson 8 - Building a WebsiteCOMPUTER 10 Lesson 8 - Building a Website
COMPUTER 10 Lesson 8 - Building a Website
 
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve Decarbonization
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve DecarbonizationUsing IESVE for Loads, Sizing and Heat Pump Modeling to Achieve Decarbonization
Using IESVE for Loads, Sizing and Heat Pump Modeling to Achieve Decarbonization
 
Introduction to Matsuo Laboratory (ENG).pptx
Introduction to Matsuo Laboratory (ENG).pptxIntroduction to Matsuo Laboratory (ENG).pptx
Introduction to Matsuo Laboratory (ENG).pptx
 
NIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopNIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 Workshop
 
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdf
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdf
activity_diagram_combine_v4_20190827.pdfactivity_diagram_combine_v4_20190827.pdf
 
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDEADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
ADOPTING WEB 3 FOR YOUR BUSINESS: A STEP-BY-STEP GUIDE
 
Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
 
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019
IESVE Software for Florida Code Compliance Using ASHRAE 90.1-2019
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-py
 
AI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity WebinarAI You Can Trust - Ensuring Success with Data Integrity Webinar
AI You Can Trust - Ensuring Success with Data Integrity Webinar
 

[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. 컴포넌트 계층구조간 커뮤니케이션 부모 컴포넌트 자식 컴포넌트 부모 컴포넌트 자식 컴포넌트 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 손주 컴포넌트 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트
  • 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 = ''; } }
  • 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. 하지만 서비스와 컴포넌트가 아주 많아지면? 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 서비스 부모 컴포넌트 자식 컴포넌트 자식 컴포넌트 손주 컴포넌트 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스 서비스
  • 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/)
  • 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)); } }
  • 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
  • 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