Search results for 'ngxs'

Angular Test Tips (Mock)

2019. 6. 1. 15:35

한동안 개발 잉여력이 떨어져서 오래 쉬었네요.

회사에서 바쁘면 집에서 개발을 덜하게 됩니다.

개발을 천천히 하니 글 쓸 일을 더욱 없다는게...

 

그렇다해서 지금 바쁘지 않다는건 아니지만.

더 밀리면 안쓰고 넘어갈 것 같아서 기록합니다.

기록하다보면 더 정리되는 기분도 있구요.

 

TDD를 연습하자는 취지로 진행중인데,

소스가 많아질 수록 점점 Test가 어려워집니다.

Angular 또한 예외는 아니구요.

 

그 어려움을 덜어낸 방법 2가지 공유합니다.

 

1. Component Mocking

 

자식(Child) 컴포넌트를 포함하는

부모(Parent) 컴포넌트를 테스트하다보면

점점 테스트 설정이 무거워집니다.

 

부모 컴포넌트의 의존성 뿐 아니라

자식 컴포넌트의 의존성을 모두 주입해야하니까요.

그래서 자식컴포넌트를 Mock으로 주입하고

자식의 의존성은 생략해버리는 것이 유용합니다.

 

제 프로젝트는 아래와 같은 포함관계가 있습니다.

Budget > Calendar > Day Component

Budget 컴포넌트의 테스트가 비대해지는 중이죠.

그래서 Calendar 컴포넌트를 Mock을 했습니다.

의존성도 덜어냈구요.

그 방법은 아래와 같습니다.

 

1) Mock 컴포넌트의 선언

 

테스트(spec.js)에 가짜(Mock) 컴포넌트를 선언합니다. 실제와 동일한 selector를 가지도록 말이지요. Input, Output과 같이 테스트가 필요한 부분은 포함합니다.

...
@Component({
  selector: 'app-calendar',
  template: 'mock calendar'
})
class MockCalendarComponent {
  @Input('date') date: Date;
  @Output('dateChange') dateChange: EventEmitter = new EventEmitter();
}

 

2) Mock 컴포넌트의 사용

 

하위 컴포넌트를 추가해주었던 declarations에 선언해준 Mock 컴포넌트를 넣습니다.

describe('BudgetComponent', () => {
  let component: BudgetComponent;
  let fixture: ComponentFixture;
  let testableDailyExpenseState: Observable;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [BudgetComponent, MockCalendarComponent],
      imports: [
      ...
      ])]
    }).compileComponents();
  }));
  ...

 

이제 Calendar나 Day 컴포넌트의 의존성은 Budget 컴포넌트의 테스트에 더이상 필요가 없습니다.

자식 컴포넌트를 위한 설정 코드들도 덜어집니다.

 

2. Override Private Property

 

사실 이 방법은 좋은 방법은 아닙니다.

부득이하게 쓸 경우가 있어서 정리해둡니다.

NGXS를 쓰다보니 테스트가 난해하더군요.

Store 값을 변경하기가 까다로워서요. 

 

Selector로 가져오는 값이 private이기 때문.

Javascript의 Object.defineProperty를 이용했습니다. 이미 component에 정의된 속성을 수정하고 테스트용 값을 넣어주는 방식입니다.

beforeEach(() => {
    ...
    Object.defineProperty(component, 'dailyExpense$', { writable: true });
    let dailyExpenseState = {
      //test data
      ...
    }
    testableDailyExpenseState = of(dailyExpenseState)
    component.dailyExpense$ = testableDailyExpenseState;
    ...
  });

위와 같이 component의 private 속성에

원하는 테스트 값을 넣는 것이 가능합니다.

 

위와 같은 방법을 이용하여 Angular의 테스트를 조금 더 수월하게 하였습니다.

 

Angular 테스트 코드가 점점 무거워지고,

수많은 의존성과 설정에 불편하신 분들께

도움이 되었으면 좋겠습니다.

 

전체 소스는 아래 github에서 보실 수 있습니다.

소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Dialog(Material Design)  (0) 2019.01.01
Angular Component Communication (Output)  (0) 2018.12.16
Angular Component Communication (Input)  (0) 2018.12.02
Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12

TechTrip IT Tech/Angular

Angular Router (NGXS)

2018. 11. 5. 21:19

Angular의 화면 이동에 대한 부분입니다.

지난 글에서 RouterModule을 다뤄봤는데요.

(Angular Router)


브라우저에 주소를 입력하여 이동했었죠?

오늘은 NGXS의 플러그 인을 사용하겠습니다.


사이트의 이동경로는 중요한 상태중 하나입니다.

그것을 스토어에서 관리하면 장점이 있지요.

사용자가 접속한 주소를 컴포넌트가 알 수 있구요.

어느 경로를 통해 왔는지도 파악 가능합니다.

물론 사이트 내의 이동경로 뿐이지만요.


그럼 코드를 통해 살펴보겠습니다.


1. Import NgxsRouterPluginModule


플러그인을 사용하기 위해 모듈을 Import 합니다.

NgxsRouterPluginModule 입니다.

그리고 @NgModule 의 Imports에 추가합니다.

RouterModule 설정은 그대로 둡니다.

...
import { NgxsRouterPluginModule } from '@ngxs/router-plugin'

const appRoutes : Routes = [
  { path: '', component: BudgetComponent },
  { path: 'config', component: SettingComponent }
]

@NgModule({
  ...
  imports: [
    ...
    RouterModule.forRoot(appRoutes),
    NgxsRouterPluginModule.forRoot()
  ],
  ...
})
export class AppModule { }


2. Component에 Navigate 메소드 추가


이제 경로 이동을 추가할 컴포넌트로 갑니다.

저는 Header에 경로 이동 코드를 넣겠습니다.

Store와 Navigate 모듈을 Import 하구요.

Store에 Navigate 객체를 dispatch 합니다.

마치 action 동작을 수행하는 것처럼요.

(Angular State Management (NGXS) Code)

...
import { Store } from '@ngxs/store';
import { Navigate } from '@ngxs/router-plugin';

@Component({
  ...
})
export class HeaderComponent implements OnInit {
  constructor(private store: Store) { }
  moveTo(path){
    this.store.dispatch(new Navigate([path]))
  }
}

참! 생성자에 Store 의존성을 주입하구요.

Service를 사용할 때와 동일합니다.

(Angular Service 만들기)

Angular의 강점인 의존성 주입이죠?


3. Template에 method 바인딩


로고 아이콘, 타이틀을 클릭하면 루트로

설정 아이콘을 클릭하면 설정으로 보냅니다.

아래와 같이 click에 바인딩을 해주었습니다.

<mat-toolbar color="primary">
  <mat-icon (click)="moveTo('/')">account_balance</mat-icon>
  <span class="application-title" 
      (click)="moveTo('/')">Daily Account Log</span>
  <span class="fill-remaining-space"></span>
  <mat-icon 
      (click)="moveTo('/config')">settings</mat-icon>
</mat-toolbar>

그러면 로고를 클릭할 때 moveTo가 호출되구요.

path에 따라 화면을 이동합니다.


route 이동 동작화면(ngxs)


동작화면을 보면 깜빡임도 없죠?

<router-outlet> 부분만 다시 rendering 됩니다.

필요한 부분(Component)만 바꿔치기 되는 거죠.


소스주소: https://github.com/jsrho1023/account-book


정보출처: 

https://angular.io/guide/router

https://ngxs.gitbook.io/ngxs/plugins/router

'IT Tech > Angular' 카테고리의 다른 글

Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12
Angular Router  (0) 2018.10.31
Angular Forms (Validation)  (0) 2018.10.14
Angular Forms (Simple)  (0) 2018.10.07

TechTrip IT Tech/Angular

Angular State Management (NGXS) Code

2018. 9. 22. 23:04

오늘은 지난 글에서 소개했던 ngxs의 코드를 살펴보겠습니다.


아래와 같은 순서로 만들어 가게 되는데요.


1. Action 추가 (소비 내역 추가)

2. State 추가 (하루 소비 내역)

3. Select 추가 (component에서 하루 소비 내역 조회)


1. Action 추가


action은 데이터를 변경하는 동작을 말합니다.

소비 내역을 추가하는 action을 정의하였습니다.

(저는 가계부를 만드는 개인 프로젝트 진행중입니다.)

import { Consumption } from "../domain/consumption";

export class AddConsumption {
    static readonly type = '[Budget Page] Add Consumption';
    constructor(public consumption: Consumption) {}
}

지난 번 생성한 Consumption 도메인을 활용합니다.


type에는 어떠한 action인지 설명을 적어줍니다.

ngxs 공식 페이지에서는 아래와 같은 규칙을 권장합니다.

- 명령이 수행되는 맥락 (ex: [User API],[Product Page])

- 행위를 설명하는 동사 (ex: Get, Add, Delete)

- 행위의 대상 Entity (ex: User, Product)


constructor는 추가할 consumption을 인자로 받습니다.


즉, 설명과 Action이 수행될 metadata를 정의해주는 것입니다.


2. State 추가


State의 이름과 기본값, action의 실제 동작을 정의합니다.

ngxs 코드의 가장 핵심 부분이라고 볼 수 있습니다.


State는 @State라는 annotation을 사용하여 정의합니다.

import { Action, State, StateContext } from '@ngxs/store';
import { DailyExpense } from '../domain/dailyExpense';
import { AddConsumption } from './budget.actions';

@State({
    name: 'dailyExpense',
    defaults: {
        datetime: new Date(),
        consumptions: [],
    }
})
export class DailyExpenseState {
    @Action(AddConsumption)
    addConsumption(context: StateContext, action:AddConsumption){
        const state = context.getState();
        context.setState({
            ...state,
            consumptions: [
                ...state.consumptions,
                // this is the new consumption instance that we add
                action.consumption,
            ],
        });
    }
}

name은 Store에서 State를 구분하는 역할을 합니다.

따라서 유일(Unique)한 이름을 부여해야만 합니다.


기본값은 날짜와 소비내역 배열로 하였습니다.


State class에는 action과 dependency를 정의합니다.

저는 아직 dependency는 정의하지 않았구요.

action만 정의하였습니다.


Action 추가에서 action의 class를 만들었죠.

실제 수행되는 코드는 State안에 있습니다.

@Action annotation을 사용합니다.

context와 action을 인자로 받는 method를 만듭니다.

context에는 ngxs Store에 저장된 State가 담겨있습니다.

그것을 가져와서 action을 수행합니다.


3. Select 활용


이제 실제로 Component에서 State를 활용해보겠습니다.

먼저 app.module.ts에 import를 해주어야합니다.

...
import { NgxsModule } from '@ngxs/store';
import { DailyExpenseState } from './budget/budget.state';

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
    NgxsModule.forRoot([
      DailyExpenseState
    ])
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

ngxs 모듈에 정의한 State를 넣어주었습니다.


다음은 State를 사용할 Component의 코드를 보겠습니다.

...
import { Store, Select } from "@ngxs/store";
import { Observable } from 'rxjs';

@Component({
    ...
})
export class BudgetComponent implements OnInit {
    ...
    // ngxs select
    @Select(state => state.dailyExpense) dailyExpense$: Observable<DailyExpense>;

    // ngxs store dependency injection
    constructor(private store: Store) { }

    ngOnInit() {
        this.balance = this.budget;
        this.date = new FormControl(new Date());

        this.dailyExpense$.subscribe((dailyExpense) => {
            // using state data
            ...
        })
    }

    // using ngxs action 
    addConsumption(consumption: Consumption) {
        this.store
            .dispatch(new AddConsumption(consumption));;
    }
}

State를 조회하기 위해 @Select annotation을 사용합니다.

State중 가져올 값을 lambda 형태로 정의해주면 됩니다.

그리고 저장할 변수값을 정하죠. 

보통 $를 붙여서 state임을 표시합니다. 


타입을 보면 아시겠지만 state는 rxjs의 Observable 입니다.

따라서 subscribe하면 값이 화면에 동적으로 반영되죠.


state를 변경할 때는 설명드린바와 같이 action을 사용합니다.

action을 사용할 때에는 생성자에 주입한 Store를 이용합니다.

Store에 action 객체를 생성하여 dispatch하는 형태입니다.


복잡해보이지만 천천히 살펴보시면 쉽게 이해가 되실 겁니다.

지난 포스팅의 ngxs의 개념이 도움이 되실거에요.

(http://dschci.tistory.com/111)


아래는 ngxs를 기반으로 동작하는 화면입니다.


ngxs를 활용한 화면 예제


다음에는 동작화면의 Material Form에 대해 알아볼게요.

진행하는 작업은 github에서 보실 수 있습니다.


소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Forms (Validation)  (0) 2018.10.14
Angular Forms (Simple)  (0) 2018.10.07
Angular State Management (NGXS)  (0) 2018.09.15
Npm 거슬리는 pacakge-lock.json?  (2) 2018.09.02
Angular 업데이트(Update from 5 to 6)  (0) 2018.05.19

TechTrip IT Tech/Angular

Angular State Management (NGXS)

2018. 9. 15. 22:57

지난 번에는 Service를 만들어 비즈니스 로직을 분리해봤습니다.


원래 Service에 RxJS를 더해 직접 구현하려다가

(Angular 기본접근법은 그렇습니다) 

조금 변경하여 State Management 라이브러리를 도입하기로 하였습니다.

Application 복잡도는 낮지만 배움과 재미를 위한 개발이라서요.


라이브러리는 많은 선배 개발자들의 고민을 바탕으로 하고 있습니다.

그렇게 만들어진 좋은 구조를 익혀가는 것도 의미가 있다고 생각되었어요.


Angular의 State Management를 생각하면 먼저 떠오르는 것은 ngrx입니다.

하지만 저는 ngxs를 써보려고 합니다.


그 이유는 ngxs가 더 Angular 답기 때문입니다.


ngrx의 출발은 Angular가 아닌 React입니다.

React는 Redux라는 State Management 라이브러리를 가지고 있습니다. 

 ※ React는 Facebook에서 만든 Front-End Library

Redux를 Angular로 가져온 것이 바로 ngrx 입니다.

잘 사용하려면 먼저 Redux를 이해할 필요가 있습니다.

Redux를 이해하려면 React의 라이프사이클에 익숙해야하죠.

즉, Learning Curve가 높을 수 있습니다.


그에 반해 ngxs는 ngrx보다는 새로운 angular 전용 라이브러리입니다.


오늘은 ngxs의 간단한 소개를 할까 합니다.

ngxs는 4가지 중요한 개념이 있습니다.

(다른 State Management 라이브러리도 비슷한 개념을 가집니다.)


먼저 doc site의 설명을 그대로 가져오면 아래와 같습니다.

State: state를 정의하는 class

Action: 수행하는 action과 metadata에 대한 정보가 담긴 class 

Selects: Store에서 state의 일부를 가져오는 function

Store: state의 저장소이자, action, select를 관리하는 전역 객체


네 이해 안되실거에요. 저도 그렇습니다.


제가 작업하면서 개념적으로 이해한 것은 아래와 같습니다.

State: Angular Component가 화면에 보여줘야하는 데이터

Action: 서버와 통신(비동기)하고 데이터를 변경하는 역할

Selects: 데이터를 Angular Component에게 가져오는 역할

Store: State를 저장, action과 select를 관리/참조하는 중심


저는 머리 속에 아래 그림처럼 정리하였습니다.

사실 많은 부분을 생략하여 그린 그림입니다.

이해를 돕는 차원에서 봐주시면 될 것 같아요.


ngxs 중요 개념 (많은 부분 생략)


그래서 ngxs 라이브러리를 사용하면 좋은 점은 무엇일까요?

스스로 개발해야했던 부분이 이미 상당부분 구현되어있습니다.

아마도 제가 스스로 만드는 것보다 훨씬 잘 만들어져있겠죠.


서버와의 통신 부분이 구조화/모듈화될 수 있습니다.

코드 관리가 더 수월해지는 장점이 있습니다.


다음 포스팅에서는 실제 코드로 작성된 부분을 공유해볼게요.


정보출처:

ngxs document site - https://ngxs.gitbook.io/ngxs


'IT Tech > Angular' 카테고리의 다른 글

Angular Forms (Simple)  (0) 2018.10.07
Angular State Management (NGXS) Code  (0) 2018.09.22
Npm 거슬리는 pacakge-lock.json?  (2) 2018.09.02
Angular 업데이트(Update from 5 to 6)  (0) 2018.05.19
Angular Service 만들기  (2) 2018.05.13

TechTrip IT Tech/Angular