上一章節,給你們回顧了一下AngularJS指令參數的基礎使用。若是有紕漏,歡迎你們給我留言,相互探討探討。html
指令化,其實本質就是代碼的通用化與模塊化,AngularJS的指令化工做,將邏輯與DOM都結合在一塊兒,可以作到即插即用,與Asp的Component是類似的概念。前端
要作到模塊化,必要的要求就是通用代碼與業務代碼的解耦。而解耦並不表明徹底的隔絕,解耦要作的是,通用模塊與業務模塊的隔離,同時也保留接口提供二者通信。segmentfault
囉嗦,趕忙實例吧!後端
某天,產品老大壓下來需求,要作個學生信息填寫卡,說白了就是一個表單編輯器,so easy,前端單身狗拍拍腦殼立刻開工。app
<body> <div ng-controller='demoCtrl'> <div class="panel"> <h3>student card</h3> <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> <p>......</p> <button class="btn btn-info" type="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 = function () { //處理性別變動 };
又隔了一天,產品老大終於定稿,這個學生信息填寫卡,要應用到demo頁、demo1頁、demo2頁上,而3個頁面保存功能指向的後端接口都不同。函數
前端單身狗:雖然不至於問候您大爺,但至少要好好考慮如何實現才省功夫吧。難道要把demoCtrl的相關代碼都copy到另外兩個頁面嗎?這個時候,只要是寫過代碼的同志都會say no吧!oop
趕忙指令化。post
首先,把信息卡的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> var app = angular.module("app", []); app.directive('student', function () { return { restrict: 'E', scope: {}, template: function (elem, attr) { return document.getElementById('t1').innerHTML; }, controller:function($scope){ //這裏省略信息卡交互邏輯…… } }; }); </script>
雖然獨立了信息卡代碼,但有兩個問題是顯而易見的
隔離之後,指令內部如何得到3個demoCtrl的stu?
保存按鈕又是如何調用外部3個demoCtrl不一樣的save方法?
問題其實指明瞭解決思路,student須要兩個接口與外部通信:scope.stu
& scope.save()
爲你們介紹3種通信方案。
指令scope {}參數,能夠徹底隔離做用域,可是也預留了3種綁定策略,實現子域與父域通信。
爲指令創建兩個通信接口,stu
採起=
雙向綁定父域對象的策略;而onSave
則採起$
反向調用父域函數策略。
scope: { stu: '=', //=爲雙向綁定策略 onSave: '&' //$反向調用父域函數策略 },
優勢:簡單、簡潔
缺點:
通信接口要求比較多、複雜的狀況下,指令scope {}要配置的綁定策略也比較多;
形成指令與指令之間的通信容易混亂;
指令內部好像沒有辦法,往onSave()
函數裏面傳參,這個不肯定,求助你們。
DOM寫法:
<student stu="guy" on-save="saveEditing()"></student>
完整例子:
<body> <div ng-controller='demoCtrl'> <!--指令scope.stu雙向綁定demoCtrl scope.guy--> <!--指令scope.onSave函數指向demoCtrl scope.saveEditing()--> <student stu="guy" on-save="saveEditing()"></student> </div> <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> var app = angular.module("app", []); app.controller('demoCtrl', function ($scope) { $scope.guy = { name: "mark", sexy: 1 }; $scope.saveEditing = function () { //$http.post("...", { stu: $scope.guy }); console.log($scope.guy.name + " is saving!"); console.log($scope.guy); }; }); app.directive('student', function () { return { restrict: 'E', scope: { stu: '=', //=爲雙向綁定策略 onSave: '&' //$反向調用父域函數策略 }, template: function (elem, attr) { return document.getElementById('t1').innerHTML; }, controller: function ($scope, $element, $attrs, $transclude) { //這裏省略信息卡交互邏輯…… } }; }); </script> </body>
效果:
ngModel,這個內置指令相信你們都不會陌生。自定義指令引用其自身的ngModel指令,其原理就是:
父域對象 綁定 ngModel
ngModel 綁定 指令子域對象
優勢:能夠充分利用ngModel的特性,例如commit、rollback等特性,也能夠搭配ng-model-options
進行使用。
缺點:
相對於方案1,父子對象綁定中間還要多一層ngModel的綁定,性能必然下降;
相對於方案1,使用比較麻煩;
自身不能實現反向調用父域函數,須要藉助$parse轉換表達式方案實現
DOM寫法:
<student ng-model="guy" on-save="saveEditing()"></student>
完整例子:
<body> <div ng-controller='demoCtrl'> <student ng-model="guy" on-save="saveEditing()"></student> </div> <script type="text/html" id="t1"> <div class="panel"> <p> <span>name:</span> <input type="text" ng-model="ngModel.$modelValue.name" /> </p> <p> <span>sexy :</span> <select ng-model="ngModel.$modelValue.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> var app = angular.module("app", []); app.controller('demoCtrl', function ($scope) { $scope.guy = { name: "mark", sexy: 1 }; $scope.saveEditing = function () { //$http.post("...", { stu: $scope.guy }); console.log($scope.guy.name + " is saving!"); console.log($scope.guy); }; }); app.directive('student', ["$parse", function ($parse) { return { restrict: 'E', require: ['student', 'ngModel'], scope: {}, template: function (elem, attr) { return document.getElementById('t1').innerHTML; }, link: function (scope, element, attr, ctrls) { //link階段,經過require獲取指令自身的控制器,及ngModel指令的控制器 var stCtrl = ctrls[0]; var ngModelCtrl = ctrls[1]; //並將ngModel指令的控制器,經過自身控制器的init()方法傳入到其中 stCtrl.init(ngModelCtrl); }, controller: function ($scope, $element, $attrs, $transclude) { //建立controller的對外初始化方法,並將外部ngModel的控制器設置本地做用域對象 this.init = function (ngModelCtrl) { $scope.ngModel = ngModelCtrl; }; //得到on-save屬性指向的表達式{{saveEditing()}} var saveInvoker = $parse($attrs.onSave); $scope.onSave = function () { //在父域中,執行表達式{{saveEditing()}}——執行父域saveEditing() saveInvoker($scope.$parent, null); }; } }; }]); </script> </body>
在ngModel的解決方案中,已經有過經過$parse獲取執行表達式操做父域函數的例子:$parse($attrs.onSave)
。這個方案不只可以執行父域函數表達式,同時也可以執行對象的get/set表達式。可是本方案本質實際上是對於父域$scope.$parent
的直接操做,只是經過$parse服務實現解耦。
關於$parse服務,想了解更多,請移步——
http://segmentfault.com/a/1190000002749571
<body> <div ng-controller='demoCtrl'> <student stu="guy" on-save="saveEditing()"></student> </div> <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> var app = angular.module("app", []); app.controller('demoCtrl', function ($scope) { $scope.guy = { name: "mark", sexy: 1 }; $scope.saveEditing = function () { //$http.post("...", { stu: $scope.guy }); console.log($scope.guy.name + " is saving!"); console.log($scope.guy); }; }); app.directive('student', ["$parse", function ($parse) { return { restrict: 'E', scope: {}, template: function (elem, attr) { return document.getElementById('t1').innerHTML; }, controller: function ($scope, $element, $attrs, $transclude) { 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, function (stu) { $scope.stu = stu; }); $scope.onSave = function () { //在父域中,執行表達式{{guy}} assign——將本地對象stu設置到父域guy setStudent($scope.$parent, $scope.stu); //在父域中,執行表達式{{saveEditing()}}——執行父域saveEditing() saveInvoker($scope.$parent, null); }; } }; }]); </script> </body>
效果:
說了一大堆,你們仍是動手試試哪一個方案更合適你的項目,或者有更好的開發思路。
下一章節,爲你們介紹一下關於指令的自動化測試。歡迎繼續捧場。