水無瀬のプログラミング日記

プログラムの勉強した時のメモ

@ngrx/effectsを使ってみる

はじめに

@ngrx/store についてまとめたから、今回は@ngrx/effectsについてまとめる。
インストールしてサクッと動かしてみるとこまで。

TL;DR.

ソースコード

@ngrx/effectsとは

APIアクセスなどの副作用のある処理をラップしてくれるもの。

前提条件

(なくてもいいけど、)前回の状態

インストール

$ npm install --save @ngrx/effects

Service作成

ここは普通のAngularと同じく、API叩くServiceを作成する。
ただ、今回は 作るのがめんどくさかったから サクッと作るためObservable生成して流すことにする。

    @Injectable({
        providedIn: 'root'
    })
    export class TodoService {
    
        constructor(private http: HttpClient) {
        }
    
        public fetchAll(): Observable<Array<string>> {
            return Observable.create(observable => {
                observable.next(['task1', 'task2', 'task3']);
                observable.complete();
                return observable;
            });
        }
    }

Action作成

サービスを取得するためのActionを作成する。
前回の状態があるなら、todo.action.tsに追記でOK。

    import { Action } from '@ngrx/store';
    
    export enum ActionTypes {
        Fetch = 'TODO_FETCH',
            Load = 'TODO_LOAD'
    }
    
    export class Fetch implements Action {
        readonly type = ActionTypes.Fetch;
    } 

Reducer作成

取得結果を処理する。
結果を受け取ってリストをまるっと入れ替えてあげる。
Actionと同じく todo.reducer.tsに記載。

    import { Action } from '@ngrx/store';
    import { ActionTypes } from '../action/todo.action';
    
    export const initialList: Array<string> = [];
    
    export function todoReducer(state = initialList, action: Action): Array<string> {
        switch (action.type) {
            case ActionTypes.Fetch:
                return [...action['payload']];
            default:
                return state;
        }
    }

Effects作成

serviceをラップして作ってあげる様に作ってあげる。
名前はtodo.effects.ts とする。

    import {Injectable} from '@angular/core';
    import {Actions, Effect, ofType} from '@ngrx/effects';
    import {EMPTY} from 'rxjs';
    import {catchError, map, mergeMap} from 'rxjs/operators';
    import {TodoService} from '../service/todo.service';
    import {ActionTypes, Fetch} from '../action/todo.action';
    
    @Injectable()
    export class TodoEffects {
    
        @Effect()
        loadTodo$ = this.actions$
            .pipe(
                // このイベントが発火するためのAction
                ofType(ActionTypes.Load),
                // サービス呼び出す
                mergeMap(() => this.todoService.fetchAll()
                    .pipe(
                        map(serverTodo => {
                   // serviceの結果を元にAction発火
                            return new Fetch({todos: serverTodo});
                        }),
                        catchError(() => EMPTY)
                    ))
            );
    
        constructor(
            private actions$: Actions,
            private todoService: TodoService
        ) {
        }
    }

moduleに追記

effectsを使うためにmoduleに追記する。

    @NgModule({
        declarations: [
            AppComponent
        ],
        imports: [
            // 配列形式でEffects定義。
            EffectsModule.forRoot([TodoEffects])
        ],
        providers: [TodoService],
        bootstrap: [AppComponent]
    })
    export class AppModule {
    }

Componentに定義

componentから呼び出すための設定。
今回は初期表示時に設定した内容がほしいので、OnInit → effectsのイベント発火 → 結果をsubscribe。
という流れで設定。

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      public todo$: Observable<Array<string>>;
      public todolist: Array<string> = [];
    
      constructor(
          private store: Store<{ todo: Array<string>}>,
          private service: TodoService
      ) {
        this.todo$ = store.pipe(select('todo'));
        this.todo$.subscribe(res => this.todolist = res);
      }
    
      ngOnInit(): void {
        // 初期表示用にイベント発火
        this.store.dispatch({ type: ActionTypes.Load });
      }
    }

動かしてみる

ng serve で起動。
初期表示がされてればOK!

まとめ

今回はサクッとeffectsを使ってみた。
ドキュメントのコードを丸コピしてたんだけど、
effectsからReducerに通るところでハマってた。
ちゃんと文章読むのは大事だねというところで、今回はこのへんで。

参考資料