Anglarはserviceの状態によってdirectiveを更新します.
5070 ワード
TL;DR
この文章はserviceの状態によってdirectiveを更新する3つの方法を説明しています.それぞれ
問題
サービスのコードは以下の通りです.
方法1:ウォッチ
最初に思いついた方法は、directiveにおいて
しかし、複数のdirectiveの属性がservice statusの影響を受けると、
方法2:$broadcast/emit+$on
この考え方はserviceが状態が変わるたびに一つのイベントを送信し、directive傍受イベントが状態を変えます.directiveでレンダリングした時にはstatusが更新されているかもしれません.したがって、
最初は
修正後のコードは以下の通りです.
方法3:controller+property
個人的には前の二つの方法は問題を解決できると思いますが、コードの維持性はあまりよくないです.
参考資料
$rootScope.Scope Anglar APIは、
$rootScope.$emit()$rootScope.$broadcast()
この文章は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は性能の違いを解決しました.