NgRx Dataが便利で、便利で

#1.はじめに

 この記事は [Angular Advent Calendar 2020](https://qiita.com/advent-calendar/2020/angular) 17日目の記事です。

 　スターアプリケーションズ株式会社吉田です。
　みなさんNgRxを使っていますか？エッ、あんまり使ってない。そうNgRxは難しいですからね。私もずいぶん苦労しました。いや、仕組みや機能は何とか理解できたのですが、一番難しかったのは、なぜこれを使うのかが分からなかったのです。「頭の良い人が使ってるから？」「みんなが使ってるから？」「チームで統一したいから？」Angularの場合代替手段もあるので、NgRxを使うモチベーションが高まりませんでした。それでもリアクティブプログラミングの流れに乗りたいと思いReduxやFluxも勉強し、使う理由をはっきりさせたかった。
　そしてある時、気がついたのです。（ここから先は筆者の主観が多く、間違ってる部分もあるかもしれません。)ReduxにしろNgRxにしろ、世間では「Status管理」とか「状態管理」とよんでいます。決して間違っていないのですが、これが初学者にあらぬ誤解を与えていたのです。例えばNgRxのサンプルでは、カウンターの例が多く出ていますが、たかがカウンターの増減で、どうしてこんなメンドちいことをするのだろう。さらに英語のStatusと日本語のステータスは実は違っているのではと疑ってみました。ステータスというと小さなフラグで✖️✖️✖️Modeとか□□□Modeとかを識別するのに用いたりするものを連想するのですが、Statusはもっと広い世界を現している。結局、ReduxやNgRxが目指しているものは、ページ間で共有するグローバルデータを管理するものだと言うことです。なぜそれが必要か、SPAだからです。エッ！みんな知っていた。失礼しました。
　さて、NgRxですがActionやReducerなどを作成するために、AngularCLIを利用したり、いくつかの簡略化の手段が提供されています。それでも多数のentityをstoreで利用する場合には、膨大なコーディングが必要になってきます。そこで以前よりNgRxにFacadeを使う動きがありました[(参考)](https://thomasburlesonia.medium.com/ngrx-facades-better-state-management-82a04b9a1e39)。NgRx DataもNgRxのFacadeとしてとらえられても良いと言うのが、本記事の趣旨です。NgRx Dataを使うことにより、ActionやReducerを書く手間を大幅に省けると言うことです。NgRx Dataを使ったことのない人向けの入門記事にもなります。

#2.デモプログラム

昨年のAdvent CalendarでもNgRxに関して述べました[[Angularから@ngrx/dataとActiveRecordを使ってRDBにアクセスする（追記あり）]](https://qiita.com/FumioYoshida/items/e9b1eaa36b0a94467d1b)。昨年のデモでは外部DBを使うものでしたが、本年は簡単なTodoListで内部メモリーだけを使ってAngularが閉じた形になります。
[(デモプログラム)](https://stackblitz.com/edit/angular-ngrx-todolist?file=src/app/app.component.ts
)

#3. entity

　NgRx Dataではentityと言う言葉が何度もでますが、entityとは何でしょうか。非常に端折った表現をすると、一意の識別子(key)を持った表(Table)になります。Tableと言うとデータベースを連想するかもしれませんが、データベースと関連することも有りますが、基本的には無関係です。entityは顧客リストであったり、従業員リストであったり、注文受付記録であったりします。一意の識別子は、NgRxではidと言う列(field)になります。なぜ一意の識別子が必要か、この辺はnrslibさんがDDDの説明で述べている解説がとても参考になります。[(ボトムアップドメイン駆動設計 3.2)](https://nrslib.com/bottomup-ddd/#outline__3_2)
entityのデータは必ずしも外部（例えばデータベース）から取り込まれるものに限っている訳ではありません。内部でしか使わないデータでもentityの形式に合わせて持っておくことが出来ます。

#4.セットアップ
##4.1 NgRx Dataのセットアップ
　NgRx Dataのセットアップから見ていきます。前提として、store,effects,entityのモジュールがあることです。コンソールから以下を実行します。

- ng add @ngrx/store@latest
- ng add @ngrx/effects@latest
- ng add @ngrx/entity@latest
- ng add @ngrx/data@latest

　上記のセットアップが終了すると、（entity-metadata.ts）と言うファイルがappディレクトリーの直下に出来ます。このファイルを編集することにより、entityの登録や、ソート、データの絞り込みなどを宣言することが出来ます。app.modules等への設定は自動的に行われます。

##4.2 entityのセットアップ
　entityのセットアップには、３種類（3箇所）あります。例として従業員entity の作成例を見ていきます。
###4.2.1 entity定義
　classを作成します。idは必須です。id以外の名前を使うことも出来ます(Entity MetaDataのselectidを参照)。

```employee.ts
export class Employee {
    id?: number ;
    firstName?: string ;
    familyName?: string ;
    employeeCode?: string ;
    divisionCode?: string ; 
}
```
###4.2.2 entity service作成
　serviceを作成します。下記の図でEmployeeと記載された部分を各entity毎に修正していきます。

```employee.service.ts
import { Injectable } from '@angular/core';
import { EntityCollectionServiceBase, EntityCollectionServiceElementsFactory } from '@ngrx/data';
import { Employee } from './employee' ;

@Injectable({ providedIn: 'root' })
export class EmployeeService extends EntityCollectionServiceBase<Employee> {
  constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
    super('Employee', serviceElementsFactory);
  }
}
```
###4.2.3 entity登録
　entityをNgRx Dataに知らせるため登録します。登録ファイル(entity-metadata.ts)はNgRx Dataインストール時に自動的にappディレクトリの下に作成されます。

```entity-metadata.ts
import { EntityMetadataMap, EntityDataModuleConfig } from '@ngrx/data';
import { TodoListFilter } from './repository/todo-list/todo-list-filter'

const entityMetadata: EntityMetadataMap = {
  Employee :{
     //様々なoptionを記載します sort,Filter,id名の変更など
  },
};
const pluralNames = { 
     //複数形が単純に末尾sでない場合、ここに記載します 例　Hero : Heroes
 };
export const entityConfig: EntityDataModuleConfig = {
  entityMetadata,
  pluralNames
};

```

#5.コマンド
##5.1 コマンドの種類
　前節で記載の通りentityごとにserviceが作成され、service毎にコマンドが発行出来ます。コマンドは、発行してそれの応答を待つことはしません。例えばデータを1件削除するコマンドを発行すると、entityのリストの中から１件削除され、その内容を自動的に受け取る形になります。
　entityコマンドは次の通りです。

```EntityCommands
interface EntityCommands<T> extends EntityServerCommands, EntityCacheCommands {
 
  // inherited from data/EntityServerCommands
  add(entity: T, options?: EntityActionOptions): Observable<T>
  cancel(correlationId: any, reason?: string, options?: EntityActionOptions): void
  delete(entity: T, options?: EntityActionOptions): Observable<number | string>
  getAll(options?: EntityActionOptions): Observable<T[]>
  getByKey(key: any, options?: EntityActionOptions): Observable<T>
  getWithQuery(queryParams: string | QueryParams, options?: EntityActionOptions): Observable<T[]>
  load(options?: EntityActionOptions): Observable<T[]>
  update(entity: Partial<T>, options?: EntityActionOptions): Observable<T>
  upsert(entity: T, options?: EntityActionOptions): Observable<T>
 
  // inherited from data/EntityCacheCommands
  addAllToCache(entities: T[], options?: EntityActionOptions): void
  addOneToCache(entity: T, options?: EntityActionOptions): void
  addManyToCache(entities: T[], options?: EntityActionOptions): void
  clearCache(options?: EntityActionOptions): void
  removeOneFromCache(entity: T, options?: EntityActionOptions): void
  removeManyFromCache(entities: T[], options?: EntityActionOptions): void
  updateOneInCache(entity: Partial<T>, options?: EntityActionOptions): void
  updateManyInCache(entities: Partial<T>[], options?: EntityActionOptions): void
  upsertOneInCache(entity: Partial<T>, options?: EntityActionOptions): void
  upsertManyInCache(entities: Partial<T>[], options?: EntityActionOptions): void
  setFilter(pattern: any, options?: EntityActionOptions): void
  setLoaded(isLoaded: boolean, options?: EntityActionOptions): void
  setLoading(isLoading: boolean, options?: EntityActionOptions): void
}

```

上のコマンドですが、Cache系とそれ以外に分かれています。Cache系は文字通り、Cacheのデータに対する操作になります。本記事DemoプログラムはCache上の操作になります。
また上のコマンド軍の中にupsertと言う文字列がありますが、これはinsertとupdateを合成したものになります。更新しに行って、データが存在しなければデータの挿入になります。
パラメータのうちentityとあるのは1行分のデータ、entitiesとあるのは複数行分可能になります。Partialとあるのは部分の意味です。例えば更新系ですと、entityの全てのフィールドを設定することなく一部のフィールド(変更があった部分)のみの設定で可能です。パラメータで注意しなければならないのは、データは必ずentityとは別に作成してそれを渡します。

##5.2 コマンドの使い方

```
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Employee } from '../employee';
import { EmployeeService } from '../employee.service';
 
@Component({
  selector: 'app-employees',
  templateUrl: './employees.component.html',
  styleUrls: ['./employees.component.scss']
})
export class EmployeesComponent implements OnInit {
  loading$: Observable<boolean>;
  employees$: Observable<Employee[]>;
 
  constructor(private employeeService: EmployeeService) {
    this.employees$ = employeeService.entities$;
    this.loading$ = employeeService.loading$;
  }
 
  ngOnInit() {
    this.getEmployees();
  }
  add(employee: Employee) {
    this.employeeService.add(employee);
  }
  getEmployees() {
    this.employeeService.getAll();
  }
  update(employee: Employee) {
    this.employeeService.update(employee);
  }
```
#6.データ受け取り
　前節でコマンド発行、この節でデータの受け取りと言うことで、何となくCQRSの流れになっていると思いませんか。

##6.1 受け取りデータの種類
　NgRx Dataで受け取るのは次の３つです。特に最初の２つが重要です。末尾が＄の変数はObservableな変数になります。

　・entities$  　　　　　entityの全データが対象になります

　・filteredEntities$　　予めfilterで絞り込まれたデータが対象です

　・loading$　　　　　boolean値で現在ロード中ならtrueになりまう

##6.2 データ取得
　component側ではserviceをDIで取得、entityの値とobservable変数を結びつけます。Template側では、Asyncパイプを使ってデータを取り込みます。

```employees.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Employee } from '../employee';
import { EmployeeService } from '../employee.service';
 
@Component({
  selector: 'app-employees',
  templateUrl: './employees.component.html',
  styleUrls: ['./employees.component.scss']
})
export class EmployeesComponent implements OnInit {
  loading$: Observable<boolean>;
  employees$: Observable<Employee[]>;
 
  constructor(private employeeService: EmployeeService) {
    this.employees$ = employeeService.entities$;

```
Template側は次のようになります。

```employees.component.html

<ng-container *ngIf = "employees$ | async as employees">

<!-- ユーザーのHTML文　*ngIf が成功したとき実行される　-->

</ng-container>

```



#7.まとめ

　本記事は、NgRx Dataの[マニュアル](https://ngrx.io/guide/data)に沿った内容になります。ここで、プログラムの内部データや設定値のようなものでも、entityの形式に落とし込めれば、NgRx Dataを利用することができると言うことです。
　もう一度NgRx Dataの内容をまとめると、次の5つのことを行うだけです。
　　1.NgRx Dataをprojectにセットアップする
　　2.entityの定義を行い、登録する
　　3.entity serviceを作成する(ボイラープレート有り)
　　4.entity serviceを使ってコマンドを発行する
　　5.Asyncパイプでデータを受け取る(将来は Let Directiveになる可能性も)
　上記の２から５はentityごとに発生するものです。基本の形が用意されているので、うまく利用すれば開発の生産性がとても上がってくると思います。