【Angular】Ngrx概要

 

Ngrx?

 

f:id:kanmi-program:20201126224833p:plain



 

用語集

Component

 

Action

  • 「何が起こったか」という情報を持ったオブジェクト

  • コンポーネントからのイベントをトリガーとしてActionオブジェクトをStoreにDispatchする

 

Reducer

  • ActionオブジェクトとStateを受け取りStateを更新する

  • 更新する場合はStateを新規のオブジェクトとして作成する

 

Store

  • Stateを保存する場所

 

Selector

  • Storeから特定のデータを取り出す

 

Effects

  • 外部APIとの通信などをComponentから切り分ける

  • Reduxからの拡張機能

 

Service

  • Angularのサービス

 

 

実装

インストール

Store

npm install @ngrx/store --save
ng add @ngrx/store@latest

 

Effects

  • 使う人だけ

npm install @ngrx/effects --save
ng add @ngrx/effects@latest

 

Devtool

  • 使う人だけ

  • productionビルド時はログ参照のみになる

npm install @ngrx/store-devtools --save
ng add @ngrx/store-devtools@latest

 

<参考>

https://ngrx.io/guide/store/install

https://ngrx.io/guide/store-devtools/install

https://ngrx.io/guide/effects/install

 

 

Storeの作成

  • Stateとして管理するものを定義

 

store-definition.ts

import { LoggedInReducer, UserIdReducer } from '../reducer/user-reducer';

/**
* Key定義
*/
export enum Storekey {
 isloggedIn = 'isloggedIn',
 userId = 'userId',
}

/**
* Store定義
*/
export const StoreDefinition = {
 isloggedIn: LoggedInReducer,
 userId: UserIdReducer,
};

/**
* 型定義
*/
export interface AppStore {
 isloggedIn: boolean;
 userId: string;
}

 

 

Actionの作成

  • Stateを参照するActionを定義

 

user-actions.ts

import { createAction, props } from '@ngrx/store';

/**
* ユーザーAction
*/
export const UserActions = {
 login: createAction('user/login'),
 logout: createAction('user/logout'),
 // Actionの引数としてuserIdを定義
 addUser: createAction('user/add', props<{ userId: string }>()),
 deleteUser: createAction('user/delete'),
};

 

Reducerの作成

  • Actionに紐づく処理を定義

 

user-reducers.ts

import { Action, createReducer, on } from '@ngrx/store';
import { UserActions } from '../action/user-actions';

/** ログイン状態 */
export const loggedIn: boolean = false;
/** ユーザーID */
export const userId: string = '';

const loggedInReducer = createReducer(
  loggedIn,
  on(UserActions.login, (state) => {
    // 変更後のstateを返却(新しいオブジェクトとして作成)
    return true;
  }),
  on(UserActions.logout, (state) => {
    return false;
  })
);

const userIdReducer = createReducer(
  userId,
  // Actionの引数としてuserIdを受け取る
  on(UserActions.addUser, (state, { userId }) => {
    return userId;
  }),
  on(UserActions.deleteUser, (state) => {
    return '';
  })
);

export function LoggedInReducer(state: boolean, action: Action) {
  return loggedInReducer(state, action);
}

export function UserIdReducer(state: string, action: Action) {
  return userIdReducer(state, action);
}

 

NgModuleに設定

  • インストール時のng addで自動で追加される

  • Store定義を手動で追加

 

app.module.ts

import { StoreModule } from '@ngrx/store';
import { StoreDefinition } from './state/store/store-definition';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

imports: [
    // Store定義を追加
    StoreModule.forRoot(StoreDefinition),
    // Devtoolの設定
    StoreDevtoolsModule.instrument({
      maxAge: 25,
      logOnly: environment.production,
    }),
]

 

Componentから呼び出す

  • 定義したActionをStoreにDispatchする

  • 入力した内容でStateを更新する

  • 遷移先のComponentで更新されたStateを取得する

 

遷移元component

import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { UserActions } from 'src/app/state/action/user-actions';
import { AppStore, Storekey } from 'src/app/state/store/store-definition';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
})
export class TestComponent implements OnInit {
  constructor(
    private fb: FormBuilder,
    private router: Router,
    private store: Store<AppStore>
  ) {}

  public sampleForm: FormGroup;
  ngOnInit() {
    this.initForm();
  }

  initForm() {
    this.sampleForm = this.fb.group({
      userId: ['', Validators.required],
      password: ['', Validators.required],
    });
  }

  login(value: any) {
    // ログインActionをdispatch
    this.store.dispatch(UserActions.login());
    // ユーザーID追加Actionをdispatch
    this.store.dispatch(UserActions.addUser({ userId: value.userId }));

    // 画面遷移
    this.router.navigate(['/state']);
  }
}

 

遷移先component

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { AppStore, Storekey } from 'src/app/state/store/store-definition';

@Component({
  selector: 'app-state',
  templateUrl: './state.component.html',
  styleUrls: ['./state.component.scss'],
})
export class StateComponent implements OnInit {
  /** ログイン状態 */
  isloggedIn$: Observable<boolean>;
  /** ユーザーID */
  userId$: Observable<string>;

  constructor(private store: Store<AppStore>) {
    // storeから取得
    this.isloggedIn$ = store.select(Storekey.isloggedIn);
    this.userId$ = store.select(Storekey.userId);
  }

  ngOnInit(): void {}
}

 

遷移先html

<!-- 取得したstateを表示 -->
<p>ログイン状態:{{isloggedIn$ | async}}</p>
<p>ユーザーID:{{userId$ | async}}</p>