趣味的なIT・ネットの話題

Angular2でSPAなシステム開発:serviceのデータを同期するならPromiseよりもEmitter、EmitterよりもObservable

Ionic2がAngular2を前提している関係上、作法に則った形でアプリケーションを作ろうとすると、どうしてもAngular2の知識が必要になります。

今回やりたかったのは、websocketの接続やデータを一括してserviceで行い、serviceがwebsocketでデータを受信したら、関係しているPageでデータのアップデートを行うという作業。各Pageでwebsocketの接続を作るのであれば考えることは少ないのですが、一つのアプリケーションで複数の接続を張るというのは合理的では無いですし、同じデータを複数のPageで使うこともあるので、ばらばらに接続すると整合性に問題も出そうです。

Angular2で各Pageで同じデータを共有するにはInjecterを付けたserviceを使うのが基本のお作法の様です。

Services – ts

でここで困ったのが、service側で情報を取得して管理する情報が変化したときに、これをPage側に働きかけてPageを更新する方法。Page側からserviceのメソッドを実行するのは簡単なのですが、Service側がきっかけとなって更新をする方法がすぐには分かりませんでした。本当はserviceとPageの各変数を直接バインドしてしまう方法があればいいなと思ったのですが、今日かなり検索した限りでは見つかりませんでした。

最初に気づいたのが、先ほどのページで解説されているPromiseを使う方法。

JavaScript Promiseの本

このサイトは素晴らしいですね。無茶苦茶わかりやすい。みんな買いましょう。

  getAfterFalse() {
    console.log("getAfterFalse");
    return new Promise<Hero[]>(resolve =>{
      setInterval(()=>{
        if(this.flag == false){
          this.flag = true;
          console.log("flag change");
          resolve(HEROES);
        }
      },1000);
    });
  }

service内で目的の条件が達成されるかどうかをウォッチしておいて、条件達成時にresolveをして、呼出元に処理を戻します。

this._heroService.getAfterFalse().then(heroes => this.heroes = heroes);

Pageに戻ってきたらPageの変数を更新します。が、ポーリングをしているというのがいかにもださく、処理に即時性も無いですし、負荷も大きいはずです。

そこで次に発見したのがEventEmitterを使う方法。

Angular2 detect change in service – Stack Overflow

import {EventEmitter} from 'angular2/core';

@Injectable()
export class HeroService {

  public flagUpdated = new EventEmitter();

service側でEventEmitterを定義しておいて、発火させたいところでemitします。

    this.flagUpdated.emit(this.flag);

Page側はsubscribeしておくと、emitされて時点でイベントを検知して処理が実行できます。

    this._heroService.flagUpdated.subscribe(
      (flag) => {
        this.getHeroes();
        console.log(flag);
      }

が、EventEmiterをカスタムイベント以外のために使うのは誤用のようでできるだけ避けるべきとの事。

EventEmitter is really an Angular abstraction, and should be used pretty much only for emitting custom Events in components.

でここで解説されている通りObservableを使うことに。一番ややこしそうなので敬遠していたのですが、HTTPモジュールを使うためには結局は慣れていないといけないと諦めました。リンク先のサンプルは何故か長いですが、やってみるとコンパクトな記述もできそうです。

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';

@Injectable()
export class HeroService {

  flagChange$: Observable<number>;
  private _observer;

  constructor() {
  this.flagChange$ = new Observable(observer =>
    this._observer = observer).share();
  // share() allows multiple subscribers
  }

それでも下準備は一番長いです。2つのimportと2つの変数、そしてインスタンスの定義が必要です。

定義した_observerのnextを呼ぶと発火します。

    this._observer.next(this.flag);

Page側の処理はEventEmitterの場合と同じで、インスタンスのsubscribeを呼びます。

    this._heroService.flagChange$.subscribe(
      flag => {
        this.getHeroes();
        console.log(flag);
      }

公式のTour of HerosのServicesのサンプルコードに手を加えて実験してみました。何をやっているかわかりにくいのですが、見出しのTour of Herosの文字列にクリックイベントが設定してあり、ここでID13の名前を押した時点でのDateのmilisecondに変更しています。同時にObservableのnextを実行して、Page側のsubscribeを実行しています。Page側はgetHeroesを実行しているので変数をservicesから取得してきて表示を更新しています。実際にはObservableでデータの取得・受渡までやらないと意味が無いのだと思いますが、とりあえず非同期をトリガーできるところまで。

日本語の情報が少なくて一つのことをやるのにも大分と時間がかかりますが、やってみると殊の外簡単。シンプルにアプリのロジックを書けそうでよいですね。


Facebooktwitterpinterestlinkedinmail
納得したらすぐにシェア!