[NGXS] Action Handler, Action Life Cycle

fepanbr·2021년 7월 28일
0

Angular

목록 보기
6/7

Action Life Cycle

NGXS의 모든 Action은 4가지 상태로 존재한다.

DISPATCHED, ERRORED, CANCLEED, SUCCESSFUL

Action들은 내부스트림으로 ActionContext라는 객체를 방출하는데,

해당 객체는 아래와 같은 구조로 되어 있다.

{
  action: GetNovelsInstance,
  status: 'DISPATCHED'
}

위의 액션이 성공한다면, ActionContext는 아래와 같은 객체를 방출한다.

{
  action: GetNovelsInstance,
  status: 'SUCCESSFUL'
}

만약 어떤 액션이 에러를 Throw한다면,

@Action(GetNovels)
getNovels() {
  throw new Error('This is just a simple error!');
}

ActionContext는 아래와 같은 형태를 만든다.

{
  action: GetNovelsInstance,
  status: 'ERRORED'
}

비동기의 경우, 어떤 액션이 끝나기전에 새로운 액션이 발생한다면 ActionContext CANCELED 상태가 된다.

또한 아래와같이 cacelUnCompleted 속성을 주게 되면, switchMap처럼 작동하게 된다.

export class NovelsState {
  constructor(private novelsService: NovelsService) {}

  @Action(GetNovels, { cancelUncompleted: true })
  getNovels(ctx: StateContext<Novel[]>) {
    return this.novelsService.getNovels().pipe(
      tap(novels => {
        ctx.setState(novels);
      })
    );
  }
}

비동기 action

export interface BooksStateModel {
  novels: Book[];
  detectives: Book[];
}

export class GetNovels {
  static type = '[Books] Get novels';
}

export class GetDetectives {
  static type = '[Books] Get detectives';
}

@State<BooksStateModel>({
  name: 'books',
  defaults: {
    novels: [],
    detectives: []
  }
})
@Injectable()
export class BooksState {
  constructor(private booksService: BooksService) {}

  @Action(GetNovels)
  getNovels(ctx: StateContext<BooksStateModel>) {
    return this.booksService.getNovels().pipe(
      tap(novels => {
        ctx.patchState({ novels });
      })
    );
  }

  @Action(GetDetectives)
  getDetectives(ctx: StateContext<BooksStateModel>) {
    return this.booksService.getDetectives().pipe(
      tap(detectives => {
        ctx.patchState({ detectives });
      })
    );
  }
}
store
  .dispatch(new GetNovels())
  .subscribe(() => {
    ...
  });

store
  .dispatch(new GetDetectives())
  .subscribe(() => {
    ...
  });

우리는 GetNovels() → GetDetectives() 순으로 완료 되길 원한다.

하지만 HTTP reponse는 언제 응답이 올지 모르기 때문에 나중에 소설이 set될수도 있다.

이에 대한 대안으로

store
  .dispatch([
    new GetNovels(),
    new GetDetectives()
  ])
  .subscribe(() => {
    ...
  });

배열로 dispatch한다면 두 액션이 완료 됬을 때만 subscribe를 진행하게 된다.

Error life cycle

위의 예제에서 에러는 observable의 onError 콜백으로 받을 수 있다.

store
  .dispatch([
    new GetNovelById(id), // action handler throws `new Error(...)`
    new GetDetectiveById(id)
  ])
  .subscribe(
    () => {
      // they will never see me
    },
    error => {
      console.log(error); // `Error` that was thrown by the `getNovelById` handler
    }
  );

Asynchronous Actions continued - "Fire and forget" vs "Fire and wait"

NGXS에서 @Action의 return 을 한다면 observable을 subscribe해주고, 해당 액션의 완료를 바인딩 해줄것이다.

만약, 너가 비동기 완료를 완료될때까지 기다리지 않고 싶다면, 리턴하지 마라.

그리고 observable의 경우는 subscribe나 toPromise()를 사용해라.

또다른 "fire and forget" 접근법의 사용처는 새로운 액션을 dispatch할 때이다.

Child Action을 기다릴 필요 없을 때, 아래와 같이 dispatch한다.

export class BooksState {
  constructor(private booksService: BooksService) {}

  @Action(GetNovels)
  getNovels(ctx: StateContext<BooksStateModel>) {
    return this.booksService.getNovels().pipe(
      tap(novels => {
        ctx.patchState({ novels });
        ctx.dispatch(new GetDetectives());
      })
    );
  }

  @Action(GetDetectives)
  getDetectives(ctx: StateContext<BooksStateModel>) {
    return this.booksService.getDetectives().pipe(
      tap(detectives => {
        ctx.patchState({ detectives });
      })
    );
  }
}

반대로 기다리고 싶다면 mergeMap을 이용하라(swtichMap, concatMap, 등등) 그리고 return하라

@Action(GetNovels)
getNovels(ctx: StateContext<BooksStateModel>) {
  return this.booksService.getNovels().pipe(
    tap(novels => {
      ctx.patchState({ novels });
    }),
    mergeMap(() => ctx.dispatch(new GetDetectives()))
  );
}
profile
나무아래에 앉아, 코딩하는 개발자가 되고 싶은 박철현 블로그입니다.

0개의 댓글