準備代碼,會在實例中用到html
var app = angular.module('app', []);
angular指令定義大體以下express
app.directive('directiveName', function() { return { // config } })
其中return返回的配置對象包含不少參數,以下一一說明。數組
restrict
值爲字符串,可選參數,指明指令在DOM中以什麼形式被聲明緩存
<!-- E (element) --> <directiveName></directiveName> <!-- A (attribute) --> <div directiveName="expression"></div> <!-- C (class) --> <div class="directiveName"></div> <!-- M(comment) --> <!--directive:directiveName expression-->
默認值爲restrict: 'EA'
,表示能夠在DOM裏面能夠用元素形式和屬性形式被聲明。通常來講,若是你想建立一個本身模板的組件時,則使用元素形式,可是僅僅是爲已有元素添加功能的話,就使用屬性名。服務器
若是想要支持IE8,則最好使用屬性和類形式來定義,另外從angular 1.3.X開始,已經放棄支持IE8了。app
數字,可選參數,致命指令的優先級,若在單個DOM元素上有多個指令,則優先級高的先執行。cors
固然,設置指令的優先級不太經常使用,可是比較特殊的例子是,內置指令ng-repeat的優先級爲1000,而ng-init的優先級爲 450。異步
布爾型,可選參數,能夠被設置爲true或者false,若設置爲true,則優先級低於此指令的其餘指令則無效,不會被調用優先級相同任然會執行。函數
字符串或者函數,可選參數。post
能夠是一段html文本
app.directive('hello', function() { return { template: '<div><h3>Hello, world!</h3></div>' } })
使用以下
<hello></hello>
渲染結果爲
<hello> <div> <h3>Hello, world!</h3> </div> </hello>
也能夠是一個函數,可接受兩個參數Element與Attrs
其中Element是指使用此指令的元素,而Attrs則是實例的屬性,它是由一個元素上全部屬性組成的集合,形如
{ title: 'test', id: 'testDiv', class: 'demo', input: 'text', ... }
下面讓咱們看看template是一個函數時的狀況
app.directive('hello', function() { return { template: function(element, attrs) { return '<div>'+ attrs.title +'</div>' } }; });
html代碼
<hello title="message"></hello>
渲染結果
<hello> <div>message</div> </hello>
布爾型,默認值爲false,設置爲true的時候,表示能夠用模板內容替換自定義的元素標籤。
在template的例子中,咱們發現渲染結果中包含有自定義的元素<hello></hello>
,很顯然,這並非咱們想要的渲染結果,所以將replace設置爲true以後,hello標籤將會消失
app.directive('hello', function() { return { replace: true, template: function(element, attrs) { return '<div>'+ attrs.title +'</div>' } }; });
渲染結果以下,hello標籤消失不見
<div>message</div>
字符串或者函數,可選參數
可使一個表明html文件路徑的字符串,也能夠是一個函數,大體意思與template同樣。
在本地開發時,須要運行一個服務器,否則使用templateUrl會報錯
Cross origin request script(cors)
因爲加載html模板是經過異步加載,若加載大量的模板會拖慢網站的速度,這裏有一個技巧,就是先緩存模板,你能夠先在你的index頁面加載好,將下列代碼做爲你頁面的一部分包含在內
<script type="text/ng-template" id="demo.html"> <div><!--這裏是模板內容--></div> </script>
這裏的id屬性就是被設置在templateUrl上用的
另一種方法緩存以下
app.run(function($templateCache) { $templateCache.put('template.html', '<div>template</div>'); })
布爾值或者對象,可選參數,默認值爲false,表示繼承父級做用域。
若是值爲true,表示繼承父做用域,並建立本身的做用域(子做用域)
若是爲對象,{}
,則表示建立一個全新的隔離做用域。
首先咱們須要來了解一下scope的繼承機制。咱們使用ng-controller這個指令來舉例。咱們都知道ng-controller能夠從父做用域中繼承並建立一個新的子做用域。以下:
<div ng-app="app" ng-init="aaa='parent'"> parentNode: {{aaa}} <div ng-controller="demoController"> childrenNode: {{aaa}} </div> </div>
angular.module('app', []) .controller('demoController', function($scope) { $scope.aaa = "children"; })
這時頁面的顯示結果爲
parentNode: parent childrenNode: children
當時當咱們並無在demoController的做用域中給aaa賦值,也就是在上例中刪除這一句$scope.aaa = "children";
,那麼執行結果就爲
parentNode: parent childrenNode: parent
注意:若是一個元素上有多個指令都使用了隔離做用域,那麼只有其中一個能夠生效,只有指令模板中的根元素才能得到一個新的做用域,這時候,scope就被設置爲true了。
<div ng-app="app" ng-init="aaa='parent'"> parentNode: {{aaa}} <div ng-controller="demoController01"> childrenNode: {{aaa}} <div ng-controller="demoController02"> lastNode: {{aaa}} </div> </div> </div>
angular.module('app', []) .controller('demoController01', function($scope) { $scope.aaa = "children"; }) .controller('demoController02', function($scope) { $scope.aaa = "last" })
接下來,咱們經過一個簡單明瞭的例子來講明scope取值的不一樣差異
<div ng-app="app" ng-controller="mainController"> parent: {{ name }} <br /> <input type="text" ng-model="name" /> <div my-directive></div> </div>
angular.module('app', []) .controller('mainController', function($scope) { $scope.name = "Jake"; }) .directive('myDirective', function() { return { restrict: 'EA', scope: false, replace: true, template: '' + '<div>' + 'childNode: {{ name }} ' + '<br />' + '<input type="text" ng-model="name">' + '</div>' } })
點擊上面的實例地址,咱們能夠依次改變scope的值爲false, true, {},結果發現
當想要建立一個可重用的組件時,隔離做用域是一個很好的選擇,經過隔離做用域,咱們能夠確保指令是獨立的,而且能夠輕鬆的插入到任何HTML APP中,而且這種作法防止了父做用域被污染。
隔離做用域能夠經過綁定策略來訪問父做用域的屬性
咱們來看一個例子
<div ng-app="app" ng-controller="mainController"> <input type="text" ng-model="color" placeholder="Enter a color"/> <hello-world></hello-world> </div>
angular.module('app', []) .controller('mainController', function($scope) { }) .directive('helloWorld', function() { return { restrict: 'EA', scope: false, replace: true, template: '<p style="background-color:{{color}}">Hello world!</p>' } })
運行上面的代碼,咱們在input中輸入顏色值,好比red,那麼hello world一行的背景就會變成紅色。動手試試!
可是,當咱們將scope的值設置爲{}時,再次運行代碼就發現頁面並不能成功的完整顯示了.這是由於{}讓helloWorld指令產生了隔離做用域,沒辦法從父級做用域中繼承到color的值了。
因此在template中的{{color}}變成了依賴於本身的做用域,而不是依賴於父級做用域。所以咱們須要一些辦法來讓隔離做用域可以讀取父級做用域的屬性,這個方法就是綁定策略。
下面咱們來探索設置這種綁定策略的幾種方法
<div ng-app="app" ng-controller="mainController"> <input type="text" ng-model="color" placeholder="Enter a color"/> <hello-world color-attr="{{color}}"></hello-world> </div>
angular.module('app', []) .controller('mainController', function($scope) { }) .directive('helloWorld', function() { return { restrict: 'EA', scope: { color: '@colorAttr' }, replace: true, template: '<p style="background-color:{{color}}">Hello world!</p>' } })
這種辦法只能單向,經過在運行的指令DOM上設置color-attr
屬性,而且採用{{}}綁定某個模型值。固然,咱們也能夠直接在這裏綁定字符串的顏色值,如color-attr="red"
所以當表達式的值發生變化時,屬性color-attr的值也會發生變化,經過單向綁定該值,就能夠改變隔離做用域中的屬性color.
若是綁定的隔離做用域屬性名與元素的屬性名相同,則能夠採用缺省寫法
// html <hello-world color="{{color}}"></hello-world> // js app.directive('helloWorld', function() { return { scope: { color: '@' }, ... } })
<div ng-app="app" ng-controller="mainController"> <input type="text" ng-model="color" placeholder="Enter a color"/> <br /> {{ color }} <!-- 這裏要注意寫法與@綁定的不一樣 --> <hello-world color="color"></hello-world> </div>
angular.module('app', []) .controller('mainController', function($scope) { $scope.color = 'red'; }) .directive('helloWorld', function() { return { restrict: 'EA', scope: { color: '=' }, replace: true, template: '<div><p style="background-color:{{color}}">' + 'Hello world!' + '</p>' + '<input type="text" ng-model="color"></div>' } })
此處也採用了相似的缺省寫法。
這裏須要注意的是,咱們要直接在指令元素設置屬性時,是直接將實際的做用域模型複製給該屬性
這樣一個雙向綁定被創建了,改變任何一個input值都會改變另一個值。
<div ng-app="app" ng-controller="mainController"> <input type="text" ng-model="name" placeholder="Enter name"/> <br /> {{ name }} <hello-world say="say()" name="{{name}}"></hello-world> </div>
angular.module('app', []) .controller('mainController', function($scope) { $scope.name = "yangbo"; $scope.say = function() { alert('hello!'); } }) .directive('helloWorld', function() { return { restrict: 'EA', scope: { name: '@', say: '&' }, replace: true, template: '<button type="button" ng-bind="name" ng-init="say()"></button>' } })
一樣採用了缺省寫法,運行以後,彈出窗口!
布爾值或者字符element
,默認值爲false
這個配置選項可讓咱們提取包含在指令那個元素裏面的內容,再將它放置在指令模板的特定位置。當咱們開啓transclude以後,咱們就可使用ng-transclude來指明應該在什麼地方放置transclude的內容
<div ng-app="app" ng-controller="mainController"> <div class="a"> <p>china</p> <hello-world> {{name}} </hello-world> </div> </div>
angular.module('app', []) .controller('mainController', function($scope) { $scope.name = "yangbo5207"; }) .directive('helloWorld', function() { return { restrict: 'EA', scope: {}, replace: true, transclude: true, template: '<div class="b"><div ng-transclude>你看不見我</div></div>' } })
運行上面的代碼,輸出
china yangbo5207
咱們查看渲染出來的html,結果爲
<div ng-app="app" ng-controller="mainController" class="ng-scope"> <div class="a"> <p>china</p> <div class="b ng-isolate-scope"> <div ng-transclude=""> <span class="ng-binding ng-scope">yangbo5207</span> </div> </div> </div> </div>
另外當開啓transclude時,會建立一個新的transclude空間,而且繼承父做用域(也就是scope設置的隔離做用域)
從上面例子咱們知道,當transclude的值被設置爲true時,嵌入的內容爲{{name}},可是若是將它的值設置爲element
呢,咱們能夠在上例的基礎上進行一個簡單的修改,發現,嵌入內容爲整個元素
<hello-world>{{name}}</hello-world>
查看渲染結果
<div ng-app="app" ng-controller="mainController" class="ng-scope"> <div class="a"> <p>china</p> <div class="b ng-isolate-scope"> <div ng-transclude=""> <hello-world class="ng-binding ng-scope"> yangbo5207 </hello-world> </div> </div> </div> </div>
注意:在一個指令的模板中,只能聲明一次ng-transclude
那麼問題來了,若是咱們想要把嵌入內容屢次放入咱們的模板中怎麼辦?
可使用$transclude,後面會講到!也可使用complie函數中,裏面的transcludeFn參數,後面會講到!或者使用link鏈接函數
能夠是一個字符串或者函數。
若爲字符串,則將字符串當作控制器的名字,來查找註冊在應用中的控制器的構造函數
angular.module('app', []) .directive('myDirective', function() { return { restrict: 'EA', replace: true, transclude: true, // 會查找模塊中其餘被名爲targetController的控制器 controller: 'targetController' } }) .controller('targetController', function($scope, $element, $attrs, $transclude) { // 控制器邏輯放在這裏 })
固然,也能夠直接在指令內部定義爲匿名函數,一樣能夠注入任何服務
angular.module('app', []) .directive('myDirective', function() { return { restrict: 'EA', replace: true, transclude: true, controller: function($scope, $element, $attrs, $transclude) { // 控制器邏輯 } } })
另外還有一些特殊的服務能夠注入,
指令的控制器和link函數(後面會講到)能夠進行互換。區別在於,控制器主要用來提供可在指令間複用的行爲,可對外提供與外部交互的接口,可是link連接只能在當前指令內部中定義行爲,且沒法在指令間複用。
<div ng-app="app"> <div test-directive="dateType" url="http://www.tigerbrokers.com">外面的世界很精彩。</div> </div>
angular.module('app', []) .directive('testDirective', function() { return { transclude: true, replace: true, controller: function($scope, $element, $attrs, $transclude, $log) { $transclude(function(clone) { var a = angular.element('<a>'); a.attr('href', $attrs.url); a.text(clone.text()); $element.append(a); }); } } })
$transclude 能夠接受兩個參數,第一個是$scope,第二個是纔有參數clone的回調函數。
而這個clone實際上就是嵌入的內容。能夠在根據它作不少DOM操做。
它還有最簡單的用法
angular.module('app', []) .directive('testDirective', function() { return { transclude: true, replace: true, controller: function($scope, $element, $attrs, $transclude, $log) { // 這裏的$transclude就是嵌入的內容 var a = $transclude(); $element.append(a); } } })
可是咱們要注意,使用$transclude會生成一個新的做用域。默認狀況下,若是咱們簡單使用$transclude(),那麼其做用域就是$transclude 生成的做用域。可是若是咱們使用$transclude($scope, function(clone) {}),那麼做用域就是directive的做用域了。
固然問題又來了,若是咱們想使用父級做用域呢?
$scope.$parent
<div ng-app="app" ng-controller="parentController"> <div ng-controller="sonController"> <test-directive url="http://www.tigerbrokers.com">炒美股,上老虎2</test-directive> </div> </div>
angular.module('app', []) .controller('parentController', function($scope) { $scope.title = "PARENT TIGER"; }) .controller('sonController', function($scope) { $scope.title = "CHILDREN TIGER"; }) .directive('testDirective', function() { return { transclude: true, replace: true, controller: function($scope, $element, $attrs, $transclude, $log) { var a = $transclude(); $element.append(a); $log.info($scope.title); $log.info($scope.$parent.title); } } })
angualr 1.2以後,就已經開始支持controllerAs的語法,這樣咱們就能夠不用將屬性和方法掛載到$scope上,而是this上。
<div ng-app="app" ng-controller="demoController as demo">{{ demo.name }}</div>
angular.module('app', []) .controller('demoController', function() { this.name = "Jake"; })
咱們能夠從實例中看出,這裏的this就是指的as後面的那個別名。而後咱們在表達式中就可使用這個別名
咱們知道,在directive中的controller,主要職能是對外提供交互接口,而結合require與link,所以咱們經常會利用這樣的語法而非上面例子中的$scope
app.directive('testDirective', function() { return { controller: function() { this.name = "Jake"; } } })
字符串或者數組,字符串表明另外一個指令的名字,它將做爲link函數的第四個參數。具體用法咱們能夠舉例子來講明。
假設如今咱們要編寫兩個指令,兩個指令的link函數中存在許多重合的方法,這時候咱們就能夠將這些重複的方法寫在第三個指令的controller中,而後在這兩個指令中,使用require將第三個指令引入進來,而後咱們就能夠經過link鏈接函數的第四個參數引用這些重合的方法了
指令之間經常經過這種方式進行交互
<div ng-app="expanderModule" ng-controller="SomeController" class="wrap"> <accordion> <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'> {{expander.text}} </expander> </accordion> </div>
var expModule=angular.module('expanderModule',[]) expModule.directive('accordion', function() { return { restrict : 'EA', replace : true, transclude : true, template : '<div ng-transclude></div>', controller : function() { var expanders = []; this.gotOpened = function(selectedExpander) { angular.forEach(expanders, function(expander) { if (selectedExpander != expander) { expander.showMe = false; } }); } this.addExpander = function(expander) { expanders.push(expander); } } } }); expModule.directive('expander', function() { return { restrict : 'EA', replace : true, transclude : true, require : '^?accordion', scope : { title : '=expanderTitle' }, template : '<div>' + '<div class="title" ng-click="toggle()">{{title}}</div>' + '<div class="body" ng-show="showMe" ng-transclude></div>' + '</div>', link : function(scope, element, attrs, accordionController) { scope.showMe = false; accordionController.addExpander(scope); scope.toggle = function toggle() { scope.showMe = !scope.showMe; accordionController.gotOpened(scope); } } } }); expModule.controller("SomeController",function($scope) { $scope.expanders = [{ title : 'Click me to expand', text : 'Hi there folks, I am the content that was hidden but is now shown.' }, { title : 'Click this', text : 'I am even better text than you have seen previously' }, { title : 'Test', text : 'test' }]; });
實例說明,controller是用來與不一樣指令之間通訊的。
另外咱們能夠在require的參數值加上下面的某個前綴,這回改變查找控制器的行爲。
首先加載angular庫,查找ng-app指令,從而找到應用的邊界,根據ng-app劃定的做用域來調用$compile服務進行編譯。
angular會遍歷整個html文檔,並根據js中指令的定義來處理在頁面上聲明的各個指令,按照指令的優先級排列,根據指令中的配置參數(template, replace, transclude等)轉換DOM,而後就開始按照順序執行各指令的compile函數(若是指令上有定義compile函數)對模板自身進行轉換。
此處的compile函數值什麼指令中配置的,與上面說的$compile服務不同
每一個compile函數執行完後會返回一個link函數,全部的link函數會合成一個大的link函數,而後這個大的link函數就會被執行, 主要作數據綁定,經過 DOM上註冊監聽器來動態修改scope中的數據,或者是使用$watchs監聽scope中變量來修改DOM,從而創建雙向綁定等等。
若咱們的指令中沒有配置compile函數,那咱們配置的link函數就會運行,她作的事情大體跟上面compile返回以後全部的link合成的大link函數差很少。
因此,在指令中compile與link選項是互斥的,若是同時設置了這兩項,那麼就會把compile所返回的函數當作是連接函數,而link選項自己會被忽略。
cmopile選項能夠返回一個對象或者函數,在這裏咱們能夠在指令和實時數據被放到DOM以前進行DOM操做,好比咱們能夠在這裏進行添加或者刪除節點的DOM操做。
因此編譯函數是負責對模板的DOM進行轉換,而且僅僅只會運行一次
//compile函數的語法 compile:function compile(tElement,tAttrs,transclude){ return{ pre:function preLink(scope,iElement,iAttrs,controller){}, post:function postLink(scope,iElement,iAttrs,controller){} } }
對於咱們編寫的大部分指令來講,並不須要對模板進行轉換,因此大部分狀況只須要編寫link函數就行。
preLink函數會在編譯階段以後,指令連接到子元素以前執行,相似的,postLink會在指令連接到子元素以後執行。這意味着,爲了避免破壞綁定過程,若是你須要修改DOM結構,你應該在postLink函數中來作這件事情。
link函數負責將做用域與DOM進行連接
//link連接函數 link:function postLink(scope,iElement,iAttrs){}
若指令中定義有require選項,則link函數會有第四個參數,表明控制器或者所依賴的指令的控制器(上面require選項例子已有例子)
內容都真夠多的,終於整理完了,今天寫了兩篇文章,感受好累!若是你們以爲文章對你有幫助,求贊求收藏。
許多內容都是從不一樣的網站上整理而來,每個實例都是本身運行事後保存在codepen上,你們能夠結合codepen的例子結合理解文章內容,本文屬於收集文,非原創,但絕對乾貨!值!得!一!贊!與收藏。