最近一段时间准备使用AngularJs中的自定义Directive重构一下代码。
在这里说明一下,把自定义控件封装成Directive并不一定是要复用,而是要让代码结构更加清晰。就好像你将一个长方法拆分成多个独立的小方法,也未必要复用它们一样。职责独立等一票好处,会让后期维护更加轻松。
在重构的过程中,我遇到了这样一个问题,先上图:
图一:
这就是我要重构的界面,由于之前时间紧,将这三个Filter和两个button都写在了一个页面中。当时我已经预感到,如果将这里面的状态都写到一个scope上,将出现后期近乎不可维护的灾难。尽管我已经将它们用ViewModel隔离,但代码结构依然让我很不满意。
你说啥?没看出来哪复杂。好,你等着,接下来让你看看里面啥样。
图二:
这是其中一个下拉框展开的模样。什么?你觉得这很一般呐?没看出它多么狰狞?好,你等着。上图三。
图三:
这是三哥展开之后的完全体。现在你应该明白将它们封装成directive是多么必要。
(上图中的这个产品是如今风靡全球的DocAveOnline,使用微软SharePoint的公司没有不知道的。啥?你没听过?偶,忘了说了,只有大公司才用得上)
我重构的思路很简单,将每一个下拉框都独立写成一个directive,然后将哥仨嵌入同一个Directive中,这个外层的directive里面包含Filter和Reset两个Button。
这里原本的交互逻辑是这样的,每个下拉框中的元素都有自己的初始化状态。但是不论你之前怎么霍霍,点击那个Reset按钮之后,要恢复每个下拉框的“出厂设置”。
每个下拉框的Directive拥有独立的scope是肯定的,一切自给自足,与外界无关。也就是说,每个下拉框的reset方法是封装在自己的Directive之中。
那么问题来了,我点击Reset按钮之后,怎么才能调用其子元素中的reset方法?
开始我尝试使用require和link的组合,我是想将reset方法封装到下拉框的controller中,然后在父元素中将三个下拉框对应的directive名字写到require中,这样我就能在父元素的link方法的第四个参数中取到,由这三个子元素controller组成的数组,这我就能轻松调用每个controller的reset方法。(本来controller的作用就是对外暴露API)
可写着写着我就发现不对,好像require只能找自己或者向父元素查找,没法由父元素发起向子元素查找的行动。(也许有,但没找到。如果你知道请在留言中告诉我。)
在江郎才尽的情况下,请教了我师父,他告诉我以下这种方法,其实一点儿都不难,就看你是否理解了自定义directive中,scope里面的,'='这个符号。
稍微懂点儿就知道,这是双向绑定的意思。那解决办法来了,代码如下:
Html部分:
<div ng-controller="myCtrl">
<div>
<inner-directive filter-model="filterModel"></inner-directive>
<button ng-click="filterModel.reset()">click</button>
</div>
</div>
Js部分:
<script>
angular.module('app',[])
.controller('myCtrl',function($scope){
$scope.filterModel = {
name:'xiaomablog'
};
})
.directive('innerDirective',function(){
return {
restrict:'E',
scope:{
filterModel:'='
},
template:'<div ng-bind="filterModel.name"></div>',
link:function(scope,elem,attrs){
scope.filterModel.reset = function(){
console.log('reset');
};
}
}
});
</script>
在innerDirective中的scope属性中,定义了filterModel这个双向绑定的属性,这就意味着directive内部可以霍霍这个属性,外部myCtrl中也可以霍霍这个属性,这样的话彼此都能调用对方霍霍的结果。
我贴心地下了断点,debug给你看:
第一步:
Angular的启动过程是先初始化外部的scope,也就是先执行myCtrl中的方法,给filterModel赋了一个对象;
(注意,此刻filterModel的值为undefined)
第二步:
(没添加reset方法之前,已经看到了之前加的name属性)
然后才执行directive的link方法,将scope与DOM紧紧地联系在一起。
第三步:
此时点击button,已经能调用reset方法了,并在console中打印出'reset'
大家可以尝试在myCtrl和innerDirective的link方法中加断点debug一下,亲自观察执行顺序,加深印象。
注意:
我们了解锤子的方式 , 不是盯着它看 , 而是拿起来用。
—— 马丁·海德格尔
听马丁的话,赶紧去敲。