State를 정의한 클래스
@State<string[]>({
name: 'animal',
defaults: [],
})
@Injectable()
class AnimalState {}
state 데코레이터로 정의하고, Injectable 데코레이터로 의존성 주입을 한다.
name
: 전체 어플리케이션에서 해당 이름은 유니크 해야한다. (식별자)defaults
: 이 상태의 기본 값(object, array)를 설정한다.children
: 하위 state를 나타낸다.state의 name을 토큰으로 고유하게 이용가능하다. (StateToken)
**@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하기
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 공식 문서 를 참고하여 정리하였습니다.