[feconf korea 2017]angular 컴포넌트 대화법

73
Angular 컴포넌트 대화법 Jeado Ko

Upload: jeado-ko

Post on 21-Jan-2018

1.005 views

Category:

Technology


2 download

TRANSCRIPT

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

Angular 컴포넌트 대화법Jeado Ko

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

+jeado.ko (고재도)[email protected]

- “Google Developer Expert” WebTech

- “Kakao Bank 빅데이터 파트” Developer

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

질문이 있습니다!

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

컴포넌트

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

● 명세specification를 가진 재사용할 수 있는reusable 소프트웨어 구성요소 (위키피디아)● 웹 애플리케이션의 기본 구성요소로 HTML 요소들을 포함● 독립된 구성요소로 뷰와 로직으로 구성됨● 컴포넌트들은 단방향 트리형태로 구성되고 최상위 루트 컴포넌트가 존재

Public API

사진 출처: https://v1.vuejs.org/guide/overview.html

컴포넌트 개요

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

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!!';

}

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

컴포넌트 계층구조간 커뮤니케이션

부모

컴포넌트

자식 컴포넌트

부모

컴포넌트

자식 컴포넌트

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

손주컴포넌트

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

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

컴포넌트 계층구조간 커뮤니케이션

부모

컴포넌트

자식 컴포넌트

부모

컴포넌트

자식 컴포넌트

Page 9: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 10: [FEConf Korea 2017]Angular 컴포넌트 대화법

컴포넌트 커뮤니케이션 (부모 → 자식)

부모

컴포넌트

자식 컴포넌트

TodosComponent

TodoComponent

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

● 자식컴포넌트에서 부모가 전달할 속성에 @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() { }}

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

● 부모 컴포넌트에서는 속성 바인딩을 통해 데이터 전달

컴포넌트 커뮤니케이션 (부모 → 자식)

<!-- 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: '공부하기'} ]; }|

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

● 자식 컴포넌트에서 @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() {}}

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

● 부모컴포넌트에서 자식 컴포넌트 인스턴스를 @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 = '나의 하루' }}

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

컴포넌트 커뮤니케이션 (자식 → 부모)

부모

컴포넌트

자식 컴포넌트

TodosComponent

AddTodoComponent

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

● 자식컴포넌트에서 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 = ''; }}

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

● 부모 컴포넌트는 $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}); }}

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

● 자식컴포넌트에서 부모컴포넌트를 주입받음

컴포넌트 커뮤니케이션 (자식 → 부모)

@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 = ''; }}

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

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

손주컴포넌트

Page 20: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 21: [FEConf Korea 2017]Angular 컴포넌트 대화법

AppComponent

CartComponentHomeComponent

ProductComponent ProductComponent

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

라우터를 연결하고 다른 모듈을 넣었다면?!

<!-- 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>

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

AppComponent CartComponent

HomeComponent

ProductComponent ProductComponent

RouterOutlet

App Module

Home Module

Route{ path: 'home', component: HomeComponent}

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

서비스를 활용한 커뮤니케이션

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

서비스

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

손주컴포넌트

서비스

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

● 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[]>

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

● 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);

}

// 생략}

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

● 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(); } // 생략}

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

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));

}

}

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

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);

}

}

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

하지만 서비스와 컴포넌트가 아주 많아지면?

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

서비스

부모 컴포넌트

자식 컴포넌트

자식 컴포넌트

손주컴포넌트

서비스

서비스서비스서비스서비스서비스

서비스서비스서비스서비스서비스

Page 31: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 32: [FEConf Korea 2017]Angular 컴포넌트 대화법

자바스크립트 앱을 위한 예측가능한 상태

컨테이너

PREDICTABLE STATE CONTAINERFOR JAVASCRIPT APPS

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

WITHOUT REDUX

● 컴포넌트간 직접적인 통신

(속성 바인딩, eventEmitter 활용)

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

WITH REDUX

● 컴포넌트간의 직접 통신이 없다.

● 스토어를 통한 단 하나의 상태로

관리

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

REDUX Store

1. 컴포넌트에서 액션action을

보냄dispatch

2. 스토어는 변화를 적용한다.

3. 컴포넌트는 관련된 상태를

전달받는다. (subscribe에 의해서)

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

REDUX Reducer

(state, action) => state

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

Actions

Reducers

Store

View(Component)

subscribe

change state dispatch

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

angular-reduxreact-redux ngrx

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

● @ngrx - Reactive Extensions for Angular

ngrx (https://ngrx.github.io/)

Page 40: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 41: [FEConf Korea 2017]Angular 컴포넌트 대화법

참고 : https://gist.github.com/btroncone/a6e4347326749f938510

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

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 {}

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

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; }}

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

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;

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

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)); }}

Page 46: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 47: [FEConf Korea 2017]Angular 컴포넌트 대화법

DashboardComponent

TableComponentGraphComponent

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

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 { }

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

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

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

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

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

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

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

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

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

@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

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

@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

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

코드에 박힌 데이터 말고 실제 서비스는 어떻게 하는데…

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

@ngrx/effects

출처: https://blog.nextzy.me/manage-action-flow-in-ngrx-with-ngrx-effects-1fda3fa06c2f

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

Actions

Reducers

Store

View(Component)

subscribe

change statedispatch

Effect

@ngrx/effects flow

Service

subscribe

dispatch

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

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

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

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

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

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

Page 61: [FEConf Korea 2017]Angular 컴포넌트 대화법
Page 62: [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

Page 63: [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

// TableComponentselect(p: Order) { if (p === this.selectedOrder) { this.store.dispatch(new orderActions.Reset()); } else { this.store.dispatch(new orderActions.SelectOrder(p)); } }}

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

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) { }

}

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

Sidebar 모듈!라우팅이 존재!

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

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 { }

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

@ngrx/router-store

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

/**

* 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>;

};

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

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)

}

}

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

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>

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

EventEffects는 에디터에서 보겠습니다.

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

감사합니다.

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

● 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