[NGXS] State

CheolHyeon Park·2021년 7월 14일
0

Angular

목록 보기
4/7

State

State를 정의한 클래스

State 정의하기


@State<string[]>({
	name: 'animal',
	defaults: [],
})
@Injectable()
class AnimalState {}

state 데코레이터로 정의하고, Injectable 데코레이터로 의존성 주입을 한다.

  • name : 전체 어플리케이션에서 해당 이름은 유니크 해야한다. (식별자)
  • defaults : 이 상태의 기본 값(object, array)를 설정한다.
  • children : 하위 state를 나타낸다.

state의 name을 토큰으로 고유하게 이용가능하다. (StateToken)

Action 정의하기

**@Action 을 이용하여, 액션을 듣는다.** 데코레이터에는 한개의 액션 혹은 액션 array가 들어간다.

간단한 액션구현

import { Injectable } from '@angular/core';
import { State, Action, StateContext } from '@ngxs/store';

export class FeedAnimals {
	static readonly type = '[Zoo] FeedAnimals';
}

export interface ZooStateModel {
	feed: boolean;
}

@state<ZooStateModel>({
	name: 'zoo',
	defaults: {
		feed: false
	}
})
@Injectable()
export class ZooState {
	@Action(FeedAnimals)
	feedAnimals(ctx: StateContext<ZooStateModel>) {
		const state = ctx.getstate();
		ctx.setState({
			...state,
			feed: !state.feed
		});
	}
}

feedAnimals 함수는 FeedAnimals 액션에 반응하여 실행 된다. 해당 함수의 첫번째 인자는 StateContext타입의 인자가 들어오고, 그 인자를 이용하여 state를 바꾼다.

getState() 는 접근할 때마다, global store의 최신 state를 리턴한다.

payload가 포함된 action

import { Injectable } from '@angular/core';
import { State, Action, StateContext } from '@ngxs/store';

// This is an interface that is part of your domain model
export interface ZebraFood {
  name: string;
  hay: number;
  carrots: number;
}

// naming your action metadata explicitly makes it easier to understand what the action
// is for and makes debugging easier.
export class FeedZebra {
  static readonly type = '[Zoo] FeedZebra';
  constructor(public zebraToFeed: ZebraFood) {}
}

export interface ZooStateModel {
  zebraFood: ZebraFood[];
}

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    zebraFood: []
  }
})
@Injectable()
export class ZooState {
  @Action(FeedZebra)
  feedZebra(ctx: StateContext<ZooStateModel>, action: FeedZebra) {
    const state = ctx.getState();
    ctx.setState({
      ...state,
      zebraFood: [
        ...state.zebraFood,
        // this is the new ZebraFood instance that we add to the state
        action.zebraToFeed
      ]
    });
  }
}

액션은 액션과 함께 해야할 metadata를 함께 전달할 수 있다. 위와 같이 feedZebra 함수의 두번째 인자로 action paayload를 넘긴다. payload에는 zebraToFeed 타입의 객체가 들어있다.

이 payload를 통해 ZooState를 갱신한다. 위 표현을 간략히 한게 patchState 이다.

@Action(FeedZebra)
feedZebra(ctx: StateContext<ZooStateModel>, action: FeedZebra) {
  const state = ctx.getState();
  ctx.patchState({
    zebraFood: [
      ...state.zebraFood,
      action.zebraToFeed,
    ]
  });
}

setState를 하는 두가지 방법

@Action(MyAction)
public addValue(ctx: StateContext, { payload }: MyAction) {
  ctx.setState({ ...ctx.getState(), value: payload  });
}
@Action(MyAction)
public addValue(ctx: StateContext, { payload }: MyAction) {
	ctx.setState((state) => ({...state, value: payload});
}

Async Actions

action을 비동기적으로 수행할 수도 있고, 동기적으로 어떤 operation 후에 수행할 수 있다.

import { Injectable } from '@angular/core';
import { State, Action, StateContext } from '@ngxs/store';
import { tap } from 'rxjs/operators';

export class FeedAnimals {
  static readonly type = '[Zoo] FeedAnimals';
  constructor(public animalsToFeed: string) {}
}

export interface ZooStateModel {
  feedAnimals: string[];
}

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    feedAnimals: []
  }
})
@Injectable()
export class ZooState {
  constructor(private animalService: AnimalService) {}

  @Action(FeedAnimals)
  feedAnimals(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
    return this.animalService.feed(action.animalsToFeed).pipe(
      tap(animalsToFeedResult => {
        const state = ctx.getState();
        ctx.setState({
          ...state,
          feedAnimals: [...state.feedAnimals, animalsToFeedResult]
        });
      })
    );
  }
}

위의 예제에서 보면, feed 함수를 실행한 후에 tap 을 통해 상태를 갱신하고 있다.

비동기를 순차적으로 처리한 경우이다.

그리고 feedAnimals 함수는 Observable을 리턴하고 있는데 이렇게 하면 자동적으로 subscribe를 하여 우리가 직접 다룰 필요가 없어지게 된다.

만약 한번의 비동기만을 처리한다면, Promise 를 사용하는 것도 가능하다.

import { Injectable } from '@angular/core';
import { State, Action } from '@ngxs/store';

export class FeedAnimals {
  static readonly type = '[Zoo] FeedAnimals';
  constructor(public animalsToFeed: string) {}
}

export interface ZooStateModel {
  feedAnimals: string[];
}

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    feedAnimals: []
  }
})
@Injectable()
export class ZooState {
  constructor(private animalService: AnimalService) {}

  @Action(FeedAnimals)
  async feedAnimals(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
    const result = await this.animalService.feed(action.animalsToFeed);
    const state = ctx.getState();
    ctx.setState({
      ...state,
      feedAnimals: [...state.feedAnimals, result]
    });
  }
}

액션을 dispatch한 후에 다른 액션을 dispatch하기

  • mergeMap과 같은 방식으로, 추가 액션을 dispatch하기
  • return값으로 dispatch하기
import { Injectable } from '@angular/core';
import { State, Action, StateContext } from '@ngxs/store';
import { map } from 'rxjs/operators';

export interface ZooStateModel {
  feedAnimals: string[];
}

@State<ZooStateModel>({
  name: 'zoo',
  defaults: {
    feedAnimals: []
  }
})
@Injectable()
export class ZooState {
  constructor(private animalService: AnimalService) {}

  /**
   * Simple Example
   */
  @Action(FeedAnimals)
  feedAnimals(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
    const state = ctx.getState();
    ctx.setState({
      ...state,
      feedAnimals: [...state.feedAnimals, action.animalsToFeed]
    });

    return ctx.dispatch(new TakeAnimalsOutside());
  }

  /**
   * Async Example
   */
  @Action(FeedAnimals)
  feedAnimals2(ctx: StateContext<ZooStateModel>, action: FeedAnimals) {
    return this.animalService.feed(action.animalsToFeed).pipe(
      tap(animalsToFeedResult => {
        const state = ctx.getState();
        ctx.patchState({
          feedAnimals: [...state.feedAnimals, animalsToFeedResult]
        });
      }),
      mergeMap(() => ctx.dispatch(new TakeAnimalsOutside()))
    );
  }
}

심플 예제에서 보듯이 feedAnimals 함수는 dispatch를 return하고 있다. 위에서 설명 했듯이, 자동적으로 dispatch를 subscribe하기에 신경 쓸 필요가 없다.

또 다른 방식은 feedAnimals2 처럼 mergMap을 통해 추가로 dispatch하는 방법이다.

ngxs 공식 문서 를 참고하여 정리하였습니다.

profile
나무아래에 앉아, 코딩하는 개발자가 되고 싶은 박철현 블로그입니다.

0개의 댓글