Angular装饰器的应用
angular.js官方网站上有关于decorator
的文档,点击这里查看。干的事情很简单,就是注册一个service的装饰器,支持依赖注入;通过他允许我们去覆盖或者修改service的原本功能。
项目中有一个这样的场景,需要路由发生改变,但是呢,改变之后的路由和当前的路由除了url不同,其他的都是相同的,也就意味着controller是一样的,得到的模板内容是一样的。在这种场景下,希望的是路由会发生变化,但是不希望ng-view
的元素不发生变化。你可能会说,直接用search或者hash来更改地址多好,再配合reloadOnSearch:false
不就万事大吉了吗,事实是不符合老板要求。。。所以这里需要一种方案来解决这个问题。
既然只需要对ng-view
部分做修改,他是一个指令,而指令在angular中其实也是按照service处理的。对于ng-view
指令而言,其实就是通过$provide.factory
注册了一个服务名字为ngViewDirective
,由此想到能不能修改下这个指令定义的默认逻辑呢?答案是肯定的,通过decorator
就可以做到。
这个需求可以换个说法,也就是在路由定义的时候,多指定一个参数state
,值是一个字符串名字,在发生改变的时候,判断下当前的这个值和上次的值是否相同,且顺便判断下template
和controller
是否一样;如果都为true
的话,就认为不需要更新ng-view
元素了。
所以说下面就是实现部分了:
angular.module('ngRoute').decorator('ngViewDirective', ['$route', '$delegate', function ($route, $delegate) {
// 保留原link函数
var oldL = $delegate[0].link;
// 修改compile值
$delegate[0].compile = function () {
var lastR = null;
var el;
// 新的link
return function (scope, $element, attr, ctrl, $transclude) {
// 代理的$transclude函数
var trs = function (scope, cb) {
var current = $route.current;
if (lastR && current && lastR.state &&
lastR.state === current.state &&
lastR.controller === current.controller &&
lastR.locals.$template === current.locals.$template) {
// 返回保留的el
return el;
}
lastR = current;
el = $transclude(scope, cb);
return el;
};
trs.$$boundTransclude = $transclude.$$boundTransclude;
// 调用原始的link
oldL(scope, $element, attr, ctrl, trs);
}
};
// 把原service实例返回
return $delegate;
}]);
注意上边的一些细节:
-
为什么是直接修改的
$delegate[0].compile
的值,而不是直接修改其link
值呢?最开始的时候我也是这样想的,但是发现无效,为什么呢?后来想到了在指令处理过程中不会去判断link
,而是去判断的compile
,也就是说在angular的指令处理中,如果你写了link
而没有写compile
的话,angular会给你造一个compile
,然后返回你定义的link
。 -
可以看到核心是把调用
link
时的最后一个参数$transclude
给代理代理掉了,然后在其中根据上次的route信息和当前的route信息来判断是否需要调用真正的$transclude
得到新的内容。 -
判断模板是否相同的时候,采用的是
locals.$template
,这是为了处理$templateUrl
的情况,得到$templateUrl
的模板内容后会给locals.$template
赋值。
这样就实现了需求,有state
且上次的route信息和当前的route信息中的state
是相同的,然后再加上controller
和$template
的判断就可以了。
使用的话很简单:
$routeProvider
.when('/a', {
template: 'a',
controller: 'aa',
state: 'a'
})
.when('/b', {
template: 'a',
controller: 'aa',
state: 'a'
})
.when('/c', {
template: 'c',
controller: 'cc'
})
.otherwise('/a')
上边的配置,默认路由是/a
,假设现在路由更新为/b
,那么由于/a
和/b
的路由配置中state
相同都是a
,且template和controller都是相同的,所以说此时不会创建新的ng-view
,当然controller也不会重新实例化;然后路由更新到/c
,一切回归正常的默认行为,内容发生更新。
decorator
还是很有用的,把它用在合适的场景(虽然这个场景是不大合理的)会提供一种解决问题的新思路!