AngularとRXJSで簡単なストアを作ってみる

# はじめに
**注意！！Fluxパターンの記事ではありません！！**

Vue人気も一段落ついた感があるので、ここからAngularを盛り上げる（？）ためにも何か投稿せねば！！

ということでAngular+RXJSの`BehaiviorSubject`を利用した簡単なストアを作ってみます。

環境

- angular-cli 6.2.2
- angular ^6.1.0
- rxjs ~6.2.0

# サービス定義


まずはおもむろに`ng new`するなりして、Angularが書ける環境を用意します。

んで`src/app`内に`core`ディレクトリを切ります。

その中でさっそく`StoreService`を書きます。このサービスは`User`型を`BehaviorSubject`で保持します。`BehaviorSubject`のstreamを流すだけで無く、最後に流された値を保持する機能を利用します。

ついでにObservableとして観測できるようにもします。

```typescript:core/store-service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

// 何の変哲もないユーザー型
export interface User {
  id: number;
  name: string;
}

// Storeサービス
@Injectable({
  providedIn: 'root',
})
export class StoreService {
  // 直接アクセスされるのは避けたいのでprivate
  private userSource = new BehaviorSubject<User>({
    id: 0,
    name: 'knight',
  });

  // Observable value
  user$ = this.userSource.asObservable();

  // Getter & Setter
  get user(): User {
    return this.userSource.getValue();
  }
  set user(value: User) {
    this.userSource.next(value);
  }
}
```

次にこのサービスを提供する`CoreModule`を作成します。このモジュールは`AppModule`からしか呼ぶことができなくしてあります＝Singleton。

```typescript:core/core.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  imports: [CommonModule],
})
export class CoreModule {
  constructor(
    @Optional()
    @SkipSelf()
    parentModule: CoreModule,
  ) {
    if (parentModule) {
      throw new Error(`${parentModule} has already been loaded. Import Core module in the AppModule only.`);
    }
  }
}
```

# コンポーネント定義
次に`core`ディレクトリから出て`StoreService`を利用する`AComponent`を作成します。

このコンポーネントは`StoreService`をInjectし、ストアを参照します。

クリックイベントを起こして取得するGET版と、観測型のObservable版の2種類で`User`を取得しています。

```typescript:a/a.component.ts
import { Component } from '@angular/core';
import { StoreService, User } from '../core/store.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-a',
  template: `
    <h2>Get版</h2>
    <ng-container *ngIf="user">
      <dl class="list">
        <dt>ID:</dt><dd>{{ user.id }}</dd>
        <dt>NAME:</dt><dd>{{ user.name }}</dd>
      </dl>
    </ng-container>
    <button (click)="onGetUser()">GET USER</button>
    <div class="divider"></div>
    <h2>Observable版</h2>
    <ng-container *ngIf="user$ | async as u">
      <dl class="list">
        <dt>ID:</dt><dd>{{ u.id }}</dd>
        <dt>NAME:</dt><dd>{{ u.name }}</dd>
      </dl>
    </ng-container>
  `,
  styles: [
    `
      .list {
        margin: 8px;
        display: flex;
      }

      dt {
        font-weight: bold;
      }

      dd {
        margin-right: 16px;
      }

      .divider {
        width: 100%;
        margin: 16px 0;
        border-bottom: solid 1px #000;
      }
    `
  ]
})
export class AComponent {
  user$: Observable<User>;
  user: User = null;

  constructor(private store: StoreService) {
    this.user$ = this.store.user$;
  }

  onGetUser() {
    this.user = this.store.user;
  }
}

```

`AComponent`の観測（`user$`）がちゃんと見れているか確かめるため、`AppComponent`側の`ngOnInit()`で5秒後に`User`を更新してみます。

```typescript:app.component.ts
import { Component, OnInit } from '@angular/core';
import { StoreService, User } from './core/store.service';

@Component({
  selector: 'app-root',
  template:
  `
    <div>
      <app-a></app-a>
    </div>
  `,
  styles: []
})
export class AppComponent implements OnInit {
  constructor(private store: StoreService) {}

  ngOnInit() {
    setTimeout(() => {
      this.store.user = {
        id: 1,
        name: 'spidey',
      };
    }, 5000);
  }
}

```

`CoreModule`を読んでるだけですが、`AppModule`も載せときます。

```typescript:app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { AComponent } from './a/a.component';

@NgModule({
  declarations: [
    AppComponent,
    AComponent,
  ],
  imports: [
    CommonModule,
    BrowserModule,
    CoreModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

# RUN
```console
$: ng serve
```
してみると・・・
![image.png](https://qiita-image-store.s3.amazonaws.com/0/94252/290fc9c1-dc17-fe8b-0e62-94ca3c8a9fd5.png)

んな感じで表示されるはずです。

上記のスクショは5秒経つ前のものですが、5秒後にはObservable版は`AppComponent`で定義した`{id:1, name: spidey}`で更新されます。

# おわりに
何か身のない記事になった気もしますが、応用すれば全ての状態を観測可能なものにもできたりするんじゃないかなぁと思ったりします。

Angularですが、バージョンも6となり（もうすぐ7が出ますが・・・）、2系初期の混乱からは抜け出した感があると思うので、VueやReactだけじゃなくてAngularももうちょっと人気が出てくれたらなぁと。