Angular 依据 service 的状况更新 directive

TL;DR

这篇文章讲解了三种依据 service 的状况更新 directive 的做法。分别是 $watch 表达式,事宜通报,和 controller 的盘算属性。

题目

我有一个 readerService ,个中包括一些状况信息(比方衔接状况和电量)。如今我须要做一个 directive 去展现这些状况。由于它只须要从 readerService 中猎取数据,不须要任何外部传值,所以我直接把 service 注入进去。但怎样更新就成了一个题目。

service 的代码以下。

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: `
      <div class="status">
        {{statusDisplay}}
      </div>
    `,
    link(scope) {
      // Set and change scope.statusDisplay here
    }
  }
})

我尝试过以下几种要领,下面逐一引见。

要领一:$watch

第一个想到的要领就是在 directive 顶用 $watch 去看管 readerService.status。由于它不是 directive scope 的属性,所以我们须要用一个函数来包裹它。Angular 会在 dirty-checking 时盘算和比较新旧值,只需状况真的发生了转变才会触发还调。

// In directive
link(scope) {
  scope.$watch(() => readerService.status, (status) => {
    scope.statusDisplay = STATUS_DISPLAY[status]
  })
}

这个做法充足简朴高效,只需触及 readerService.status 转变的代码会触发 dirty-checking ,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 。这个待会议论。

要领二:$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 秒多(不过 $on 回调却是很快)。Google 一番后晓得缘由是 $broadcast 是向基层一切 scope 播送,播送完成后再 dirty-checking 。一个更好的做法是运用 $emit ,它只会向上通报事宜,不过不论发送事宜照样监听事宜都得用 $rootScope

修正后的代码以下:

// 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]
  })
}

假如由于某些缘由不得不必 $broadcast 的话,你能够在 $on 回调最后用 $digest$apply 强迫触发 dirty-checking ,这也能够到达疾速更新 UI 的目标。

要领三:controller + property

我个人以为前两个要领能处理题目,但代码保护性都不太好。$watch 在属性互相关联的情况下异常难看懂,$emit/$on 须要把一些逻辑写两次(初始化 directive 时和回调执行时)。要领一中我提到了有些时刻声明式的属性比 $watch 更轻易看懂。这个要领就是运用 controller 。directive 能够设置本身的 controller 作为数据泉源(或者说 view model),我们能够把那些须要盘算的属性作为 controller 的属性。如许 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: `
    <div class="status">
      {{vm.statusDisplay}}
    </div>
  `
}

如许一来,大部分逻辑都能够挪到 controller 中。假如没有 DOM 操纵我们以至能够不写 link 要领。也没必要到场分外的 $watch$on 。只是由于 dirty-checking 的特征,绑定到 template 的属性往往会多盘算频频。所以属性必需异常简朴。大部分情况下这不会有什么题目。

参考资料

$rootScope.Scope
Angular API ,能够看看内里对 $watch$broadcast$emit , $on 的形貌。

$rootScope.$emit() vs $rootScope.$broadcast()
$emit$broadcast 的机能比较。注重厥后的 Angular 已处理了机能差别,二者相差无几。

    原文作者:darkbaby123
    原文地址: https://segmentfault.com/a/1190000004845354
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞