第3部あなたのPOKのDEXをビルドします.


この投稿は、初心者から忍者までNGRXを使ってあなたのPOK . DEXを構築する方法について説明しています.
  • Part 1. Build your Pokédex: Introduction to NGRX
  • Part 2. Build your Pokédex: @ngrx/entity
  • Part 3. Build your Pokédex: Improve NgRX using create* functions
  • 第4部あなたのPOKのDEXをビルドします
  • 第5部ビルドあなたのPOK

  • 導入


    このポストでは、我々はAngular フレームワークNgRX 国家管理図書館として.NGRX 8でリリースされた新しいCreate *関数を使用します.
    中間レベルで角度を管理する方法を知っていて、適切にこのポストを理解するために州管理図書館が何であるかについてわかっていてください.このシリーズでは、具体的な例がどのように開発されているかを示します.
    まず、これらのポストに沿って構築される結果を以下のGIFに示す.

    を読むことが不可欠ですfirst and second このシリーズの部分は何が構築されている理解できるようにする.このポストでは、以前に開発されたコードを改善します@ngrx/entity パッケージは、アクション、縮小、効果を作成するために必要なボイラープレートコードを簡素化します.

    創造的行動


    NGRXでは、アクションを作成するときに大量のボイラープレートコードが必要です.あなたは頻繁にenums、アクションの種類、クラス、ユニオンの種類を作成する必要があります.この新しいバージョンでは、簡単な方法でアクションを作成することができます.
    NGRXコアチームは、このゴールに達するために有名な工場機能設計パターンを使用しました.ファクトリ関数はcreateactionです.CreateAction関数は2つのパラメータを受け取ります.

  • アクションタイプ.は、アクションを識別するために使用される有名な文字列です.

  • 小道具アクションメタデータです.例えばペイロード.
  • 両方を比較するために、以下のコードは、私たちのpok . dexで新しいcreateaction関数を使用する方法を示します.
    以前
    export class LoadPokemon implements Action {
      readonly type = PokemonActionTypes.LOAD_POKEMONS;
    
      constructor() {}
    }
    
    export class LoadPokemonSuccess implements Action {
      readonly type = PokemonActionTypes.LOAD_POKEMONS_SUCCESS;
    
      constructor(public payload: Array<Pokemon>) {}
    }
    
    アフター
    loadPokemonFailed = createAction(
       PokemonActionTypes.LOAD_POKEMONS_FAILED,
       props<{ message: string }>()
     ),
    
    add: createAction(PokemonActionTypes.ADD, props<{ pokemon: Pokemon }>()),
    
    前のコードでは、Action インターフェイスtype 属性とpayload コンストラクタの使用.一方、後のコードでは、createAction 関数は、最初のパラメータがtype そして2番目のパラメータはprops 属性(コンテキストではペイロード).
    コアチームはENUMの使用が必要でないと述べますが、私の特定のコーディングスタイルでは、アクションセットを意識するためにアクションenumを定義するのを好みます.
    したがって、前後pokemon.action.ts は以下の通りです.
    import { Action } from '@ngrx/store';
    import { Pokemon } from '@models/pokemon.interface';
    
    export enum PokemonActionTypes {
      ADD = '[Pokemon] Add',
      ADD_SUCCESS = '[Pokemon] Add success',
      ADD_FAILED = '[Pokemon] Add failed',
      LOAD_POKEMONS = '[Pokemon] Load pokemon',
      LOAD_POKEMONS_SUCCESS = '[Pokemon] Load pokemon success',
      LOAD_POKEMONS_FAILED = '[Pokemon] Load pokemon failed',
      UPDATE = '[Pokemon] Update',
      UPDATE_SUCCESS = '[Pokemon] Update success',
      UPDATE_FAILED = '[Pokemon] Update failed',
      DELETE = '[Pokemon] Delete',
      DELETE_SUCCESS = '[Pokemon] Delete success',
      DELETE_FAILED = '[Pokemon] Delete failed'
    }
    
    export class LoadPokemon implements Action {
      readonly type = PokemonActionTypes.LOAD_POKEMONS;
    
      constructor() {}
    }
    
    export class LoadPokemonSuccess implements Action {
      readonly type = PokemonActionTypes.LOAD_POKEMONS_SUCCESS;
    
      constructor(public payload: Array<Pokemon>) {}
    }
    export class LoadPokemonFailed implements Action {
      readonly type = PokemonActionTypes.LOAD_POKEMONS_FAILED;
    
      constructor(public message: string) {}
    }
    
    export class Add implements Action {
      readonly type = PokemonActionTypes.ADD;
    
      constructor(public pokemon: Pokemon) {}
    }
    
    export class AddSuccess implements Action {
      readonly type = PokemonActionTypes.ADD_SUCCESS;
    
      constructor(public pokemon: Pokemon) {}
    }
    export class AddFailed implements Action {
      readonly type = PokemonActionTypes.ADD_FAILED;
    
      constructor(public message: string) {}
    }
    
    export class Delete implements Action {
      readonly type = PokemonActionTypes.DELETE;
    
      constructor(public id: number) {}
    }
    export class DeleteSuccess implements Action {
      readonly type = PokemonActionTypes.DELETE_SUCCESS;
    
      constructor(public id: number) {}
    }
    export class DeleteFailed implements Action {
      readonly type = PokemonActionTypes.DELETE_FAILED;
    
      constructor(public message: string) {}
    }
    
    export class Update implements Action {
      readonly type = PokemonActionTypes.UPDATE;
    
      constructor(public pokemon: Pokemon) {}
    }
    export class UpdateSuccess implements Action {
      readonly type = PokemonActionTypes.UPDATE_SUCCESS;
    
      constructor(public pokemon: Pokemon) {}
    }
    export class UpdateFailed implements Action {
      readonly type = PokemonActionTypes.UPDATE_FAILED;
    
      constructor(public message: string) {}
    }
    
    export type PokemonActions =
      | LoadPokemonSuccess
      | Add
      | AddSuccess
      | AddFailed
      | Delete
      | DeleteSuccess
      | DeleteFailed
      | Update
      | UpdateSuccess
      | UpdateFailed;
    
    import { createAction, props } from '@ngrx/store';
    
    import { Pokemon } from '@models/pokemon.interface';
    
    export enum PokemonActionTypes {
      ADD = '[Pokemon] Add',
      ADD_SUCCESS = '[Pokemon] Add success',
      ADD_FAILED = '[Pokemon] Add failed',
      LOAD_POKEMONS = '[Pokemon] Load pokemon',
      LOAD_POKEMONS_SUCCESS = '[Pokemon] Load pokemon success',
      LOAD_POKEMONS_FAILED = '[Pokemon] Load pokemon failed',
      UPDATE = '[Pokemon] Update',
      UPDATE_SUCCESS = '[Pokemon] Update success',
      UPDATE_FAILED = '[Pokemon] Update failed',
      REMOVE = '[Pokemon] Delete',
      REMOVE_SUCCESS = '[Pokemon] Delete success',
      REMOVE_FAILED = '[Pokemon] Delete failed'
    }
    
    export const actions = {
      loadPokemon: createAction(PokemonActionTypes.LOAD_POKEMONS),
      loadPokemonSuccess: createAction(
        PokemonActionTypes.LOAD_POKEMONS_SUCCESS,
        props<{ pokemons: Pokemon[] }>()
      ),
      loadPokemonFailed: createAction(
        PokemonActionTypes.LOAD_POKEMONS_FAILED,
        props<{ message: string }>()
      ),
      add: createAction(PokemonActionTypes.ADD, props<{ pokemon: Pokemon }>()),
      addSuccess: createAction(
        PokemonActionTypes.ADD_SUCCESS,
        props<{ pokemon: Pokemon }>()
      ),
      addFailed: createAction(
        PokemonActionTypes.ADD_FAILED,
        props<{ message: string }>()
      ),
      remove: createAction(PokemonActionTypes.REMOVE, props<{ id: number }>()),
      removeSuccess: createAction(
        PokemonActionTypes.REMOVE_SUCCESS,
        props<{ id: number }>()
      ),
      removeFailed: createAction(
        PokemonActionTypes.REMOVE_FAILED,
        props<{ message: string }>()
      ),
      update: createAction(
        PokemonActionTypes.UPDATE,
        props<{ pokemon: Pokemon }>()
      ),
      updateSuccess: createAction(
        PokemonActionTypes.UPDATE_SUCCESS,
        props<{ pokemon: Pokemon }>()
      ),
      updateFailed: createAction(
        PokemonActionTypes.UPDATE_FAILED,
        props<{ message: string }>()
      )
    };
    
    エクスポートしましたaction CONSTとは、アクション名をキーとしてアクション自身を値として含む辞書です.
    CreateActionは、ActionCreatorと呼ばれる関数を返すファクトリ関数です.呼び出されたときにアクションオブジェクトを返します.したがって、アクションを送信するときにActionCreatorを呼び出す必要があります.
    this.store.dispatch(addSuccess(pokemon: Pokemon));
    
    アクションのクラスに関連付けられたオブジェクトを作成する必要はなくなりました.関数を直接呼び出すことができます.
    このため、アクションが作成されるすべてのエフェクトには、次のリファクタリングを適用する必要があります.
    以前
    @Effect()
    loadAllPokemon$: Observable<any> = this.actions$.pipe(
      ofType(PokemonActions.PokemonActionTypes.LOAD_POKEMONS),
      switchMap(() =>
        this.pokemonService.getAll().pipe(
        map(pokemons => new PokemonActions.LoadPokemonSuccess(pokemons)),
          catchError(message => of(new PokemonActions.LoadPokemonFailed(message)))
        )
      )
    );
    
    アフター
    @Effect()
    loadAllPokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.loadPokemon),
        switchMap(() =>
          this.pokemonService.getAll().pipe(
            map(pokemons => PokemonActions.loadPokemonSuccess({ pokemons })),
            catchError(message => of(PokemonActions.loadPokemonFailed({ message }))
              )
            )
          )
        )
      );
    
    この効果は、次のセクションでCreateEffect関数を使用してリファクタリングされます.

    創造的効果


    NGRX 8はcreateEffect に代わる方法@Effect() デコレータ.使用の主な利点createEffect デコレータの代わりに、それはタイプセーフです.つまり、効果が返されない場合はObservable<Action> コンパイルエラーをスローします.
    次のコード片ではloadAllPokemon$ 新築前後の効果createEffect メソッド.移行は非常に簡単です.
    以前
    @Effect()
    loadAllPokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.loadPokemon),
        switchMap(() =>
          this.pokemonService.getAll().pipe(
            map(pokemons => PokemonActions.loadPokemonSuccess({ pokemons })),
            catchError(message => of(PokemonActions.loadPokemonFailed({ message }))
              )
            )
          )
        )
      );
    
    アフター
    loadAllPokemon$ = createEffect(() =>
        this.actions$.pipe(
          ofType(PokemonActions.loadPokemon),
          switchMap(() =>
            this.pokemonService.getAll().pipe(
              map(pokemons => PokemonActions.loadPokemonSuccess({ pokemons })),
              catchError(message =>
                of(PokemonActions.loadPokemonFailed({ message }))
              )
            )
          )
        )
      );
    
    したがって、前後pokemon.effects.ts は以下の通りです.
    以前
    import * as PokemonActions from '@states/pokemon/pokemon.actions';
    
    import { Actions, Effect, ofType } from '@ngrx/effects';
    import { Observable, of } from 'rxjs';
    import { catchError, map, switchMap, tap } from 'rxjs/operators';
    
    import { Injectable } from '@angular/core';
    import { MatSnackBar } from '@angular/material';
    import { Pokemon } from '@shared/interfaces/pokemon.interface';
    import { PokemonService } from '@services/pokemon.service';
    
    @Injectable()
    export class PokemonEffects {
      constructor(
        private actions$: Actions,
        private pokemonService: PokemonService,
        public snackBar: MatSnackBar
      ) {}
    
      POKEMON_ACTIONS_SUCCESS = [
        PokemonActions.PokemonActionTypes.ADD_SUCCESS,
        PokemonActions.PokemonActionTypes.UPDATE_SUCCESS,
        PokemonActions.PokemonActionTypes.DELETE_SUCCESS,
        PokemonActions.PokemonActionTypes.LOAD_POKEMONS_SUCCESS
      ];
    
      POKEMON_ACTIONS_FAILED = [
        PokemonActions.PokemonActionTypes.ADD_FAILED,
        PokemonActions.PokemonActionTypes.UPDATE_FAILED,
        PokemonActions.PokemonActionTypes.DELETE_FAILED,
        PokemonActions.PokemonActionTypes.LOAD_POKEMONS_FAILED
      ];
    
      @Effect()
      loadAllPokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.PokemonActionTypes.LOAD_POKEMONS),
        switchMap(() =>
          this.pokemonService.getAll().pipe(
            map(response => new PokemonActions.LoadPokemonSuccess(response)),
            catchError(error => of(new PokemonActions.LoadPokemonFailed(error)))
          )
        )
      );
    
      @Effect()
      addPokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.PokemonActionTypes.ADD),
        switchMap((action: any) =>
          this.pokemonService.add(action.pokemon).pipe(
            map((pokemon: Pokemon) => new PokemonActions.AddSuccess(pokemon)),
            catchError(error => of(new PokemonActions.AddFailed(error)))
          )
        )
      );
    
      @Effect()
      deletePokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.PokemonActionTypes.DELETE),
        switchMap(({ id }) =>
          this.pokemonService.delete(id).pipe(
            map(() => new PokemonActions.DeleteSuccess(id)),
            catchError(error => of(new PokemonActions.DeleteFailed(error)))
          )
        )
      );
    
      @Effect()
      updatePokemon$: Observable<any> = this.actions$.pipe(
        ofType(PokemonActions.PokemonActionTypes.UPDATE),
        switchMap(({ pokemon }) =>
          this.pokemonService.update(pokemon).pipe(
            map(() => new PokemonActions.UpdateSuccess(pokemon)),
            catchError(error => of(new PokemonActions.UpdateFailed(error)))
          )
        )
      );
    
      @Effect({ dispatch: false })
      successNotification$ = this.actions$.pipe(
        ofType(...this.POKEMON_ACTIONS_SUCCESS),
        tap(() =>
          this.snackBar.open('SUCCESS', 'Operation success', {
            duration: 2000
          })
        )
      );
      @Effect({ dispatch: false })
      failedNotification$ = this.actions$.pipe(
        ofType(...this.POKEMON_ACTIONS_FAILED),
        tap(() =>
          this.snackBar.open('FAILED', 'Operation failed', {
            duration: 2000
          })
        )
      );
    }
    
    
    アフター
    import { Actions, createEffect, ofType } from '@ngrx/effects';
    import { catchError, map, switchMap, tap } from 'rxjs/operators';
    
    import { Injectable } from '@angular/core';
    import { MatSnackBar } from '@angular/material';
    import { Pokemon } from '@shared/interfaces/pokemon.interface';
    import { actions as PokemonActions } from '@states/pokemon/pokemon.actions';
    import { PokemonService } from '@services/pokemon.service';
    import { of } from 'rxjs';
    
    @Injectable()
    export class PokemonEffects {
      constructor(
        private actions$: Actions,
        private pokemonService: PokemonService,
        public snackBar: MatSnackBar
      ) {}
    
      POKEMON_ACTIONS_SUCCESS = [
        PokemonActions.addSuccess,
        PokemonActions.updateSuccess,
        PokemonActions.removeSuccess,
        PokemonActions.loadPokemonSuccess
      ];
    
      POKEMON_ACTIONS_FAILED = [
        PokemonActions.addFailed,
        PokemonActions.updateFailed,
        PokemonActions.removeFailed,
        PokemonActions.loadPokemonFailed
      ];
    
      loadAllPokemon$ = createEffect(() =>
        this.actions$.pipe(
          ofType(PokemonActions.loadPokemon),
          switchMap(() =>
            this.pokemonService.getAll().pipe(
              map(pokemons => PokemonActions.loadPokemonSuccess({ pokemons })),
              catchError(message =>
                of(PokemonActions.loadPokemonFailed({ message }))
              )
            )
          )
        )
      );
    
      addPokemon$ = createEffect(() =>
        this.actions$.pipe(
          ofType(PokemonActions.add),
          switchMap((action: any) =>
            this.pokemonService.add(action.pokemon).pipe(
              map((pokemon: Pokemon) => PokemonActions.addSuccess({ pokemon })),
              catchError(message => of(PokemonActions.addFailed({ message })))
            )
          )
        )
      );
    
      deletePokemon$ = createEffect(() =>
        this.actions$.pipe(
          ofType(PokemonActions.remove),
          switchMap(({ id }) =>
            this.pokemonService.delete(id).pipe(
              map(() => PokemonActions.removeSuccess({ id })),
              catchError(message => of(PokemonActions.removeFailed({ message })))
            )
          )
        )
      );
    
      updatePokemon$ = createEffect(() =>
        this.actions$.pipe(
          ofType(PokemonActions.update),
          switchMap(({ pokemon }) =>
            this.pokemonService.update(pokemon).pipe(
              map(() => PokemonActions.updateSuccess({ pokemon })),
              catchError(message => of(PokemonActions.updateFailed(message)))
            )
          )
        )
      );
    
      successNotification$ = createEffect(
        () =>
          this.actions$.pipe(
            ofType(...this.POKEMON_ACTIONS_SUCCESS),
            tap(() =>
              this.snackBar.open('SUCCESS', 'Operation success', {
                duration: 2000
              })
            )
          ),
        { dispatch: false }
      );
    
      failedNotification$ = createEffect(
        () =>
          this.actions$.pipe(
            ofType(...this.POKEMON_ACTIONS_FAILED),
            tap(() =>
              this.snackBar.open('FAILED', 'Operation failed', {
                duration: 2000
              })
            )
          ),
        { dispatch: false }
      );
    }
    
    使用する前にdispatch: false 各エフェクトに渡されるパラメータはcreateEffect メソッド.オプションを覚えておいてください{ dispatch: false } 新しいアクションを送信しない効果に対して使用されます.このオプションを追加すると、Observable<Action> .

    還元剤


    新しいcreateReducer メソッドを使用すると、switch 文.があるon アクションの型を区別するメソッドです.もう一つの興味深い事実は、還元器の取り扱われていない行動のためにデフォルトのケースを扱う必要はありません.
    したがって、前後pokemon.reducers.ts は以下の通りです.
    以前
    import { PokemonActionTypes, PokemonActions } from './pokemon.actions';
    import { PokemonState, pokemonAdapter } from './pokemon.adapter';
    
    export function pokemonInitialState(): PokemonState {
      return pokemonAdapter.getInitialState();
    }
    
    export function pokemonReducer(
      state: PokemonState = pokemonInitialState(),
      action: PokemonActions
    ): PokemonState {
      switch (action.type) {
        case PokemonActionTypes.LOAD_POKEMONS_SUCCESS:
          return pokemonAdapter.addAll(action.payload, state);
    
        case PokemonActionTypes.ADD_SUCCESS:
          return pokemonAdapter.addOne(action.pokemon, state);
    
        case PokemonActionTypes.DELETE_SUCCESS:
          return pokemonAdapter.removeOne(action.id, state);
    
        case PokemonActionTypes.UPDATE_SUCCESS:
          const { id } = action.pokemon;
          return pokemonAdapter.updateOne(
            {
              id,
              changes: action.pokemon
            },
            state
          );
    
        default:
          return state;
      }
    }
    
    アフター
    import { Action, createReducer, on } from '@ngrx/store';
    import { PokemonState, pokemonAdapter } from './pokemon.adapter';
    
    import { actions as PokemonActions } from './pokemon.actions';
    
    export function pokemonInitialState(): PokemonState {
      return pokemonAdapter.getInitialState();
    }
    
    const pokemonReducer = createReducer(
      pokemonInitialState(),
      on(PokemonActions.loadPokemonSuccess, (state, { pokemons }) =>
        pokemonAdapter.addAll(pokemons, state)
      ),
      on(PokemonActions.addSuccess, (state, { pokemon }) =>
        pokemonAdapter.addOne(pokemon, state)
      ),
      on(PokemonActions.removeSuccess, (state, { id }) =>
        pokemonAdapter.removeOne(id, state)
      ),
      on(PokemonActions.updateSuccess, (state, { pokemon }) =>
        pokemonAdapter.updateOne({ id: pokemon.id, changes: pokemon }, state)
      )
    );
    
    export function reducer(state: PokemonState | undefined, action: Action) {
      return pokemonReducer(state, action);
    }
    
    注意createReducer methodパラメータのリストを受け取ります:
    最初のパラメータは初期状態で、2番目のパラメータはon メソッド.にon 最初のパラメータは関連するアクションです.私の場合、私は行動を維持しましたenum データ構造が好きだからです.もちろん、ENUMを使用せずに直接アクションをエクスポートできます.The second parameter of the on メソッドは、state and payload を受信する.その後、我々は強力な使用することができますEntityAdapter 最も一般的な操作を行う.

    結論


    このポストでは、我々は我々のPOK@ngrx/entity パッケージのcreate* 関数.CREATE *関数の使用は、アプリケーションの状態の管理に不要な複雑さを減少させます.さらに、アダプタは最も一般的な操作(CRUD)を実行するために使用されます.
    したがって、この投稿では以下のトピックを取り上げました.
  • @ ngrx/entityを使用することで非常に反復的であるので、状態の作成を自動化します.
  • 自動化効果、アクションの作成と縮小機能を簡素化を使用して@ngrx/entity .
  • このシリーズの次の投稿は、次のような興味深いトピックをカバーします.
  • ファサードパターンは、@ngrx/data パッケージ.
  • アプリケーションの状態のテスト.
  • このポストの最も重要な部分は、示される概念です、そして、テクニックまたは図書館は使われませんでした.したがって、このポストは、大きな角度のアプリケーションを開始し、建築の原則を適用する必要がある人のためのガイドとして撮影する必要があります.

    より多くの.

  • Announing NgRx 8
  • Angular Architecture Best Practices
  • Angular Architecture - ng-conf
  • Angular Architecture (official docs)
  • NGRX
  • RxJS
  • Facade Pattern
  • このポストのGithub支店はhttps://github.com/Caballerog/ngrx-pokedex/tree/ngrx-part3