Angular 根据 service 的状态更新 directive
AngularJS(Angular.JS)是一组用来开发Web页面的框架、模板以及数据绑定和丰富UI组件。它支持整个开发进程,提供web应用的架构,无需进行手工DOM操作。
AngularJS是为了克服HTML在构建应用上的不足而设计的。HTML是一门很好的为静态文本展示设计的声明式语言,但要构建WEB应用的话它就显得乏力了。这里AngularJS就应运而生,弥补了HTML的天然缺陷,用于构件Web应用等。
TL;DR
这篇文章讲解了三种根据service的状态更新directive的做法。分别是$watch表达式,事件传递,和controller的计算属性。
问题
我有一个readerService,其中包含一些状态信息(比如连接状态和电量)。现在我需要做一个directive去展示这些状态。因为它只需要从readerService中获取数据,不需要任何外部传值,所以我直接把service注入进去。但如何更新就成了一个问题。
service的代码如下。
constSTATUS={ DETACH:'DETACH', ATTACH:'ATTACH', READY:'READY' } classReaderService{ constructor(){ this.STATUS=STATUS //Thestatuswillbechangedbysomecallbacks this.status=STATUS.DETACH } } angular.module('app').service('readerService',readerService)
directive代码如下:
angular.module('app').directive('readerIndicator',(readerService)=>{ constSTATUS=readerService.STATUS constSTATUS_DISPLAY={ [STATUS.DETACH]:'Disconnected', [STATUS.ATTACH]:'Connecting...', [STATUS.READY]:'Connected', } return{ restrict:'E', scope:{}, template:` <divclass="status"> {{statusDisplay}} </div> `, link(scope){ //Setandchangescope.statusDisplayhere } } })
我尝试过以下几种办法,下面一一介绍。
方法一:$watch
第一个想到的方法就是在directive中用$watch去监视readerService.status。因为它不是directivescope的属性,所以我们需要用一个函数来包裹它。Angular会在dirty-checking时计算和比较新旧值,只有状态真的发生了改变才会触发回调。
//Indirective link(scope){ scope.$watch(()=>readerService.status,(status)=>{ scope.statusDisplay=STATUS_DISPLAY[status] }) }
这个做法足够简单高效,只要涉及readerService.status改变的代码会触发dirty-checking,directive就会自动更新。service不需要修改任何代码。
但如果有多个directive的属性都受servicestatus的影响,那$watch代码就看得比较晦涩了。尤其是$watch修改的值会影响其他的值的时候。比如:
//Indirective link(scope){ scope.$watch(()=>readerService.status,(status)=>{ scope.statusDisplay=STATUS_DISPLAY[status] scope.showBattery=status!==STATUS.DETACH }) scope.$watch('showBattery',()=>{ //someotherthingsdependonshowBattery }) }
这种时候声明式的编程风格会更容易看懂,比如Ember或Vue里面的computedproperty。这个待会讨论。
方法二:$broadcast/$emit+$on
这种思路是service每次状态改变都发送一个事件,然后directive监听事件来改变状态。因为directive渲染的时候也许status已经更新了。所以我们需要在link中计算一个初始值。
我最开始是用$broadcast去做的。代码如下:
//Inservice setStatus(value){ this.status=value //Needtoinject$rootScope this.$rootScope.$broadcast('reader.statusChanged',this.status) } //Indirective 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。
修改后的代码如下:
//Inservice setStatus(value){ this.status=value //Use$emitinsteadof$broadcast this.$rootScope.$emit('reader.statusChanged',this.status) } //Indirective link(scope){ scope.statusDisplay=STATUS_DISPLAY[nfcReaderService.status] //Use$rootScopeinsteadofscope $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作为数据来源(或者说viewmodel),我们可以把那些需要计算的属性作为controller的属性。这样dirty-checking时它们就会自动计算。
//Indirective classReaderController{ constructor($scope,readerService){ this.readerService=readerService } getstatusDisplay(){ returnSTATUS_DISPLAY[this.readerService.status] } } return{ //... controller:ReaderController, controllerAs:'vm', template:` <divclass="status"> {{vm.statusDisplay}} </div> }
这样一来,大部分逻辑都可以挪到controller中。如果没有DOM操作我们甚至可以不写link方法。也没必要加入额外的$watch和$on。只是因为dirty-checking的特性,绑定到template的属性往往会多计算几次。所以属性必须非常简单。大部分情况下这不会有什么问题。
以上内容是小编给大家介绍的Angular根据service的状态更新directive,希望对大家有所帮助!