「控制器應該儘量保持短小精悍,而在控制器中進行DOM操做和數據操做則是一個很差的實踐。設計良好的應用會將複雜的邏輯放到指令和服務中。經過使用指令和服務,咱們能夠將控制器重構成一個輕量且更易維護的形式」 ----《AngularJs權威教程》
Directive中文翻譯爲指令,從《AngularJs權威教程》中對指令的描述,我理解的指令是對控制器的補充,主要功能是對Dom元素和數據的操做(通常指令主Dom操做、服務主數據操做,非強制規定),本文基於Angular1.4.6版本進行講解和演示。css
在Angular中內置了一些指令,如常見的ng-app
指令初始化一個 AngularJS 應用程序,ng-model
指令把元素值(好比輸入域的值)綁定到應用程序。同時Angular也支持建立自定義指令。html
基礎案例展現。網絡
Html代碼:app
<!DOCTYPE html> <html ng-app="myApp"> <head> <meta charset="utf-8"> <script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body ng-controller="myCtrl"> <!-- 引入指令 --> <my-dir></my-dir> </body> </html>
Js代碼:函數
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { template: "<h1>這是自定義指令</h1>", restrict: "E" } });
在線代碼演示post
這是一個很簡單的指令小案例,指令中的template
模板內容「這是自定義指令」經過<my-dir>
標籤引入到頁面中,涉及到指令兩個很重要的參數template
和restrict
,下面會具體講解指令的參數。ui
參數 | 類型 |
---|---|
restrict | String |
priority | Number |
terminal | Boolean |
template | String or Template Function |
templateUrl | String or Template Function |
replace | Boolean or String |
transclude | Boolean |
scope | Boolean or Object |
controller | String or function(scope, element, attrs, transclude, otherInjectables) { ... } |
controllerAs | String |
require | String |
link | function(scope, iElement, iAttrs) { ... } |
compile | function(tElement, tAttrs, transclude) { return { pre: function(scope, iElement, iAttrs, controller) { ... }, post: function(scope, iElement, iAttrs, controller) { ... } } return function postLink(...) { ... } } |
已上是指令的全部參數以及類型,重點介紹經常使用參數restrict、scope、replace、template、templateUrl、link
。spa
restrict用於指定directive的使用形式。如上文例子指令代碼中的restrict: "E"
即指名使用Element元素形式,同時還有A、C、M
共四種形式。默認值爲EA
。翻譯
參數值 | 形式 | 示例 |
---|---|---|
E | 元素 Element | <my-dir></my-dir> |
A | 屬性 Attribute | <div my-dir></div> |
C | 樣式 Class | <div class="my-dir"></div> |
M | 註釋 Comment | <!-- directive:my-dir --> |
Html代碼:設計
<body> <!-- 元素形式引入指令 --> <my-dir></my-dir> <!-- 屬性形式引入指令 --> <div my-dir></div> <!-- 樣式形式引入指令 --> <div class="my-dir"></div> <!-- 指令形式引入指令 --> <!-- directive: my-dir --> </body>
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { template: "<h1>這是自定義指令</h1>", restrict: "ECMA" } });
指令命名需使用駝峯模式,Html引入指令需使用「-」鏈接每一個單詞,如指令myDir
在引入指令時改成my-dir
寫法。E、A、C、M
四種形式可組合使用。
說明:
在線代碼演示中註釋引入<!-- directive: my-dir -->
沒有成功引入指令內容,這是因爲在線codepen不支持,放到本身IDE中是沒問題的。
scope是指令的做用域,每一個指令建立後能夠繼承父做用域(外部controller
提供的做用域或根做用域$rootScope
),也能夠擁有本身獨立的做用域,scope
參數有三種,分別是:false、true、{}
,默認爲false
。
當scope
設置爲false
時,指令模板中能夠直接使用父做用域中得變量和函數,舉個例子:
Html代碼:
<div ng-app="MyApp"> <div class="container" ng-controller="MyController"> <div class="my-info">個人名字是:<span ng-bind="name"></span> <!-- 使用"ng-bind"防止網絡狀態不佳時出現沒有被賦值表達式 --> <br/>個人年齡是:<span ng-bind="age"></span> </div> <!-- 使用屬性聲明指令 --> <div class="my-dir" my-dir></div> </div> </div>
Js代碼:
angular.module("MyApp", []) .controller("MyController", function ($scope) { // 這裏咱們在做用域裏初始化兩個變量 $scope.name = "echeverra"; $scope.age = 20; // 建立一個方法,修改咱們建立的對象的年齡 $scope.changeAge = function () { $scope.age = 22; } }) // 建立咱們的指令,指令名字爲"myDir" .directive("myDir", function () { var obj = { // 指令的聲明模式爲 "AE" 屬性和元素 restrict: "AE", // 指令繼承父做用域的屬性和方法 scope: false, replace: true, template: "<div class='my-directive'>" + "<h3>下面部分是咱們建立的指令生成的</h3>" + "個人名字是:<span ng-bind='name'></span><br/>" + "個人年齡是:<span ng-bind='age'></span>" + "<input type='text' ng-model='name'>"+ " </div>" } return obj; });
從在線代碼演示效果中咱們看到建立的指令繼承了父做用域controller
中的屬性name、age
,一樣也繼承了方法changeAge
。當改變指令模塊中input
的name
會發現父做用域controller
中的name
也會發生變化,這說明指令的做用域和父做用域在同一個做用域下。
當scope
參數改成true
時,咱們再來看:
一樣指令中繼承了父做用域的屬性和方法,不一樣的是,當修改指令input
的name
時,指令的name
對應改變,父做用域controller
中的name
並未發生改變,這說明指令的做用域和父做用域不在同一個做用域下。
當scope
設置爲{}
會使指令的做用域變得更加靈活,修改以前的代碼:
Html代碼:
<div ng-app="MyApp"> <div class="container" ng-controller="MyController"> <div class="my-info">個人名字是:<span ng-bind="name"></span> <br/>個人年齡是:<span ng-bind="age"></span> <br /> </div> <div class="my-dir" my-dir my-name="{{name}}" age="age" change-my-age="changeAge()"></div> </div> </div>
Js代碼:
angular.module("MyApp", []) .controller("MyController", function ($scope) { $scope.name = "echeverra"; $scope.age = 20; $scope.male = 'man'; $scope.changeAge = function(){ $scope.age = 0; } }) .directive("myDir", function () { var obj = { restrict: "AE", scope: { name: '@myName', age: '=', changeAge: '&changeMyAge' }, replace: true, template: "<div class='my-dir'>" + "<h3>下面部分是咱們建立的指令生成的</h3>" + "個人名字是:<span ng-bind='name'></span><br/>" + "個人性別是:<span ng-bind='male'></span><br/>" + "個人年齡是:<span ng-bind='age'></span><br/>" + "在這裏修更名字:<input type='text' ng-model='name'><br/>" + "<button ng-click='changeAge()'>修改年齡</button>" + " </div>" } return obj; });
咱們使用了隔離的做用域,不表明咱們不可使用父做用域的屬性和方法,咱們能夠經過向scope
的{}
中傳入特殊的前綴標識符(即prefix
),來進行數據綁定。
咱們能夠經過前綴標識符@, &, =
應用到指令中的屬性,如:{name: '@myName', age: '=', changeAge: '&changeMyAge'}
(age: '='
是age: '=age'
的簡寫),咱們能夠在<div class="my-directive" my-directive my-name="{{name}}" age="age" change-my-age="changeAge()"></div>
這個元素中,經過使用屬性my-name,age,change-my-age
來引用這些屬性的值。
首先能夠肯定的是scope={}
不會繼承父做用域,由於個人性別項male
值是空的,在scope
中未進行屬性male
綁定。
那麼前綴標識符@, &, =
分別有什麼做用呢?
這是一個單向數據綁定的前綴標識符。在元素中使用屬性,如:<div my-dir my-name="{{name}}"></div>
,屬性的名字要用「-」將兩個單詞鏈接,由於是數據的單向綁定因此要經過使用{{}}
來綁定數據。在線代碼演示結果中修改input
的name
值可看出,只指令的做用域生效name
發生改變,父做用域name
未改變,這點和scope
參數爲true
效果是同樣的。
這是一個雙向數據綁定的前綴標識符。在元素中使用屬性,如:<div my-dir age="age"></div>
,由於是數據的雙向綁定,因此使用=
前綴實現,而不是{{}}
。在線代碼演示結果中修改input
的name
值可看出,指令的做用域和父做用域name
均發生改變,這點和scope
參數爲false
效果是同樣的。
這是一個綁定函數方法的前綴標識符。在元素中使用屬性,如:<div my-dir change-my-age="changeAge()"></div>
,屬性的名字要用「-」將每一個單詞鏈接。在線代碼演示結果中點擊「修改年齡」按鈕,指令做用域和父做用域中的年齡都發生改變,這點和前綴標識符=
效果相同。
注意:
在新建立指令的做用域對象中,使用屬性的名字進行綁定時,要使用駝峯命名標準,以下面的代碼:
scope: { // `myName` 就是原來元素中的`my-name`屬性 name: '@myName', age: '=', // `changeMyAge`就是原來元素中的`change-my-age`屬性 changeAge: '&changeMyAge' }
進一步說明,咱們建立的指令是如何利用這些前綴標識符來尋找咱們想要的屬性或函數的呢?
使用@
時當指令編譯到模板的name
,就會到scope
中查找是否含有name
的鍵值對(name:'@myName'
),找到後發現前綴標識符@
就知道這是一個單向數據綁定標識符,以後會去尋找元素上含有這個值的屬性(my-name={{name}}
),而後在父做用域中查找{{name}}
的值,獲得以後傳遞給屬性my-name
,經過指令參數scope
中鍵值對name:'@myName'
最後傳給了模板中的name
。(此處比較繞,不過真心值得研究一番,有助於深入理解)。=
和&
與@
差很少,只不過=
進行的是雙向的數據綁定,不論模板仍是父做用域上的屬性的值發生改變都會使另外一個值發生改變,而&
是綁定函數而已。
總結: scope = false
,scope = true
初始化都會繼承父做用域中的屬性和方法,不一樣的是scope = false
和父做用域在同一做用域下,scope = true
和父做用域不在同一做用域,而是建立了一個獨立的做用域。scope = {}
不會繼承父做用域,屬性控制更加靈活,單向綁定@
,外部scope
可以影響內部scope
,但反過來不成立,雙向綁定=
,外部scope
和內部scope
的model
可以相互改變,函數綁定&
把內部scope
的函數和外部scope
的函數綁定起來。
template
參數值能夠是一個字符串也能夠是一個函數,值爲字符串比較好理解,上文一直在演示的就是值爲字符串的形式。當值爲函數時,可接收兩個參數element
和attrs
。舉例:
Html代碼:
<!-- 引入指令 --> <my-dir name="echeverra" age="20" title="指令元素"></my-dir>
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { template: function(element, attrs) { return '<h2>name:'+attrs.name+'</h2>'+ '<h2>age:'+attrs.age+'</h2>'+ '<h2>title:'+attrs.title+'</h2>' }, restrict: "E" } });
函數參數element
是指使用此指令的元素<my-dir name="echeverra" age="20" title="指令元素"></my-dir>
,而attrs
是指令元素上全部屬性的集合,形如:
{ name: 'echeverra', age: '20', title: '指令元素' }
templateUrl
和上一個參數template
用法基本相同,只不過template
值爲字符串是一段html模板,templateUrl
值爲字符串是文件路徑。如:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { templateUrl: 'index.html'; } });
具體使用請參考參數template
。
replace
是一個可選參數,默認值爲false
,表示指令中模板template
內容會當作子元素插入到指令元素中,若是設置爲true
,表示指令中模板會直接替換掉指令元素。
舉例replace
爲false
:
Html代碼:
<div id="dir"> <!-- 引入指令 --> <my-dir></my-dir> </div> <button ng-click='getDirCont()'>獲取引入指令元素</button> <div ng-bind="dirCont"></div>
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) { $scope.getDirCont = function() { $scope.dirCont = document.getElementById('dir').innerHTML; } }) .directive("myDir", function() { return { template:"<h1>這是自定義指令</h1>", restrict: "E", replace: false, } });
點擊獲取引入指令元素按鈕後顯示<my-dir><h1>這是自定義指令</h1></my-dir>
,能夠看到指令元素<my-dir></my-dir>
還在,若是將replace
設置爲true
,咱們在來看:
此次獲取只顯示元素<h1>這是自定義指令</h1>
,說明指令元素<my-dir></my-dir>
被替換掉了,這就是二者的區別。
當同一個元素聲明兩個指令,須要設定執行前後順序,這時就須要用到參數priority
,值爲數字,默認值爲0,咱們來看下不使用priority
的狀況會有什麼麻煩。
Html代碼:
<!-- 引入指令 --> <div ng-init="greeting='Hello '" d1 d2>{{greeting}}!</div>
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("d1", function() { return { link: function (scope) { scope.greeting += 'World '; } } }) .directive("d2", function() { return { link: function (scope) { scope.greeting += 'Angular '; } } });
輸出:Hello Angular World !
,可見先執行了ng-init="greeting='Hello '"
,而後執行了指令d2
,最後是d1
。實際咱們想預期輸出的是Hello World Angular !
,咱們將指令元素中的d一、d2
順序對調,再將指令中建立的d一、d2
兩個指令順序對調,再試發現輸出的仍是Hello Angular World !
,這是由於Angular
是使用字母順序來肯定連接函數誰先被調用,將d1
改成e1
就會獲得預期的結果,有興趣的能夠嘗試一下。如何設定指令執行優先級?這時候priority
就派上用場了。
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("d1", function() { return { priority: 1, link: function (scope) { scope.greeting += 'World '; } } }) .directive("d2", function() { return { priority: 2, link: function (scope) { scope.greeting += 'Angular '; } } });
輸出:Hello World Angular
。
link
函數主要用於操做Dom
元素,給Dom
元素綁定事件和監聽,link
參數要求聲明一個函數,稱之爲鏈式函數。
寫法:
link: function(scope, element, attrs) { // 在這裏操做DOM }
scope
:指令所在的做用域element
:指令元素的封裝,能夠調用angular封裝的簡裝jq方法和屬性attr
:指令元素的屬性的集合若是指令使用了require選項,那麼連接函數會有第四個參數,表明控制器或者所依賴的指令的控制器。
require 'SomeController', link: function(scope, element, attrs, SomeController) { // 在這裏操做DOM,能夠訪問require指定的控制器 }
下面舉例操做Dom元素,改變元素樣式。
Html代碼:
<!-- 引入指令 --> <input ng-model="color" placeholder="請輸入顏色值"/> <br/> <my-dir name="my-dir-name"></my-dir>
Js代碼:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { template:'<h3 style="background-color:{{color}}">code</h3>', replace:true, link:function(scope, ele, attrs, ctrl) { ele.bind('click', function () { scope.$apply(function () { scope.color = 'red'; ele.text(attrs.name); }) }); ele.bind('mouseover', function () { ele.css({'cursor': 'pointer'}) }); } } });
點擊code
,獲取指令元素的屬性name
值,ele
調用text()
展現,同時ele
綁定bind
鼠標懸浮事件,調用css()
方法,改變鼠標樣式。ele
是指令元素的jqLite包裝,因此能夠基本的jq
方法,如例子中的bind()
,css()
等。
TODO