Anglarはserviceの状態によってdirectiveを更新します.


TL;DR
この文章はserviceの状態によってdirectiveを更新する3つの方法を説明しています.それぞれ$watch式、イベント伝達、およびcontrollerの計算属性である.
問題readerServiceがあります.接続状態や電気量などの状態情報が含まれています.今はdirectiveを作ってこれらの状態を展示したいです.readerServiceからしかデータを取得できないので、外部の送信値は必要ありません.だから直接にサービスを注入します.どうやって更新するかが問題です.
サービスのコードは以下の通りです.
const STATUS = {
  DETACH: 'DETACH',
  ATTACH: 'ATTACH',
  READY:  'READY'
}

class ReaderService {
  constructor() {
    this.STATUS = STATUS

    // The status will be changed by some callbacks
    this.status = STATUS.DETACH
  }
}

angular.module('app').service('readerService', readerService)
directiveコードは以下の通りです.
angular.module('app').directive('readerIndicator', (readerService) => {
  const STATUS = readerService.STATUS

  const STATUS_DISPLAY = {
    [STATUS.DETACH]: 'Disconnected',
    [STATUS.ATTACH]: 'Connecting...',
    [STATUS.READY]:  'Connected',
  }

  return {
    restrict: 'E',
    scope: {},
    template: `
      
{{statusDisplay}}
`, link(scope) { // Set and change scope.statusDisplay here } } })
以下のいくつかの方法を試してみました.以下に紹介します.
方法1:ウォッチ
最初に思いついた方法は、directiveにおいて$watchを用いてreaderService.statusを監視することである.それはdirective scopeの属性ではないので、私たちは関数でそれを包む必要があります.Anglarはdirty-checkeingの時に新旧値を計算して比較することができて、状態が本当に変化が発生しただけで回転をトリガします.
// In directive
link(scope) {
  scope.$watch(() => readerService.status, (status) => {
    scope.statusDisplay = STATUS_DISPLAY[status]
  })
}
このやり方は十分に簡単で効率的で、readerService.statusの変更されたコードに触れるとdirty-chectingをトリガし、directiveは自動的に更新されます.serviceはコードを変更する必要はありません.
しかし、複数のdirectiveの属性がservice statusの影響を受けると、$watchコードは見づらいです.特に$watchで修正された値が他の値に影響を与える場合.たとえば:
// In directive
link(scope) {
  scope.$watch(() => readerService.status, (status) => {
    scope.statusDisplay = STATUS_DISPLAY[status]
    scope.showBattery = status !== STATUS.DETACH
  })

  scope.$watch('showBattery', () => {
    // some other things depend on showBattery
  })
}
このような時は声明のようなプログラミングのスタイルが分かりやすくなります.例えば、EmberやVueの中のcomputed property.これは後で検討します.
方法2:$broadcast/emit+$on
この考え方はserviceが状態が変わるたびに一つのイベントを送信し、directive傍受イベントが状態を変えます.directiveでレンダリングした時にはstatusが更新されているかもしれません.したがって、linkで初期値を計算する必要があります.
最初は$broadcastでやりました.コードは以下の通りです
// In service
setStatus(value) {
  this.status = value
  // Need to inject $rootScope
  this.$rootScope.$broadcast('reader.statusChanged', this.status)
}

// In directive
link(scope) {
  scope.statusDisplay = STATUS_DISPLAY[nfcReaderService.status]

  scope.$on('reader.statusChanged', (event, status) => {
    scope.statusDisplay = STATUS_DISPLAY[status]
  })
}
しかし、すぐに$broadcastが発見された後、UIの更新はいつも1秒以上待つ必要があります.Googleが最初に知ったのは、$onが下の階にあるすべてのscopeに放送し、放送が終わったらdirty-checckingを放送するからです.より良い方法は、$broadcastを使用して、イベントを上にだけ送ることですが、イベントを送信するにも、傍受するにも、$emitを使用します.
修正後のコードは以下の通りです.
// In service
setStatus(value) {
  this.status = value
  // Use $emit instead of $broadcast
  this.$rootScope.$emit('reader.statusChanged', this.status)
}

// In directive
link(scope) {
  scope.statusDisplay = STATUS_DISPLAY[nfcReaderService.status]

  // Use $rootScope instead of scope
  $rootScope.$on('reader.statusChanged', (event, status) => {
    scope.statusDisplay = STATUS_DISPLAY[status]
  })
}
いくつかの理由で$rootScopeを使用しなければならないならば、$broadcastで最後に$onまたは$digestでディrty-checkingを強制的にトリガしても良いし、これも高速更新UIの目的に達することができる.
方法3:controller+property
個人的には前の二つの方法は問題を解決できると思いますが、コードの維持性はあまりよくないです.$applyは、属性が相互に関連している場合には分かりにくいです.$watchはいくつかの論理を2回書く必要があります.方法の一つでは、いくつかの場合、ステートメントの属性は$emit/$onよりも分かりやすいと述べました.この方法はcontrollerを使うことです.directiveは自分のcontrolerをデータソースとして設定できます.それらの計算が必要な属性をcontrolerの属性として使用できます.このようにdirty-checkingは自動的に計算されます.
// In directive
class ReaderController {
  constructor($scope, readerService) {
    this.readerService = readerService
  }

  get statusDisplay() {
    return STATUS_DISPLAY[this.readerService.status]
  }
}

return {
  // ...
  controller: ReaderController,
  controllerAs: 'vm',
  template: `
    
{{vm.statusDisplay}}
` }
このようにして、ほとんどのロジックはcontrolerに移動することができる.もしDOM操作がなかったら、$watch方法を書かなくてもいいです.追加のlinkおよび$watchに参加する必要もない.ただdirty-checkingの特性のため、templateの属性に結びつけられて、しばしば何回か計算されます.したがって、属性は非常に簡単でなければなりません.ほとんどの場合は問題ないです.
参考資料
$rootScope.Scope Anglar APIは、$on$watch$broadcast$emitの中の説明を見ることができます.
$rootScope.$emit()$rootScope.$broadcast()$on$emitの性能比較.後のAnglarは性能の違いを解決しました.