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,希望对大家有所帮助!