(WIP) Pub-Sub

(1年前に途中までまとめたもの。こっちに転載。)


AngularJS: Notifying about changes from services to controllers - codelord.net を参考にStoreっぽいものを実装しようとした時のメモ

// component
class FooController {
  constructor(
    private $scope: ng.IScope,
    private NotifyingService: NotifyingService,
    ) {
  }
  public $onInit() {
    this.NotifyingService.subscribe(this.$scope, () => {
      this.doSomething();
    });
  }
  private doSomething() {
    // do something
    // 
  }
}
const FooComponent: ng.IComponentOptions = {
  template: '...'
  controller: FooController,
  bindings: {
    // ...
  }
};

// service
const EVENT_NAME: string = 'notifying-service-event';
class NotifyingService {
  $data: any;
  constructor(private $rootScope: ng.IRootScopeService) {
  }
  public initialize() {
    // this.$data = new Data();
  }
  public subscribe(scope: ng.IScope, callback: (any) => void) {
    const handler = this.$rootScope.$on(EVENT_NAME, callback);
    scope.$on('$destroy', handler);
  }
  public update(data, noti: boolean = true) {
    const prev = angular.copy(this.$data);
    angular.extend(this.$data, data);
    if (!angular.equals(prev, this.$data)) {
      if (noti) {
        this.notify();
      }
    }
  }
  private notify() {
    this.$rootScope.$emit(EVENT_NAME);
  }
}

angular.module('app', [])
.component('foo', FooComponent)
.service('NotifyingService', NotifyingService);
  • $destroyがある
  • $emitしか利用しない
  • eventの挙動は隠蔽されている

簡易版

angular.module('app').controller('TheCtrl', function(NotifyingService) {
  NotifyingService.subscribe(function somethingChanged() {});
});
angular.module('app').factory('NotifyingService', function() {
  var onChanges = [];
  return {
    subscribe: function subscribe(callback) {
      onChanges.push(callback);
    },
    notify: function notify() {
      onChanges.forEach(function(cb) { cb(); });
    }
  };
});
  • 「変更した」というアクションに限定
  • $scope, $rootScopeが不要
  • React • TodoMVC に似ている
  • 複数のCtrlから利用するときに、個別リスナー解除ができない

実際にやろうとして失敗したこと

f:id:ryotah:20170517004339p:plain
(構成)

f:id:ryotah:20170517183037p:plain
(例: destory時の流れ)

  • ui-viewがまだコンポーネント対応していなかった
    • inputs/outputsにほうがシンプルでよかったかも
  • storeの並列が一番無理があった
    • 両方で同じようなデータを利用するとき
      • 両方に持つのも、主従にするのも…
    • 初期化のタイミング、更新順
  • containerとstoreのデータ被り
    • templateで利用する必要があるため、多数の変数をcontainer上で展開
    • (これは $data: FooData; みたいにすればよかったのかもしれない↓)
class FooController {
  $data: FooData;
  constructor(
    private $scope: ng.IScope,
    private FooStateService: ParentStateService
    ) {
  }
  public $onInit() {
    this.FooStateService.initialize();
    this.FooStateService.subscribe(this.$scope, () => {
      this.$data = this.FooStateService.data;
    })
    this.$data = this.FooStateService.data;
  }
  public handleUpdateBar($event: { value: number }) {
    this.ParentStateService.update({
      bar: $event.value
    });
  }
}