NGXSのドキュメントを読んでて「へぇ〜」と思ったtips


以前、会社のテックブログにNGXSに関する記事を書いたことがあり、

Angularの状態管理ライブラリ 「NGXS」に入門する | by Kana Otawara | nextbeat-engineering | Medium

その際に割と真剣にドキュメントを読んだのですが、それから1年以上経過してまた読んでみました。
いろいろと更新されていたり、1年ほど実務で開発経験を積んだ状態で読んだこともあり、これを知ってると役立ちそうだなと思ったこともあったのでまとめてみます。

※本記事では NGXSドキュメント Concepts - NGXS 配下のページ を対象とします。(2020/07/27時点の内容)

Store

リンク:https://www.ngxs.io/concepts/store

同時に複数のActionをdispatchできる!

this.store.dispatch([new AddAnimal('Panda'), new AddAnimal('Zebra')]);

これは完全に初知りでした。

Actions

リンク:https://www.ngxs.io/concepts/actions

Actionの名前付け方法のおすすめが載っている

https://www.ngxs.io/concepts/actions#how-should-you-name-your-actions
こちらのセクションです。
動詞+目的語、みたいな付け方はすでにしていますが、それにプラスしてコンテキストを書いておくといいよ〜みたいなことを書いてあります。なるほどなぁ

Action定義のグループ化ができる!

TodoAction, TodoEdit・・・みたいに定義しなくてもスッキリと。

export namespace Todo {
  export class Add {
    static readonly type = '[Todo] Add';
    constructor(public payload: any) {}
  }

  export class Edit {
    static readonly type = '[Todo] Edit';
    constructor(public payload: any) {}
  }

  export class FetchAll {
    static readonly type = '[Todo] Fetch All';
  }

  export class Delete {
    static readonly type = '[Todo] Delete';
    constructor(public id: number) {}
  }
}

State

リンク:https://www.ngxs.io/concepts/state

サブstateを指定できる!

Stateのメタデータ定義の際に、childrenという任意の属性の中に他のStateの配列を指定すると、サブStateにすることができます。

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

export interface SampleStateModel {
  hoge: string;
}

@State<SampleStateModel>({
  hoge: 'test',
  defaults: {
    hoge: null
  },
  children: [AnotherState]
})
@Injectable()
export class SampleState {}

これは「Advanced」の方で取り上げられています。(詳しく読んでみたいものです。)
https://www.ngxs.io/advanced/sub-states

複数のActionをlistenできる!

これは実は私個人は知ってたんですが、意外と知らない方もいるのではないかと勝手に思っています。
dispatchのときと同様に、複数のActionをlistenできます。

  @Action([FirstAction, SecondAction])
  doSomething(ctx: StateContext<SampleStateModel>) {
    ...
  }

Select

リンク:https://www.ngxs.io/concepts/select

Selectの定義の仕方はかなりいろいろある

そもそもtipsでもなんでもないのですが、Selectはかなりいろいろな方法で定義や生成ができるので、このページを一度読んでみるといろいろな発見があります。

selectSnapshotによりObservableに包まれない状態でその瞬間の値を得ることができる!

これも実は私個人は知ってたんですが・・・Observableを使えない場面で便利です。(例えばその瞬間は絶対に値が何かしら設定されていることが保証されていて、Observableに包まれない状態で取得して扱いたい場合などです。)

const hogeValue: string = this.store.selectSnapshot<string>((state: SampleState) => state.hoge);

Selectorの継承

これも似たようなコードが大量発生する場合に実務上で役に立ちそうだと感じました。
実際に実務で、

APIでサーバから何かしらの値の配列を取ってきて、Stateにvaluesという属性に設定し、状態管理させる

みたいなことをたくさんのStateでやっているケースがあるのですが、以下のように親Stateクラスを定義して継承することでスッキリとしそうです。

親Stateクラス
export class ParentState {
  static values<T>() {
    // Selectorを動的に生成する
    return createSelector([this], (state: { values: T[] }) => {
      return state.values;
    });
  }

  //...
}

以下のように継承すると、FirstState.valuesSecondState.valuesというSelectorが外部から使えるようになります。

子クラスから継承
// 子State 1
export interface FirstStateModel {
  values: First[];
}

@State<FirstStateModel>({
  name: 'first',
  defaults: {
    values: []
  }
})
@Injectable()
export class FirstState extends ParentState {
  //...
}

// 子State 2
export interface SecondStateModel {
  values: Second[];
}

@State<SecondStateModel>({
  name: 'second',
  defaults: {
    values: []
  }
})
@Injectable()
export class SecondState extends ParentState {
  //...
}

実際にコンポーネント等から呼ぶときは、以下のような感じです。

  @Select(FirstState.values<First>())
  firstValues$: Observable<First[]>;

  @Select(SecondState.values<Second>())
  secondValues$: Observable<Second[]>;

TODO

ドキュメント読んでいると他にもいろいろ興味深いことがあるので、深堀りしたいところです。
特に非同期のActionsについてや、いろんなSelectorなどなど・・・

参考

NGXS公式ドキュメント https://www.ngxs.io/