指令化,其实本质就是代码的通用化与模块化,AngularJS的指令化工作,将逻辑与DOM都结合在一起,能够做到即插即用,与Asp的Component是相似的概念。
要做到模块化,必要的要求就是通用代码与业务代码的解耦。而解耦并不代表完全的隔绝,解耦要做的是,通用模块与业务模块的隔离,同时也保留接口提供两者通讯。
啰嗦,赶紧实例吧!
某天,产品老大压下来需求,要做个学生信息填写卡,说白了就是一个表单编辑器,so easy,前端单身狗拍拍脑袋马上开工。
<body>
<div ng-controller='demoCtrl'>
<class="panel">
<h3>student card</h3>
<p>
<span>name:</span>
<input type="text" ng-model="stu.name" />
</p>
<span>sexy :</select "stu.sexy">
<option value="1">male</option>
<"2">female</option>
</select>
</p>......</button "btn btn-info" "button" ng-click="saveEditing()">save</button>
</div>
</div>
<script> var app = angular.module("app",[]); app.controller('demoCtrl',function ($scope) { $scope.stu = { name: "mark",sexy: 1 }; $scope.saveEditing = function () { //$http.post("...",{ stu: $scope.guy }); console.log($scope.guy.name + " is saving!"); console.log($scope.guy); }; //这里省略信息卡交互逻辑…… }); </script>
</body>
出来结果,还行。
隔了一天,产品老大想了想,要增强体验,在选择性别的时候,名称要根据男女颜色变化;输入名称的时候,要自动匹配近似名称,并且首字母大写。行,改呗。demoCtrl
加上以下逻辑
$scope.nameChange = function () {
//处理名称变更
};
$scope.sexyChange = //处理性别变更
};
又隔了一天,产品老大终于定稿,这个学生信息填写卡,要应用到demo页、demo1页、demo2页上,而3个页面保存功能指向的后端接口都不一样。
前端单身狗:虽然不至于问候您大爷,但至少要好好考虑如何实现才省功夫吧。难道要把demoCtrl的相关代码都copy到另外两个页面吗?这个时候,只要是写过代码的同志都会say no吧!
赶紧指令化。
隔离
首先,把信息卡的DOM结构独立开来,创建指令student
。指令scope参数要设为{}
,如果设为false
、true
达不到完全隔离的效果哦,不理解原因的童鞋请回顾上一章节。
<div ng-controller='demoCtrl'>
<student></student>
</div>
<div ng-controller='demo1Ctrl'>
<student></student>
</div>
<div ng-controller='demo2Ctrl'>
<student></student>
</div>
<!--信息卡DOM模板->
<script type="text/html" id="t1">
<div class="panel">
<p>
<span>name:</span>
<input type="text" ng-model="stu.name" />
</p>
<p>
<span>sexy :</span>
<select ng-model="stu.sexy">
<option value="1">male</option>
<option value="2">female</option>
</select>
</p>
<button class="btn btn-info" type="button" ng-click="onSave()">save</button>
</div>
</script>
<script>
[]); app.directive('student',function () { return { restrict: 'E',scope: {},template: function (elem,attr) { return document.getElementById('t1').innerHTML; },controller:function($scope){ //这里省略信息卡交互逻辑…… } }; }); </script>
虽然独立了信息卡代码,但有两个问题是显而易见的
问题其实指明了解决思路,student需要两个接口与外部通讯:scope.stu
&scope.save()
通讯
为大家介绍3种通讯方案。
scope {}
指令scope {}参数,可以完全隔离作用域,但是也预留了3种绑定策略,实现子域与父域通讯。
为指令建立两个通讯接口,stu
采取=
双向绑定父域对象的策略;而onSave
则采取$
反向调用父域函数策略。
scope: { stu: '=',//=为双向绑定策略 onSave: '&' //$反向调用父域函数策略 },
优点:简单、简洁
缺点:
-
通讯接口要求比较多、复杂的情况下,指令scope {}要配置的绑定策略也比较多;
-
造成指令与指令之间的通讯容易混乱;
-
指令内部好像没有办法,往
onSave()
函数里面传参,这个不确定,求助大家。
DOM写法:
student stu="guy" on-save="saveEditing()"></student>
完整例子:
'demoCtrl'>
<!--指令scope.stu双向绑定demoCtrl scope.guy-->
<!--指令scope.onSave函数指向demoCtrl scope.saveEditing()-->
<student>
</script "text/html" id="t1"> <div class="panel"> <p> <span> <"stu.name" /> </"stu.sexy"> <option> <option> </select> </"onSave()">save</button> </div> </script>
<function ($scope) { $scope.guy = { name: console.log($scope.guy); }; }); app.directive('student',0)">function () { return { restrict: 'E',scope: { stu: '=',//=为双向绑定策略 onSave: '&' //$反向调用父域函数策略 },template: function (elem,attr) { return document.getElementById('t1').innerHTML; },controller: function ($scope,$element,$attrs,$transclude) { //这里省略信息卡交互逻辑…… } }; }); </ 效果:
ngModel
ngModel,这个内置指令相信大家都不会陌生。自定义指令引用其自身的ngModel指令,其原理就是:
-
父域对象绑定ngModel
-
ngModel绑定指令子域对象
优点:可以充分利用ngModel的特性,例如commit、rollback等特性,也可以搭配ng-model-options
进行使用。
缺点:
"ngModel.$modelValue.name"
/> </"ngModel.$modelValue.sexy"> <"$parse",0)">function ($parse) { require: ['ngModel'],link: function (scope,element,attr,ctrls) { //link阶段,通过require获取指令自身的控制器,及ngModel指令的控制器 var stCtrl = ctrls[0]; var ngModelCtrl = ctrls[1]; //并将ngModel指令的控制器,通过自身控制器的init()方法传入到其中 stCtrl.init(ngModelCtrl); },161)">//创建controller的对外初始化方法,并将外部ngModel的控制器设置本地作用域对象 this.init = function (ngModelCtrl) { $scope.ngModel = ngModelCtrl; }; //获得on-save属性指向的表达式{{saveEditing()}} var saveInvoker = $parse($attrs.onSave); $scope.onSave = //在父域中,执行表达式{{saveEditing()}}——执行父域saveEditing() saveInvoker($scope.$parent,null); }; } }; }]); </body>
$parse
在ngModel的解决方案中,已经有过通过$parse获取执行表达式操作父域函数的例子:$parse($attrs.onSave)
。这个方案不仅能够执行父域函数表达式,同时也能够执行对象的get/set表达式。但是本方案本质其实是对于父域$scope.$parent
的直接操作,只是通过$parse服务实现解耦。
关于$parse服务,想了解更多,请移步——
http://segmentfault.com/a/1190000002749571
var getClassName,setClassName,saveInvoker = angular.noop; //注意:建议查阅一下$parse内置表达式转换函数的使用方法。 //获得stu属性指向的表达式{{guy}} getStudent = $parse($attrs.stu); setStudent = getStudent.assign; //获得on-save属性指向的表达式{{saveEditing()}} saveInvoker = $parse($attrs.onSave); //监听父域的{{guy}} $scope.$parent.$watch(getStudent,0)">function (stu) { $scope.stu = stu; }); $scope.onSave = //在父域中,执行表达式{{guy}} assign——将本地对象stu设置到父域guy setStudent($scope.$parent,$scope.stu);
End
原文链接:https://www.f2er.com/angularjs/148953.html