Angular + nxrxにRedux Toolkitを追加してみた覚書

意図

Action / Reducerなどの作成をまとめてできるcreateSlice関数が使いたかった。

また、手慣れた書き方ができるならそっちのほうが良さそうに感じたこともあります。

インストール

ライブラリをそれぞれ追加します。

$ npm i @ngrx/store @reduxjs/toolkit

app.module.tsにngrxのStoreModuleを登録する

まずはrootで登録します。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, StoreModule.forRoot({}, {})],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

createSliceでActionやReducerを作る

ActionやReducerなどはRedux Toolkitで作ります。

Selectorだけngrxの関数が入ります。

import { createSlice, CaseReducer } from '@reduxjs/toolkit'
import { createFeatureSelector } from '@ngrx/store'

export type CounterState = {
    count: number
}
const counterSlice = createSlice<CounterState, {
    increment: CaseReducer<CounterState, {
        type: string
    }>
}>({
    name: 'counter',
    initialState: {
        count: 0
    },
    reducers: {
        increment: state => {
            state.count++
        }
    }
})

export const {
    reducer: counterReducer,
    actions: {
        increment
    },
    name: counterFeatureKey
} = counterSlice
export const selectFeature = createFeatureSelector<ReturnType<typeof counterReducer>>(counterFeatureKey)

使いたいページのModuleでStoreを登録する

rootに登録しても良さそうなのですが、forFeatureを使えば使いたい部分のModuleにだけ登録できるみたいです。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { StoreModule } from '@ngrx/store';
import { DemoRoutingModule } from './demo-routing.module';
import { HomeComponent } from './home/home.component';

import { counterFeatureKey, counterReducer } from '../stores/counter';
import { anotherFeatureKey, anotherReducer } from '../stores/another';


@NgModule({
  declarations: [
    HomeComponent
  ],
  imports: [
    CommonModule,
    DemoRoutingModule,
    StoreModule.forFeature(counterFeatureKey, counterReducer),
    // 複数の場合
    StoreModule.forFeature(anotherFeatureKey, anotherReducer)
  ]
})
export class DemoModule { }

Dispatch / Subscribeする

呼び出す時はstore.dispatch、データのsubscribeがしたい場合はstore.selectを使います。

createSelectorの処理自体も事前定義しておいて良い気はしますが、未検証です。

import { Component, OnInit } from '@angular/core';
import { Store, createSelector } from '@ngrx/store'
import { selectFeature } from 'src/app/stores/counter/selector';
import { increment } from 'src/app/stores/counter/slice';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor(private readonly store: Store) { }

  public counter$ = this.store.select(createSelector(selectFeature, state => state.count))
  public increment() {
    this.store.dispatch(increment())
  }
}

非同期処理について

createAsyncThunkやRTK Queryをそのまま使うのは、パッと調べた範囲では難しそうです。

@ngrx/effectを使うか、非同期処理の結果だけStoreにいれるかなどを検討した方が、今の自分のレベルにはあってそうです。

参考

https://blog.lacolaco.net/2020/12/angular-using-ngrx-with-redux-toolkit/

Comment