NgRx の ComponentStore を試したのでメモ https://ngrx.io/guide/component-store
サンプルはこちら https://stackblitz.com/edit/angular-ngrx-component-store
@ngrx/component-store の特徴
- @ngrx/store と違い component などの local な状態管理するのに役立つ
- component に紐付けられるから component が破棄されるとクリーンアップされる
インストール
- いつもどおり
$ yarn add @ngrx/component-store
サンプル
- 簡単なカウンターアプリを作る
Store の用意
- constructor で渡した値が initial state になる
- 値を渡さないと ComponentStore が初期化されない(後述の
setState()を使って初期化することもできる)
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
export interface CounterState {
count: number;
}
@Injectable()
export class CounterStore extends ComponentStore<CounterState> {
constructor() {
super({ count: 0 });
}
}
Read state
select()という名前の selector が提供されているのでそれを使うと簡単に read できるselect()は state を map して返すので引数に projector を指定することで好きな形で取得が可能
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
+ import { Observable } from 'rxjs';
export interface CounterState {
count: number;
}
@Injectable()
export class CounterStore extends ComponentStore<CounterState> {
constructor() {
super({ count: 0 });
}
+ readonly count$: Observable<number> = this.select((state) => state.count);
}
- この store を使用する component 側は以下のように書くだけでよい
import { Component } from '@angular/core';
import { CounterStore } from './counter.store';
@Component({
selector: 'my-app',
template: `
<div>This count is {{ count$ | async }}</div>
`,
styleUrls: ['./app.component.css'],
providers: [CounterStore],
})
export class AppComponent {
constructor(private readonly counterStore: CounterStore) {}
readonly count$ = this.counterStore.count$;
}
- Injectable な class は Injector の範囲内でシングルトンなので component ごとに providers に登録すると component ごとの状態が持てる
Update state
- state を更新するには
updater()またはsetState()を使う必要がある
updater
updater()は現在の state を引数に更新された state を返す
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { Observable } from 'rxjs';
export interface CounterState {
count: number;
}
@Injectable()
export class CounterStore extends ComponentStore<CounterState> {
constructor() {
super({ count: 0 });
}
readonly count$: Observable<number> = this.select((state) => state.count);
+ readonly add = this.updater((state) => ({ count: state.count + 1 }));
}
import { Component } from '@angular/core';
import { CounterStore } from './counter.store';
@Component({
selector: 'my-app',
template: `
<div>This count is {{ count$ | async }}</div>
+ <button (click)="onClickAddButton()">+1</button>
`,
styleUrls: ['./app.component.css'],
providers: [CounterStore],
})
export class AppComponent {
constructor(private readonly counterStore: CounterStore) {}
readonly count$ = this.counterStore.count$;
+ onClickAddButton() {
+ this.counterStore.add();
+ }
}
setState
setState()は引数に projector または state を渡すことができるprojector(関数) が渡された場合は、内部で
updater()が呼び出されるstate が渡された場合は ComponentStore の constructor と同様に initState が呼び出される
setState()に置き換える場合以下のようになる
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { Observable } from 'rxjs';
export interface CounterState {
count: number;
}
@Injectable()
export class CounterStore extends ComponentStore<CounterState> {
constructor() {
- super({ count: 0 });
+ super();
+ this.setState({ count: 0 });
}
readonly count$: Observable<number> = this.select((state) => state.count);
- readonly add = this.updater((state) => ({ count: state.count + 1 }));
+ readonly add = () => this.setState((state) => ({ count: state.count + 1 }));
}
所感
component に閉じたローカルな状態管理は ComponentStore にまかせてもよさそうだと感じた。