LoginSignup
7
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-10-18

はじめに

注意!!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として観測できるようにもします。

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。

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を取得しています。

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を更新してみます。

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も載せときます。

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

$: ng serve

してみると・・・
image.png

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

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

おわりに

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

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

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6