블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2019. 1. 9. 18:26 Angular/Architecture

Nx Workspace와 NgRx/Platform을 연결한 boilerplate를 만들어 본다. 


  - Nx Workspace 생성하기 

  - NgRx 개념이해  및 사용예 만들기

  - 다음 3가지 문서와 동영상을 참조한다.

    + Nx Workspace 무료 동강 수강

    + Nx Workspace 메뉴얼

    + NgRx와 Nx Workshop 강좌

    + State Management Type 정리

    + Nx Workspace 기반 예제소스




Nx Workspace 생성


하나의 저장소에 여러 애플리케이션을 개발할 수 있는 workspace를 최초 한번 생성한다.  

  - 현재 최신 버전의 스케매티과 CLI를 설치한다. v7.* 이다.

  - workspace 생성시 npmScope를 설정하여 workspace의 공통 library를 import할 때 prefix로 사용된다. 예로 @jm/<library name> 식이다. 

     또한 컴폰너트 selector의 prefix로 사용된다.

  - workspace 생성시 옵션은 scss, npm (또는 yarn)을 선택한다.

$ npm i -g @nrwl/schematics @angular/cli


// create -react-app과 유사, 첫번째 인자는 workspace 명칭이다. 

$ create-nx-workspace  jamong  --npm-scope=jm


nx workspace에서 사용(2019.1.8)기준 Redux 패턴의 구현체인 ngrx/platform 버전이 v6.*으로 설정되어 있기에 v7.0.0으로 변경한다. 

  - <workspace>/package.json에서 @ngrx/xxxx 의 버전을 ^7.0.0 으로 설정한다. 

$ cd jamong

$ vi package.json


// 수정후 다시 install 한다.

$ npm i


다음으로 샘플 애플리케이션을 생성한다. 

  - g는 generate, app은 application 이다. 

  - dashboard 명칭의 애플리케이션을 생성한다.

  - routing, unit test - jest, e2e - cypress, linting등을 선택한다.

$ ng g app dashboard

또는

$ ng generate application dashboard


// 작동여부 확인

$ npm start --project=dashboard


다음으로 애플리케이션안에 component를 자동생성할 때 옵션을 미리 설정해 놓는다. angular workspace의 schematics 설정 목록을 참조한다.

  - <workspace>/angular.json 파일의 dashboard 애플리케이션에 하기 항목을 추가한다. 

  - OnPush, viewEncapsulation 적용

생성시 적용될 수 있도록 맨 하단에도 동일한 설정을 한다. 

  "schematics": {

    "@nrwl/schematics:application": {

      "style": "scss",

      "changeDetection": "OnPush",

      "viewEncapsulation": "None"

    },

    "@nrwl/schematics:library": {

      "style": "scss",

      "changeDetection": "OnPush",

      "viewEncapsulation": "None"

    }

  },

  "defaultProject": "mi-dashboard"



샘플 컴포넌트를 생성한다.

  - --project옵션을 통해 어느 프로젝트안에 생성할지 선택한다.

  - / 로 path를 지정할 수 있다.

  - component 생성 명령 설명

$ ng g component layout/page --project=dashboard

CREATE apps/dashboard/src/app/layout/page/page.component.scss (0 bytes)

CREATE apps/dashboard/src/app/layout/page/page.component.html (23 bytes)

CREATE apps/dashboard/src/app/layout/page/page.component.spec.ts (614 bytes)

CREATE apps/dashboard/src/app/layout/page/page.component.ts (261 bytes)

UPDATE apps/dashboard/src/app/app.module.ts (555 bytes)


공통 라이브러리도 생성해 본다.

  - tag를 통해 소스 분석시 논리적 그룹을 주고, tag들 끼리의 의존관계를 설정할 수도 있다. 

  - library를 생성하면 <workspace>/angular.json, nx.json, tsconfig.json 파일에 자동으로 정보가 입력된다.

  - library 생성 명령 설명

$ ng g lib shared --tags=shared-lib

? In which directory should the library be generated?

? Which module should import the library?

? Would you like to add a routing configuration to the library? No

? Will this library be lazy loaded? No

? Would you like to generate an NgModule within the library? Yes

? Which Unit Test Runner would you like to use for the library? Jest (https://

jestjs.io/)

CREATE libs/shared/tsconfig.lib.json (705 bytes)

CREATE libs/shared/tslint.json (185 bytes)

CREATE libs/shared/src/index.ts (37 bytes)

... 생략


Unit Test는 Jest를 사용하고, e2e는 cypress를 사용하므로 Karma, Jasmine와 Protractor관련 내용은 삭제한다. 

  - package.json안에 karma, jamine관련 패키지 삭제 (jasmine-marbles 패키지는 제외)

  - karma.conf.json 파일삭제

  - yarn.lock, package-lock.json파일 삭제

  - node_modules를 제거하고 다시 npm install을 수행한다. 




NgRx/Platform 적용하기


Redux 패턴의 Angular 버전인 NgRx/platform을 적용한다. 엔터프라이즈 아키텍트를 적용하여 Store/State 접근방식을 SandBox를 통한 접근토록 만든다. libs/appState 에 공통적인 부분을 구현한다. Angular Enterprise Architecture Pattern을 참조한다.

  - Application Core Facade는 Parent Class로 Presentation Module에서 사용하는 Sandbox가 상속을 받는다.

  - "State Management" library를 생성한다. 여기에는 기본 설정항목과 None Visual Domain 영역에 대한 State관리를 관장한다. 

     이것은 forRoot가 된다.

  - Common UI는 Library별로 존재하며 State Management는 각 UI별로 구현한다. 

     이들은 forFeature가 된다.


Application을 위한 ngrx state를 생성한다. 

  - appState 명칭과 --module 옵션을 반드시 설정해야 한다. 

  - 해당 명령을 수행하면 ngrx/entity가 자동 설치되는데 v6.* 가 설치되므로 package.json에서 v7.*으로 변경하고 다시 npm install 수행한다. 

  - application레벨의 root state를 선택한다. (yes) => app.module.ts에 forRoot가 자동 설정된다.

  - facade도 yes를 선택한다.

  - 명령 옵션 목록

$ ng g ngrx appState --module=./apps/dashboard/src/app/app.module.ts

? Is this the root state of the application? Yes

? Would you like to add a Facade to your ngrx state Yes


생성 파일들


Redux에서는 action, reducer가 기본이고, (side) effect라는 부수효과는 reducer를 타기전 ajax 호출같은 async를 처리할 때 이용한다. selector는 state의 내용을 Domain 요구에 따라 조합해서 반환하는 서비스이다. 그럼 Facade는 무엇일까?

  - Redux패턴에서 state값을 조합해서 얻기위해 성능향상을 줄 수 있는 것이 reselector개념이고, 이를 selector에서 구현한다. 참조글

  - Redux패턴에서의 Facade 글을 참조한다. facade 서비스를 통해서 데이터를 가져온다. 데모 소스

  - Facade 서비스가 Presentation 컴포넌트에서 사용하는 Biz 로직을 담고 있는 SandBox이다. 

  


AppStateFacade는 forFeature에서 사용하는 common한 내용을 담는다. 즉 "Application core facade" 이다.

  - schematics을 커스텀으로 만들어 사용할 수 있다.

import { Injectable } from '@angular/core';


import { select, Store } from '@ngrx/store';


import { AppStatePartialState } from './app-state.reducer';

import { appStateQuery } from './app-state.selectors';

import { LoadAppState } from './app-state.actions';


@Injectable()

export class AppStateFacade {

  loaded$ = this.store.pipe(select(appStateQuery.getLoaded));

  allAppState$ = this.store.pipe(select(appStateQuery.getAllAppState));

  selectedAppState$ = this.store.pipe(

    select(appStateQuery.getSelectedAppState)

  );


  constructor(private store: Store<AppStatePartialState>) {}


  // 업무 메소드, Action을 생성하고 dispatch하는 문구가 존재한다.

  loadAll() {

    this.store.dispatch(new LoadAppState());

  }

}


forFeature에 대한 것은 libs 폴더 밑에 생성한다.

  - library 생성을 한다. 이때 옵션으로 --directory=<groupName>을 주면 라이브러리들을 directory명칭 밑으로 그룹핑할 수 있다. 

  - 생성된 library에 menu에 대한 feature 레벨 state를 생성한다. 

$ ng g lib menu --directory=state

? Which module should import the library?

? Would you like to add a routing configuration to the library? No

? Will this library be lazy loaded? No

? Would you like to generate an NgModule within the library? Yes

? Which tags would you like to add to the library? (used for linting)

? Which Unit Test Runner would you like to use for the library? Jest

CREATE libs/state/menu/tsconfig.lib.json (712 bytes)

CREATE libs/state/menu/tslint.json (188 bytes)

CREATE libs/state/menu/src/index.ts (41 bytes)  <== state 그룹 밑으로 menu library가 생성되었다. 

CREATE libs/state/menu/src/lib/state-menu.module.ts (164 bytes)

... 중략


다음으로 menu state를 생성한다. 

  - 반드시 library의 모듈을 수행하는 명령의 상대경로로 지정한다.

$ ng g ngrx menu --module=libs/state/menu/src/lib/state-menu.module.ts

? Is this the root state of the application? No

? Would you like to add a Facade to your ngrx state Yes

CREATE libs/state/menu/src/lib/+state/menu.actions.ts (731 bytes)

CREATE libs/state/menu/src/lib/+state/menu.effects.spec.ts (1147 bytes)

CREATE libs/state/menu/src/lib/+state/menu.effects.ts (842 bytes)

... 중략


state-menu.module.ts에 forFeature가 다음과 같이 자동 등록된다. forFeature는 lazy loading 또한 가능하다.

  - state에 대한 forFeature 등록

  - effect에 대한 forFeature 등록

import { NgModule } from '@angular/core';

import { CommonModule } from '@angular/common';

import { StoreModule } from '@ngrx/store';

import { EffectsModule } from '@ngrx/effects';

import {

  MENU_FEATURE_KEY,

  initialState as menuInitialState,

  menuReducer

} from './+state/menu.reducer';

import { MenuEffects } from './+state/menu.effects';

import { MenuFacade } from './+state/menu.facade';


@NgModule({

  imports: [

    CommonModule,

    StoreModule.forFeature(MENU_FEATURE_KEY, menuReducer, {

      initialState: menuInitialState

    }),

    EffectsModule.forFeature([MenuEffects])

  ],

  providers: [MenuFacade]

})

export class StateMenuModule {}


StateMenuModule을 app.module.ts에 등록한다. 

  - @jm/state/menu 경로로 import한다. 이는 <workspace>/tsconfig.json에 path가 자동 설정되기에 가능하다. 

import { StateMenuModule } from '@jm/state/menu';


@NgModule({

  declarations: [AppComponent, PageComponent],

  imports: [

    BrowserModule,

    NxModule.forRoot(),

    RouterModule.forRoot([], { initialNavigation: 'enabled' }),

    StoreModule.forRoot(

      { appState: appStateReducer },

      {

        initialState: { appState: appStateInitialState },

        metaReducers: !environment.production ? [storeFreeze] : []

      }

    ),

    EffectsModule.forRoot([AppStateEffects]),

    !environment.production ? StoreDevtoolsModule.instrument() : [],

    StoreRouterConnectingModule,


    StateMenuModule

  ],

  providers: [AppStateFacade],

  bootstrap: [AppComponent]

})

export class AppModule {}


effect 클래스를 보면 DataPersistence 헬퍼 클래스를 Nx에서 제공한다. 

  - async 처리에 대한 추상화이다. 

  - 4가지 주요 헬퍼 메소드 제공

    + optimisticUpdate: 요청값을 client에 먼저 반영하고 만일 backend 오류 발생시 이전 상태로 복원

    + pessimisticUpdate: 요청값을 backend에 반영하고 이후 client에 반영

    + fetch: 같은 Action에 대한 다중 요청은 가장 최신 것만 반영함. pessimisticUpdate이면서 같은 Action에 따른 요청은 최신만 반영하는 것.

    + navigation: routing될 때 effect 자동 호출

  - DataPersistence에 대한 상세 설명

  - Angular Application State관리 방법

import { Injectable } from '@angular/core';

import { Effect, Actions } from '@ngrx/effects';

import { DataPersistence } from '@nrwl/nx';


import { MenuPartialState } from './menu.reducer';

import {

  LoadMenu,

  MenuLoaded,

  MenuLoadError,

  MenuActionTypes

} from './menu.actions';



@Injectable()

export class MenuEffects {

  @Effect() loadMenu$ = this.dataPersistence.fetch(

    MenuActionTypes.LoadMenu, 

    {

      run: (action: LoadMenu, state: MenuPartialState) => {

        // Your custom REST 'load' logic goes here. For now just return an empty list...

        return new MenuLoaded([]);

      },


      onError: (action: LoadMenu, error) => {

        console.error('Error', error);

        return new MenuLoadError(error);

      }

  });


  constructor(

    private actions$: Actions,

    private dataPersistence: DataPersistence<MenuPartialState>

  ) {}

}



<참조>

- Nx Workspace 홈페이지

- Nx Github

- Angular Workspace의 Schematics 설정 목록

Nx workspace에서 component 생성하기 명령

- Angular Enterprise Architecture Pattern

- NgRx Schematics 설정

- Redux의 reselector를 통한 성능향상

- NgRx Facade 이해

- Angular Application State관리 방법

- Ionic + Angular + ngrx 사례

posted by 윤영식