LoginSignup
17
14

More than 5 years have passed since last update.

AngularのサービスクラスでEventEmitterを使ってはいけない

Last updated at Posted at 2017-09-25

TL;DR

テンプレート以外からイベントの購読処理を行うケースでは,rxjsを使いましょう.
EventEmitterを使うのは危険です

EventEmitterについて

EventEmitterは,コンポーネントからその上位コンポーネントへの,任意の値の通知を提供します.
つまり,

child.component.ts
   @Output()
   someAction = new EventEmitter<SomeObject>();

   func() {
     this.someAction.emit({ foo: 'bar' })
   } 

のようにしておけば,その上位コンポーネントで

higher.component.html
<app-child (someAction)="onSomeAction($event)"></app-child>

のように値を受け取れる,というわけです.

サービスクラスでの通知処理

では,コンポーネントではなく,サービスクラス等から,何らかの値を通知したい場合を考えます.
先程との違いは,実際の購読処理を行う責務が誰にあるかです.
先程での例では,開発者はテンプレート内で通知の処理方法を記述し,実際の購読処理を行う責務はAngularにありました.
今回の場合,実際の購読処理を行う責務は開発者にあります.

これを踏まえて,先程のEventEmitterを使うと,以下のように書けそうです.

以下は間違った例です

test.service.ts
class TestService {
  someChange = new EventEmitter<SomeObject>();
}
testService.someChange.subscribe(obj => ...)

確かにEventEmitterSubject<T>を継承していますが,このように書くのは間違いです.
EventEmitterの購読方法はあくまで内部的なもので,これからも同じ方法で購読できる保証はありません.
https://stackoverflow.com/a/36076701/3094842

つまり,EventEmitterの購読処理を行う責務を,開発者が負うことはできない,ということです.
これについて,このようなケースでは,単にrxjsを使うと良いようです.

先程のコードをrxjsを使用して書き直すと,以下のようになります.

class TestService {
  private internalSomeObject: SomeObject;
  someChange$ = new BehaviorSubject<SomeObject>(undefined);

  get someObject() { return this.internalSomeObject; }
  set someObject(someObject: SomeObject) {
    this.someChange$.next(someObject);
    this.internalSomeObject = someObject;
  }
}

これで,コンポーネントから認証情報の変更を検知できるようになります.

testService.someChange$.subscribe((someObject: SomeObject) => {
  // 変更を検知したときの処理
}); 
17
14
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
17
14