angular js自定義指令 directive 如何使用?為什么要使用封裝的自定義指令?


自定義指令 directive 如何使用_騰訊視頻



【JS-8】

angular js自定義指令 directive 如何使用?為什么要使用封裝的自定義指令?

小課堂【武漢分院第130期】

分享人:徐恒

目錄

1.背景介紹

2.知識(shí)剖析

3.常見問題

4.解決方案

5.編碼實(shí)戰(zhàn)

6.擴(kuò)展思考

7.參考文獻(xiàn)

8.更多討論

一.背景介紹

指令定義

對(duì)于指令,可以把它簡單的理解成在特定DOM元素上運(yùn)行的函數(shù),指令可以擴(kuò)展這個(gè)元素

的功能。

例如, ng-click 可以讓一個(gè)元素能夠監(jiān)聽 click 事件,并在接收到事件的時(shí)候執(zhí)行AngularJS表

達(dá)式。正是指令使得AngularJS這個(gè)框架變得強(qiáng)大,并且正如所見,我們可以自己創(chuàng)造新的指令。

AngularJS應(yīng)用的模塊中有很多方法可以使用,其中 directive() 這個(gè)方法是用來定義指令的:

angular.module('myApp', [])

.directive('myDirective', function ($timeout, UserDefinedService) {

// 指令定義放在這里

});

directive() 方法可以接受兩個(gè)參數(shù):

1.? name (字符串)

指令的名字,用來在視圖中引用特定的指令。

2.? factory_function (函數(shù))

這個(gè)函數(shù)返回一個(gè)對(duì)象,其中定義了指令的全部行為。 $compile 服務(wù)利用這個(gè)方法返回的對(duì)

象,在DOM調(diào)用指令時(shí)來構(gòu)造指令的行為。

angular.application('myApp', [])

.directive('myDirective', function() {

// 一個(gè)指令定義對(duì)象

return {

// 通過設(shè)置項(xiàng)來定義指令,在這里進(jìn)行覆寫

};

});

我們也可以返回一個(gè)函數(shù)代替對(duì)象來定義指令,但是像上面的例子一樣,通過對(duì)象來定義是

最佳的方式。當(dāng)返回一個(gè)函數(shù)時(shí),這個(gè)函數(shù)通常被稱作鏈接傳遞(postLink)函數(shù),利用它我們

可以定義指令的鏈接(link)功能。由于返回函數(shù)而不是對(duì)象會(huì)限制定義指令時(shí)的自由度,因此

只在構(gòu)造簡單的指令時(shí)才比較有用。

當(dāng)AngularJS啟動(dòng)應(yīng)用時(shí),它會(huì)把第一個(gè)參數(shù)當(dāng)作一個(gè)字符串,并以此字符串為名來注冊(cè)第

二個(gè)參數(shù)返回的對(duì)象。AngularJS編譯器會(huì)解析主HTML的DOM中的元素、屬性、注釋和CSS類名

中使用了這個(gè)名字的地方,并在這些地方引用對(duì)應(yīng)的指令。當(dāng)它找到某個(gè)已知的指令時(shí),就會(huì)在

頁面中插入指令所對(duì)應(yīng)的DOM元素。

tip:為了避免與未來的HTML標(biāo)準(zhǔn)沖突,給自定義的指令加入前綴來代表自定義的

命名空間。AngularJS本身已經(jīng)使用了 ng- 前綴,所以可以選擇除此以外的名字。

在例子中我們使用 my- 前綴(比如 my-derictive )。

指令的工廠函數(shù)只會(huì)在編譯器第一次匹配到這個(gè)指令時(shí)調(diào)用一次。和 controller 函數(shù)類似,

我們通過 $injetor.invoke 來調(diào)用指令的工廠函數(shù)。

當(dāng)AngularJS在DOM中遇到具名的指令時(shí),會(huì)去匹配已經(jīng)注冊(cè)過的指令,并通過名字在注冊(cè)

過的對(duì)象中查找。此時(shí),就開始了一個(gè)指令的生命周期,指令的生命周期開始于 $compile 方法并

結(jié)束于 link 方法,后面的內(nèi)容中我們會(huì)介紹這個(gè)過程。

下面,來看看定義一個(gè)指令時(shí)可以使用的全部設(shè)置選項(xiàng)。

tip:一個(gè)JavaScript對(duì)象由鍵和值組成。當(dāng)一個(gè)給定鍵的值被設(shè)置為一個(gè)字符串、布

爾值、數(shù)字、數(shù)組或?qū)ο髸r(shí),我們把這個(gè)鍵稱為屬性。當(dāng)把鍵設(shè)置為函數(shù)時(shí),

我們把它叫做方法。

可能的選項(xiàng)如下所示,每個(gè)鍵的值說明了可以將這個(gè)屬性設(shè)置為何種類型或者什么樣的

函數(shù):

angular.module('myApp', [])

.directive('myDirective', function() {

return {

restrict: String,

priority: Number,

terminal: Boolean,

template: String or Template Function:

function(tElement, tAttrs) (...},

templateUrl: String,

replace: Boolean or String,

scope: Boolean or Object,

transclude: Boolean,

controller: String or

function(scope, element, attrs, transclude, otherInjectables) { ... },

controllerAs: String,

require: String,

link: function(scope, iElement, iAttrs) { ... },

compile: // 返回一個(gè)對(duì)象或連接函數(shù),如下所示:

function(tElement, tAttrs, transclude) {

return {

pre: function(scope, iElement, iAttrs, controller) { ... },

post: function(scope, iElement, iAttrs, controller) { ... }

}

// 或者

return function postLink(...) { ... }

}

};

});

restrict (字符串)

restrict 是一個(gè)可選的參數(shù)。它告訴AngularJS這個(gè)指令在DOM中可以何種形式被聲明。默

認(rèn)AngularJS認(rèn)為 restrict 的值是 A ,即以屬性的形式來進(jìn)行聲明。

可選值如下:

E(元素)A(屬性,默認(rèn)值)

C(類名)

M(注釋)<--directive:my-directive expression-->這些選項(xiàng)可以單獨(dú)使用,也可以混合在一起使用:angular.module('myDirective', function(){? ? return {? ? restrict: 'EA' // 輸入元素或?qū)傩? ? };});上面的配置可以同時(shí)用屬性或注釋的方式來聲明指令:<-- 作為一個(gè)屬性 -->

<-- 或者作為一個(gè)元素 -->

屬性是用來聲明指令最常用的方式,因?yàn)樗茉诎ɡ习姹镜腎E瀏覽器在內(nèi)的所有瀏覽器中

正常工作,并且不需要在文檔頭部注冊(cè)新的標(biāo)簽。

盡量避免用注釋方式來聲明屬性。這種方式最初被用來聲明由多個(gè)標(biāo)簽組成的

指令。這種方法在某些情況下特別有用,比如在 table 元素內(nèi)使用 ng-repeat

指 令 , 但 在 AngularJS 1.2 中 ng-repeat 可 以 通 過 ng-repeat-start 和

ng-repeat-end 來更優(yōu)雅地滿足這個(gè)需求,注釋模式就沒有什么用武之地了。

如果你對(duì)此很好奇,可以通過Chrome開發(fā)者工具的 element 標(biāo)簽觀察一下使用

ng-repeat 時(shí)被隱式添加的注釋。

元素方式還是屬性方式

在頁面中通過元素方式創(chuàng)建新的指令可以將一些功能封裝在元素內(nèi)部。例如,如果我們想要

做一個(gè)時(shí)鐘(忽略對(duì)老版本IE瀏覽器的支持),可以創(chuàng)建一個(gè) clock 指令,然后在DOM中用如下

代碼來聲明:

這樣做可以告訴指令的使用者,這里會(huì)完整包含應(yīng)用的某一部分內(nèi)容。這個(gè)時(shí)鐘并不是對(duì)一

個(gè)既有時(shí)鐘的修飾或擴(kuò)展,而是一個(gè)全新的單元。盡管這里也可以使用屬性形式聲明指令

(AngularJS并不在意),但我們選擇了元素形式,因?yàn)檫@樣可以更明確地表達(dá)意圖。

用屬性形式來給一個(gè)已經(jīng)存在的元素添加數(shù)據(jù)或行為。以時(shí)鐘為例,假設(shè)我們更喜歡模擬時(shí)鐘:

如何進(jìn)行選擇,通常取決于定義的指令是否包含某個(gè)組件的核心行為,或者用額外的行為、

狀態(tài)或者其他內(nèi)容(比如模擬時(shí)鐘)對(duì)某個(gè)核心組件進(jìn)行修飾或擴(kuò)展。

使用何種指令聲明格式的指導(dǎo)原則是能夠準(zhǔn)確表達(dá)每一段代碼的意圖,創(chuàng)造易于理解和分享

的清晰代碼。

另外一個(gè)重要的標(biāo)準(zhǔn),是根據(jù)指令是否創(chuàng)建、繼承或?qū)⒆约簭乃鶎俚沫h(huán)境中隔離出去進(jìn)行判

斷。指令的父子關(guān)系對(duì)其組成和重用性起著至關(guān)重要的作用,會(huì)有額外的內(nèi)容來更加深入地討論

指令的作用域。

priority優(yōu)先級(jí)(數(shù)值型)

優(yōu)先級(jí)參數(shù)可以被設(shè)置為一個(gè)數(shù)值。大多數(shù)指令會(huì)忽略這個(gè)參數(shù),使用默認(rèn)值0,但也有些

場景設(shè)置高優(yōu)先級(jí)是非常重要甚至是必須的。例如, ngRepeat 將這個(gè)參數(shù)設(shè)置為1000,這樣就可

以保證在同一元素上,它總是在其他指令之前被調(diào)用。

如果一個(gè)元素上具有兩個(gè)優(yōu)先級(jí)相同的指令,聲明在前面的那個(gè)會(huì)被優(yōu)先調(diào)用。如果其中一

個(gè)的優(yōu)先級(jí)更高,則不管聲明的順序如何都會(huì)被優(yōu)先調(diào)用:具有更高優(yōu)先級(jí)的指令總是優(yōu)先運(yùn)行。

ngRepeat 是所有內(nèi)置指令中優(yōu)先級(jí)最高的,它總是在其他指令之前運(yùn)行。這樣

設(shè)置主要考慮的是性能。在討論編譯參數(shù)時(shí)會(huì)更詳細(xì)介紹性能相關(guān)的內(nèi)容。

terminal (布爾型)

terminal 是一個(gè)布爾型參數(shù),可以被設(shè)置為 true 或 false 。

這個(gè)參數(shù)用來告訴AngularJS停止運(yùn)行當(dāng)前元素上比本指令優(yōu)先級(jí)低的指令。但同當(dāng)前指令

優(yōu)先級(jí)相同的指令還是會(huì)被執(zhí)行。

如果元素上某個(gè)指令設(shè)置了 terminal 參數(shù)并具有較高的優(yōu)先級(jí),就不要再用其他低優(yōu)先級(jí)的

指令對(duì)其進(jìn)行修飾了,因?yàn)椴粫?huì)被調(diào)用。但是具有相同優(yōu)先級(jí)的指令還是會(huì)被繼續(xù)調(diào)用。

使用了 terminal 參數(shù)的例子是 ngView 和 ngIf 。 ngIf 的優(yōu)先級(jí)略高于 ngView ,如果 ngIf 的表

達(dá)式值為 true , ngView 就可以被正常執(zhí)行,但如果 ngIf 表達(dá)式的值為 false ,由于 ngView 的優(yōu)先

級(jí)較低就不會(huì)被執(zhí)行。

template (字符串或函數(shù))

template 參數(shù)是可選的,必須被設(shè)置為以下兩種形式之一:

?? 一段HTML文本;

?? 一個(gè)可以接受兩個(gè)參數(shù)的函數(shù),參數(shù)為 tElement 和 tAttrs ,并返回一個(gè)代表模板的字符

串。 tElement 和 tAttrs 中的 t 代表 template ,是相對(duì)于 instance 的。在討論鏈接和編譯

設(shè)置時(shí)會(huì)詳細(xì)介紹,模板元素或?qū)傩耘c實(shí)例元素或?qū)傩灾g的區(qū)別。

AngularJS會(huì)同處理HTML一樣處理模板字符串。模板中可以通過大括號(hào)標(biāo)記來訪問作用域,

例如 {{ expression }} 。

如果模板字符串中含有多個(gè)DOM元素,或者只由一個(gè)單獨(dú)的文本節(jié)點(diǎn)構(gòu)成,那它必須被包

含在一個(gè)父元素內(nèi)。換句話說,必須存在一個(gè)根DOM元素:

template: '\

<-- single root element -->\Click me\

When using two elements, wrap them in a parent element

\

\

另外,注意每一行末尾的反斜線,這樣AngularJS才能正確解析多行字符串。在實(shí)際生產(chǎn)中,

更好的選擇是使用 templateUrl 參數(shù)引用外部模板,因?yàn)槎嘈形谋鹃喿x和維護(hù)起來都是一場噩夢(mèng)。

模板字符串和 templateURL 中最需要了解的重要功能,是它們?nèi)绾瓮饔糜蜴溄悠饋怼?/p>

templateUrl (字符串或函數(shù))

templateUrl 是可選的參數(shù),可以是以下類型:

?? 一個(gè)代表外部HTML文件路徑的字符串;

?? 一個(gè)可以接受兩個(gè)參數(shù)的函數(shù),參數(shù)為 tElement 和 tAttrs ,并返回一個(gè)外部HTML文件

路徑的字符串。

無論哪種方式,模板的URL都將通過AngularJS內(nèi)置的安全層,特別是 $getTrusted

ResourceUrl ,這樣可以保護(hù)模板不會(huì)被不信任的源加載。

默認(rèn)情況下,調(diào)用指令時(shí)會(huì)在后臺(tái)通過Ajax來請(qǐng)求HTML模板文件。有兩件事情需要知道。

?? 在本地開發(fā)時(shí),需要在后臺(tái)運(yùn)行一個(gè)本地服務(wù)器,用以從文件系統(tǒng)加載HTML模板,否則

會(huì)導(dǎo)致Cross Origin Request Script(CORS)錯(cuò)誤。

?? 模板加載是異步的,意味著編譯和鏈接要暫停,等待模板加載完成。

通過Ajax異步加載大量的模板將嚴(yán)重拖慢一個(gè)客戶端應(yīng)用的速度。為了避免延遲,可以在部

署應(yīng)用之前對(duì)HTML模板進(jìn)行緩存。在大多數(shù)場景下緩存都是一個(gè)非常好的選擇,因?yàn)锳ngularJS

通過減少請(qǐng)求數(shù)量提升了性能。

模板加載后,AngularJS會(huì)將它默認(rèn)緩存到 $templateCache 服務(wù)中。在實(shí)際生產(chǎn)中,可以提前將模板緩存到一個(gè)定義模板的JavaScript文件中,這樣就不需要通過XHR來加載模板了。

replace (布爾型)

replace 是一個(gè)可選參數(shù),如果設(shè)置了這個(gè)參數(shù),值必須為 true ,因?yàn)槟J(rèn)值為 false 。默

認(rèn)值意味著模板會(huì)被當(dāng)作子元素插入到調(diào)用此指令的元素內(nèi)部:

調(diào)用指令之后的結(jié)果如下(這是默認(rèn) replace 為 false 時(shí)的情況):

如果 replace 被設(shè)置為了 true :

指令作用域

為了完全理解指令定義對(duì)象中剩下的參數(shù),需要先介紹指令作用域是如何工作的。

$rootScope 這個(gè)特殊的對(duì)象會(huì)在DOM中聲明 ng-app 時(shí)被創(chuàng)建:

Inside Div Two

上面的代碼中,我們?cè)趹?yīng)用的根作用域中設(shè)置了三個(gè)屬性: someProperty 、 siblingProperty

和 aThirdProperty 。

從這里開始,DOM中每個(gè)指令調(diào)用時(shí)都可能會(huì):

?? 直接調(diào)用相同的作用域?qū)ο螅?/p>

?? 從當(dāng)前作用域?qū)ο罄^承一個(gè)新的作用域?qū)ο螅?/p>

?? 創(chuàng)建一個(gè)同當(dāng)前作用域相隔離的作用域?qū)ο蟆?/p>

上面的列子展示的是第一種情況。前兩個(gè) div 是兄弟元素,可以通過 get 和 set 訪問 $rootScope 。

第二個(gè) div 內(nèi)部的 div 同樣可以通過 get 和 set 訪問相同的根作用域。

指令嵌套并不一定意味著需要改變它的作用域。默認(rèn)情況下,子指令會(huì)被付予訪問父DOM

元素對(duì)應(yīng)的作用域的能力,這樣做的原因可以通過介紹指令的 scope 參數(shù)來理解, scope 參數(shù)默

認(rèn)是 false 。

scope 參數(shù)(布爾型或?qū)ο螅?/p>

scope 參數(shù)是可選的,可以被設(shè)置為 true 或一個(gè)對(duì)象。默認(rèn)值是 false 。

當(dāng) scope 設(shè)置為 true 時(shí),會(huì)從父作用域繼承并創(chuàng)建一個(gè)新的作用域?qū)ο蟆?/p>

如果一個(gè)元素上有多個(gè)指令使用了隔離作用域,其中只有一個(gè)可以生效。只有指令模板中的

根元素可以獲得一個(gè)新的作用域。因此,對(duì)于這些對(duì)象來說 scope 默認(rèn)被設(shè)置為 true 。

內(nèi)置指令 ng-controller 的作用,就是從父級(jí)作用域繼承并創(chuàng)建一個(gè)新的子作用域。它會(huì)創(chuàng)

建一個(gè)新的從父作用域繼承而來的子作用域。

用這些新內(nèi)容更新一下前面的例子:

看例子scope1

Inside Div Two: {{ aThirdProperty }}

Inside Div Three: {{ aThirdProperty }}

Inside Div Four: {{ aThirdProperty }}

如果直接運(yùn)行這段代碼會(huì)報(bào)錯(cuò),因?yàn)闆]有在JavaScript中定義所需的控制器,下面就來定義這

個(gè)控制器:

angular.module('myApp', [])

.controller('SomeController', function($scope) {

// 可以留空,但需要被定義

})

刷新頁面,會(huì)發(fā)現(xiàn)第二個(gè) div 中由于 {{ aTgirdProperty }} 未定義,因此什么都沒有輸出。

第三個(gè) div 顯示了設(shè)置在繼承來的作用域中的 data for a 3rd property ,如圖所示。

為了進(jìn)一步證明作用域的繼承機(jī)制是向下而非向上進(jìn)行的,下面再看另外一個(gè)例子,展示的

是 {{ aThirdProperty }} 從父作用域繼承而來:

Inside Div Two: {{ aThirdProperty }}

Inside Div Three: {{ aThirdProperty }}

Inside Div Four: {{ aThirdProperty }}

在JavaScript中加入 SecondController 的定義:

angular.module('myApp', [])

.controller('SomeController', function($scope) {

// 可以留空,但需要被定義

})

.controller('SecondController', function($scope) {

// 同樣可以留空

})

如果要?jiǎng)?chuàng)建一個(gè)能夠從外部原型繼承作用域的指令,將 scope 屬性設(shè)置為 true :

angular.module('myApp', [])

.directive('myDirective', function() {

return {

restrict: 'A',

scope: true

};

});

下面用指令來改變DOM的作用域:

Inside Div Two: {{ aThirdProperty }}

Inside Div Three: {{ aThirdProperty }}

Inside Div Four: {{ aThirdProperty }}

Outside myDirective: {{ myProperty }}

Inside myDirective: {{ myProperty }}

下面介紹最后一個(gè)和作用域相關(guān)的難題:隔離作用域。

隔離作用域

隔離作用域可能是 scope 屬性三個(gè)選項(xiàng)中最難理解的一個(gè),但也是最強(qiáng)大的。隔離作用域的

概念是以面向?qū)ο缶幊虨榛A(chǔ)的。AngularJS指令的作用域中可以看到如Small Talk語言和SOLID

原則的影子。

具有隔離作用域的指令最主要的使用場景是創(chuàng)建可復(fù)用的組件,組件可以在未知上下文中使

用,并且可以避免污染所處的外部作用域或不經(jīng)意地污染內(nèi)部作用域。

創(chuàng)建具有隔離作用域的指令需要將 scope 屬性設(shè)置為一個(gè)空對(duì)象 {} 。如果這樣做了,指令的

模板就無法訪問外部作用域了:

Outside myDirective: {{ myProperty }}

Inside myDirective: {{ myProperty }}

angular.module('myApp', [])? ? .controller('MainController', function($scope) {? ? })? ? .directive('myDirective', function() {? ? ? ? return {? ? ? ? ? ? restrict: 'A',? ? ? ? ? ? scope: {},? ? ? ? ? ? priority: 100,? ? ? ? ? ? template: '

Inside myDirective {{ myProperty }}

'? ? };});

注意,這里為 myDirective 設(shè)置了一個(gè)高優(yōu)先級(jí)。由于 ngInit 指令會(huì)以非零的優(yōu)先

級(jí)運(yùn)行,這個(gè)例子將會(huì)優(yōu)先運(yùn)行 ngInit 指令,然后才是我們定義的指定,并且這個(gè)

myProperty 在 $scope 對(duì)象中是有效的。

看例子scope3.1

示例代碼的效果與將 scope 設(shè)置為 true 幾乎是相同的。下面看一下使用繼承作用域的指令的

例子,對(duì)比一下二者:

Surrounding scope: {{ myProperty }}

JavaScript代碼:

angular.module('myApp', [])

.directive('myDirective', function() {

return {

restrict: 'A',

template: 'Inside myDirective, isolate scope: {{ myProperty }}',

scope: {}

};

})

.directive('myInheritScopeDirective', function() {

return {

restrict: 'A',

template: 'Inside myDirective, isolate scope: {{ myProperty }}',

scope: true

};

});

看例子scope3.2

理解了最重要的關(guān)于作用域的概念后,就可以將隔離作用域中的屬性同外部世界進(jìn)行綁定,

使得隔離作用域可以和外部進(jìn)行交互。

綁定策略

使用無數(shù)據(jù)的隔離作用域并不常見。AngularJS提供了幾種方法能夠?qū)⒅噶顑?nèi)部的隔離作用

域,同指令外部的作用域進(jìn)行數(shù)據(jù)綁定。

為了讓新的指令作用域可以訪問當(dāng)前本地作用域中的變量,需要使用下面三種別名中的一種。

本地作用域?qū)傩裕菏褂?@ 符號(hào)將本地作用域同DOM屬性的值進(jìn)行綁定。指令內(nèi)部作用域可以

使用外部作用域的變量:

@ (or @attr)

現(xiàn)在,可以在指令中使用綁定的字符串了。

雙向綁定:通過 = 可以將本地作用域上的屬性同父級(jí)作用域上的屬性進(jìn)行雙向的數(shù)據(jù)綁定。

就像普通的數(shù)據(jù)綁定一樣,本地屬性會(huì)反映出父數(shù)據(jù)模型中所發(fā)生的改變。

= (or =attr)

父級(jí)作用域綁定 通過 & 符號(hào)可以對(duì)父級(jí)作用域進(jìn)行綁定,以便在其中運(yùn)行函數(shù)。意味著對(duì)這

個(gè)值進(jìn)行設(shè)置時(shí)會(huì)生成一個(gè)指向父級(jí)作用域的包裝函數(shù)。

要使調(diào)用帶有一個(gè)參數(shù)的父方法,我們需要傳遞一個(gè)對(duì)象,這個(gè)對(duì)象的鍵是參數(shù)的名稱,值

是要傳遞給參數(shù)的內(nèi)容。

& (or &attr)

例如,假設(shè)我們?cè)陂_發(fā)一個(gè)電子郵件客戶端,并且要?jiǎng)?chuàng)建一個(gè)電子郵件的文本輸入框:

這里有一個(gè)數(shù)據(jù)模型( ng-model ),一個(gè)函數(shù)( sendMail() )和一個(gè)字符串( from-name )。

在指令中做如下設(shè)置以訪問這些內(nèi)容:

scope: {

ngModel: '=', // 將ngModel同指定對(duì)象綁定

onSend: '&', // 將引用傳遞給這個(gè)方法

fromName: '@' // 儲(chǔ)存與fromName相關(guān)聯(lián)的字符串

}

transclude

transclude 是一個(gè)可選的參數(shù)。如果設(shè)置了,其值必須為 true ,它的默認(rèn)值是 false 。

嵌入有時(shí)被認(rèn)為是一個(gè)高級(jí)主題,但某些情況下它與我們剛剛學(xué)習(xí)過的作用域之間會(huì)有非常

好的配合。使用嵌入也會(huì)很好地?cái)U(kuò)充我們的工具集,特別是在創(chuàng)建可以在團(tuán)隊(duì)、項(xiàng)目、AngularJS

社區(qū)之間共享的HTML代碼片段時(shí)。

嵌入通常用來創(chuàng)建可復(fù)用的組件,典型的例子是模態(tài)對(duì)話框或?qū)Ш綑凇?/p>

我們可以將整個(gè)模板,包括其中的指令通過嵌入全部傳入一個(gè)指令中。這樣做可以將任意內(nèi)

容和作用域傳遞給指令。 transclude 參數(shù)就是用來實(shí)現(xiàn)這個(gè)目的的,指令的內(nèi)部可以訪問外部

指令的作用域,并且模板也可以訪問外部的作用域?qū)ο蟆?/p>

為了將作用域傳遞進(jìn)去, scope 參數(shù)的值必須通過 {} 或 true 設(shè)置成隔離作用域。如果沒有設(shè)

置 scope 參數(shù),那么指令內(nèi)部的作用域?qū)⒈辉O(shè)置為傳入模板的作用域。

只有當(dāng)你希望創(chuàng)建一個(gè)可以包含任意內(nèi)容的指令時(shí),才使用 transclude: true 。

嵌入允許指令的使用者方便地提供自己的HTML模板,其中可以包含獨(dú)特的狀態(tài)和行為,并

對(duì)指令的各方面進(jìn)行自定義。

下面一起來實(shí)現(xiàn)個(gè)小例子,創(chuàng)建一個(gè)可以被自定義的可復(fù)用指令。

我們來創(chuàng)建一個(gè)可以復(fù)用的側(cè)邊欄,同WordPress博客的側(cè)邊欄很相似。我們希望可以保持

CSS樣式的一致性,同時(shí)又希望可以在復(fù)用時(shí)盡量少寫HTML代碼。

如果不想讓指令內(nèi)部的內(nèi)容被模板替換,可以設(shè)置這個(gè)值為true。一般情況下需要和ngTransclude指令一起使用。

看例子scope4

例如,假設(shè)我們想創(chuàng)建一個(gè)包括標(biāo)題和少量HTML內(nèi)容的側(cè)邊欄,如下所示:

First link

Second link

為這個(gè)側(cè)邊欄創(chuàng)建一個(gè)簡單的指令,并將 transclude 參數(shù)設(shè)置為 true :

angular.module('myApp', [])? ? .directive('sidebox', function() {? ? ? ? return {? ? ? ? ? ? restrict: 'EA',? ? ? ? ? ? scope: {? ? ? ? ? ? ? ? title: '@'? ? ? ? ? ? },? ? ? ? ? ? transclude: true,? ? ? ? ? ? template: '

\

\

{{ title }}

\\\

\

'? ? ? ? };? ? });

這段代碼告訴AngularJS編譯器,將它從DOM元素中獲取的內(nèi)容放到它發(fā)現(xiàn) ng-transclude

指令的地方。

借助 transclusion ,我們可以將指令復(fù)用到第二個(gè)元素上,而無須擔(dān)心樣式和布局的一致

性問題。

例如,下面的代碼會(huì)產(chǎn)生兩個(gè)樣式完全一致的側(cè)邊欄,效果所示。

First link

Second link

GraphicsAngularJSD3Front-endStartup

如果指令使用了 transclude 參數(shù),那么在控制器(下面馬上會(huì)介紹)中就無法正常監(jiān)聽數(shù)

據(jù)模型的變化了。這就是最佳實(shí)踐總是建議在鏈接函數(shù)里使用 $watch 服務(wù)的原因。

controller (字符串或函數(shù))

controller 參數(shù)可以是一個(gè)字符串或一個(gè)函數(shù)。當(dāng)設(shè)置為字符串時(shí),會(huì)以字符串的值為名字,

來查找注冊(cè)在應(yīng)用中的控制器的構(gòu)造函數(shù):

angular.module('myApp', [])

.directive('myDirective', function() {

restrict: 'A', // 始終需要

controller: 'SomeController'

})

// 應(yīng)用中其他的地方,可以是同一個(gè)文件或被index.html包含的另一個(gè)文件

angular.module('myApp')

.controller('SomeController', function($scope, $element, $attrs, $transclude) {

// 控制器邏輯放在這里

});

可以在指令內(nèi)部通過匿名構(gòu)造函數(shù)的方式來定義一個(gè)內(nèi)聯(lián)的控制器:

angular.module('myApp',[])

.directive('myDirective', function() {

restrict: 'A',

controller:

function($scope, $element, $attrs, $transclude) {

// 控制器邏輯放在這里

}

});

我們可以將任意可以被注入的AngularJS服務(wù)傳遞給控制器。例如,如果我們想要將 $log 服

務(wù)傳入控制器,只需簡單地將它注入到控制器中,便可以在指令中使用它了。

控制器中也有一些特殊的服務(wù)可以被注入到指令當(dāng)中。這些服務(wù)有:

1.? $scope

與指令元素相關(guān)聯(lián)的當(dāng)前作用域。

2.? $element

當(dāng)前指令對(duì)應(yīng)的元素。

3.? $attrs

由當(dāng)前元素的屬性組成的對(duì)象。例如,下面的元素:

具有如下的屬性對(duì)象:{id: "aDiv",class: "box"}

4.? $transclude

嵌入鏈接函數(shù)會(huì)與對(duì)應(yīng)的嵌入作用域進(jìn)行預(yù)綁定。

transclude 鏈接函數(shù)是實(shí)際被執(zhí)行用來克隆元素和操作DOM的函數(shù)。

在控制器內(nèi)部操作DOM是和AngularJS風(fēng)格相悖的做法,但通過鏈接函數(shù)就可以

實(shí)現(xiàn)這個(gè)需求。僅在 compile 參數(shù)中使用 transcludeFn 是推薦的做法。

例如,我們想要通過指令來添加一個(gè)超鏈接標(biāo)簽。可以在控制器內(nèi)的 $transclude 函數(shù)中實(shí)

現(xiàn),如下所示:

指令的控制器和 link 函數(shù)可以進(jìn)行互換。控制器主要是用來提供可在指令間復(fù)用的行為,但鏈接函數(shù)只能在當(dāng)前內(nèi)部指令中定義行為,且無法在指令間復(fù)用。

link 函數(shù)可以將指令互相隔離開來,而 controller 則定義可復(fù)用的行為。

由于指令可以 require 其他指令所使用的控制器,因此控制器常被用來放置在多個(gè)指令間共享的動(dòng)作。

如果我們希望將當(dāng)前指令的API暴露給其他指令使用,可以使用 controller 參數(shù),否則可以使用 link 來構(gòu)造當(dāng)前指令元素的功能性。如果我們使用了 scope.$watch() 或者想要與DOM元素

做實(shí)時(shí)的交互,使用鏈接會(huì)是更好的選擇。

技術(shù)上講, $scope 會(huì)在DOM元素被實(shí)際渲染之前傳入到控制器中。在某些情況下,例如使用了嵌入,控制器中的作用域所反映的作用域可能與我們所期望的不一樣,這種情況下, $scope對(duì)象無法保證可以被正常更新。

當(dāng)想要同當(dāng)前屏幕上的作用域交互時(shí),可以使用被傳入到 link 函數(shù)中的 scope

參數(shù)。

controllerAs (字符串)

controllerAs 參數(shù)用來設(shè)置控制器的別名,可以以此為名來發(fā)布控制器,并且作用域可以訪

問 controllerAs 。這樣就可以在視圖中引用控制器,甚至無需注入 $scope 。

例如,創(chuàng)建一個(gè) MainController ,然后不要注入 $scope ,如下所示:

angular.module('myApp')

.controller('MainController', function() {

this.name = "Ari";

});

現(xiàn)在,在HTML中無需引用作用域就可以使用 MainController 。

這個(gè)參數(shù)看起來好像沒什么大用,但它給了我們可以在路由和指令中創(chuàng)建匿名控制器的強(qiáng)大

能力。這種能力可以將動(dòng)態(tài)的對(duì)象創(chuàng)建成為控制器,并且這個(gè)對(duì)象是隔離的、易于測試的。

例如,可以在指令中創(chuàng)建匿名控制器,如下所示:

require (字符串或數(shù)組)

require 參數(shù)可以被設(shè)置為字符串或數(shù)組,字符串代表另外一個(gè)指令的名字。 require 會(huì)將控

制器注入到其值所指定的指令中,并作為當(dāng)前指令的鏈接函數(shù)的第四個(gè)參數(shù)。

字符串或數(shù)組元素的值是會(huì)在當(dāng)前指令的作用域中使用的指令名稱。

scope 會(huì)影響指令作用域的指向,是一個(gè)隔離作用域,一個(gè)有依賴的作用域或者完全沒有作

用域。在任何情況下,AngularJS編譯器在查找子控制器時(shí)都會(huì)參考當(dāng)前指令的模板。

如果不使用 ^ 前綴,指令只會(huì)在自身的元素上查找控制器。

//...

restrict: 'EA',

require: 'ngModel'

//...

指令定義只會(huì)查找定義在指令作當(dāng)前用域中的 ng-model="" 。

require 參數(shù)的值可以用下面的前綴進(jìn)行修飾,這會(huì)改變查找控制器時(shí)的行為:

?

如果在當(dāng)前指令中沒有找到所需要的控制器,會(huì)將 null 作為傳給 link 函數(shù)的第四個(gè)參數(shù)。

^

如果添加了 ^ 前綴,指令會(huì)在上游的指令鏈中查找 require 參數(shù)所指定的控制器。

?^

將前面兩個(gè)選項(xiàng)的行為組合起來,我們可選擇地加載需要的指令并在父指令鏈中進(jìn)行查找。

沒有前綴

如果沒有前綴,指令將會(huì)在自身所提供的控制器中進(jìn)行查找,如果沒有找到任何控制器(或

具有指定名字的指令)就拋出一個(gè)錯(cuò)誤。

AngularJS 的生命周期

在AngularJS應(yīng)用起動(dòng)前,它們以HTML文本的形式保存在文本編輯器中。應(yīng)用啟動(dòng)后會(huì)進(jìn)行

編譯和鏈接,作用域會(huì)同HTML進(jìn)行綁定,應(yīng)用可以對(duì)用戶在HTML中進(jìn)行的操作進(jìn)行實(shí)時(shí)響應(yīng)。

這個(gè)神奇的效果是如何發(fā)生的?創(chuàng)建高效率的應(yīng)用需要了解什么?

在這個(gè)過程中總共有兩個(gè)主要階段。

編譯階段

第一個(gè)階段是編譯階段。在編譯階段,AngularJS會(huì)遍歷整個(gè)HTML文檔并根據(jù)JavaScript中

的指令定義來處理頁面上聲明的指令。

每一個(gè)指令的模板中都可能含有另外一個(gè)指令,另外一個(gè)指令也可能會(huì)有自己的模板。當(dāng)

AngularJS調(diào)用HTML文檔根部的指令時(shí),會(huì)遍歷其中所有的模板,模板中也可能包含帶有模板的

指令。

模板樹可能又大又深,但有一點(diǎn)需要注意,盡管元素可以被多個(gè)指令所支持或

修飾,這些指令本身的模板中也可以包含其他指令,但只有屬于最高優(yōu)先級(jí)指

令的模板會(huì)被解析并添加到模板樹中。這里有一個(gè)建議,就是將包含模板的指

令和添加行為的指令分離開來。如果一個(gè)元素已經(jīng)有一個(gè)含有模板的指令了,

永遠(yuǎn)不要對(duì)其用另一個(gè)指令進(jìn)行修飾。只有具有最高優(yōu)先級(jí)的指令中的模板會(huì)

被編譯。

一旦對(duì)指令和其中的子模板進(jìn)行遍歷或編譯,編譯后的模板會(huì)返回一個(gè)叫做模板函數(shù)的函

數(shù)。我們有機(jī)會(huì)在指令的模板函數(shù)被返回前,對(duì)編譯后的DOM樹進(jìn)行修改。

在這個(gè)時(shí)間點(diǎn)DOM樹還沒有進(jìn)行數(shù)據(jù)綁定,意味著如果此時(shí)對(duì)DOM樹進(jìn)行操作只會(huì)有很少

的性能開銷。基于此點(diǎn), ng-repeat 和 ng-transclude 等內(nèi)置指令會(huì)在這個(gè)時(shí)候,也就是還未與

任何作用域數(shù)據(jù)進(jìn)行綁定時(shí)對(duì)DOM進(jìn)行操作。

以 ng-repeat 為例,它會(huì)遍歷指定的數(shù)組或?qū)ο螅跀?shù)據(jù)綁定之前構(gòu)建出對(duì)應(yīng)的DOM結(jié)構(gòu)。

如果我們用 ng-repeat 來創(chuàng)建無序列表,其中的每一個(gè) li 都會(huì)被 ng-click 指令所修飾,這

個(gè)過程會(huì)使得性能比手動(dòng)創(chuàng)建列表要快得多,尤其是列表中含有上百個(gè)元素時(shí)。

與克隆 li 元素,再將其與數(shù)據(jù)進(jìn)行鏈接,然后對(duì)每個(gè)元素都循環(huán)進(jìn)行此操作的過程不同,

我們僅需要先將無需列表構(gòu)造出來,然后將新的DOM(編譯后的DOM)傳遞給指令生命周期中

的下一個(gè)階段,即鏈接階段。

一個(gè)指令的表現(xiàn)一旦編譯完成,馬上就可以通過編譯函數(shù)對(duì)其進(jìn)行訪問,編譯函數(shù)的簽名包

含有訪問指令聲明所在的元素( tElemente )及該元素其他屬性( tAttrs )的方法。這個(gè)編譯函

數(shù)返回前面提到的模板函數(shù),其中含有完整的解析樹。

這里的重點(diǎn)是,由于每個(gè)指令都可以有自己的模板和編譯函數(shù),每個(gè)模板返回的也都是自己

的模板函數(shù)。鏈條頂部的指令會(huì)將內(nèi)部子指令的模板合并在一起成為一個(gè)模板函數(shù)并返回,但在

樹的內(nèi)部,只能通過模板函數(shù)訪問其所處的分支。

最后,模板函數(shù)被傳遞給編譯后的DOM樹中每個(gè)指令定義規(guī)則中指定的鏈接函數(shù),

compile (對(duì)象或函數(shù))

compile 選項(xiàng)可以返回一個(gè)對(duì)象或函數(shù)。

理解 compile 和 link 選項(xiàng)是AngularJS中需要深入討論的高級(jí)話題之一,對(duì)于了解AngularJS

究竟是如何工作的至關(guān)重要。

compile 選項(xiàng)本身并不會(huì)被頻繁使用,但是 link 函數(shù)則會(huì)被經(jīng)常使用。本質(zhì)上,當(dāng)我們?cè)O(shè)置

了 link 選項(xiàng),實(shí)際上是創(chuàng)建了一個(gè) postLink() 鏈接函數(shù),以便 compile() 函數(shù)可以定義鏈接函數(shù)。

通常情況下,如果設(shè)置了 compile 函數(shù),說明我們希望在指令和實(shí)時(shí)數(shù)據(jù)被放到DOM中之前

進(jìn)行DOM操作,在這個(gè)函數(shù)中進(jìn)行諸如添加和刪除節(jié)點(diǎn)等DOM操作是安全的。

compile 和 link 選項(xiàng)是互斥的。如果同時(shí)設(shè)置了這兩個(gè)選項(xiàng),那么會(huì)把 compile

所返回的函數(shù)當(dāng)作鏈接函數(shù),而 link 選項(xiàng)本身則會(huì)被忽略。

// ...compile: function(tEle, tAttrs, transcludeFn) {var tplEl = angular.element('

' +'

' +'

');var h2 = tplEl.find('h2');h2.attr('type', tAttrs.type);h2.attr('ng-model', tAttrs.ngModel);h2.val("hello");tEle.replaceWith(tplEl);return function(scope, ele, attrs) {// 連接函數(shù)};}//...

如果模板被克隆過,那么模板實(shí)例和鏈接實(shí)例可能是不同的對(duì)象。因此在編譯函數(shù)內(nèi)部,我

們只能轉(zhuǎn)換那些可以被安全操作的克隆DOM節(jié)點(diǎn)。不要進(jìn)行DOM事件監(jiān)聽器的注冊(cè):這個(gè)操作

應(yīng)該在鏈接函數(shù)中完成。

編譯函數(shù)負(fù)責(zé)對(duì)模板DOM進(jìn)行轉(zhuǎn)換。

鏈接函數(shù)負(fù)責(zé)將作用域和DOM進(jìn)行鏈接。在作用域同DOM鏈接之前可以手動(dòng)操作DOM。在

實(shí)踐中,編寫自定義指令時(shí)這種操作是非常罕見的,但有幾個(gè)內(nèi)置指令提供了這樣的功能。了解

這個(gè)流程對(duì)于理解AngularJS真正的工作方式很有幫助。

鏈接

用 link 函數(shù)創(chuàng)建可以操作DOM的指令。

鏈接函數(shù)是可選的。如果定義了編譯函數(shù),它會(huì)返回鏈接函數(shù),因此當(dāng)兩個(gè)函數(shù)都定義了時(shí),

編譯函數(shù)會(huì)重載鏈接函數(shù)。如果我們的指令很簡單,并且不需要額外的設(shè)置,可以從工廠函數(shù)(回

調(diào)函數(shù))返回一個(gè)函數(shù)來代替對(duì)象。如果這樣做了,這個(gè)函數(shù)就是鏈接函數(shù)。

下面兩種定義指令的方式在功能上是完全一樣的:

angular.module('myApp', [])

.directive('myDirective', function() {

return {

pre: function(tElement, tAttrs, transclude) {

// 在子元素被鏈接之前執(zhí)行

// 在這里進(jìn)行Don轉(zhuǎn)換不安全

// 之后調(diào)用'lihk'h函數(shù)將無法定位要鏈接的元素

},

post: function(scope, iElement, iAttrs, controller) {

// 在子元素被鏈接之后執(zhí)行

// 如果在這里省略掉編譯選項(xiàng)

//在這里執(zhí)行DOM轉(zhuǎn)換和鏈接函數(shù)一樣安全嗎

}

};

});

angular.module('myApp', [])

.directive('myDirective', function() {

return {

link: function(scope, ele, attrs) {

return {

pre: function(tElement, tAttrs, transclude) {

// 在子元素被鏈接之前執(zhí)行

// 在這里進(jìn)行Don轉(zhuǎn)換不安全

// 之后調(diào)用'lihk'h函數(shù)將無法定位要鏈接的元素

},

post: function(scope, iElement, iAttrs, controller) {

// 在子元素被鏈接之后執(zhí)行

// 如果在這里省略掉編譯選項(xiàng)

//在這里執(zhí)行DOM轉(zhuǎn)換和鏈接函數(shù)一樣安全嗎

}

}

}

});

當(dāng)定義了編譯函數(shù)來取代鏈接函數(shù)時(shí),鏈接函數(shù)是我們能提供給返回對(duì)象的第二個(gè)方法,也

就是 postLink 函數(shù)。本質(zhì)上講,這個(gè)事實(shí)正說明了鏈接函數(shù)的作用。它會(huì)在模板編譯并同作用域

進(jìn)行鏈接后被調(diào)用,因此它負(fù)責(zé)設(shè)置事件監(jiān)聽器,監(jiān)視數(shù)據(jù)變化和實(shí)時(shí)的操作DOM。

link 函數(shù)對(duì)綁定了實(shí)時(shí)數(shù)據(jù)的DOM具有控制能力,因此需要考慮性能問題。在選擇是用編譯函數(shù)還是鏈接函數(shù)實(shí)現(xiàn)功能時(shí),將性能影響考慮進(jìn)去。

鏈接函數(shù)的簽名如下:

link: function(scope, element, attrs) {

// 在這里操作DOM

}

如果指令定義中有 require 選項(xiàng),函數(shù)簽名中會(huì)有第四個(gè)參數(shù),代表控制器或者所依賴的指

令的控制器。

// require 'SomeController',

link: function(scope, element, attrs, SomeController) {

// 在這里操作DOM,可以訪問required指定的控制器

}

如果 require 選項(xiàng)提供了一個(gè)指令數(shù)組,第四個(gè)參數(shù)會(huì)是一個(gè)由每個(gè)指令所對(duì)應(yīng)的控制器組

成的數(shù)組。

下面看一下鏈接函數(shù)中的參數(shù):

scope

指令用來在其內(nèi)部注冊(cè)監(jiān)聽器的作用域。

iElement

iElement 參數(shù)代表實(shí)例元素,指使用此指令的元素。在 postLink 函數(shù)中我們應(yīng)該只操作此

元素的子元素,因?yàn)樽釉匾呀?jīng)被鏈接過了。

iAttrs

iAttrs 參數(shù)代表實(shí)例屬性,是一個(gè)由定義在元素上的屬性組成的標(biāo)準(zhǔn)化列表,可以在所有指

令的鏈接函數(shù)間共享。會(huì)以JavaScript對(duì)象的形式進(jìn)行傳遞。

controller

controller 參數(shù)指向 require 選項(xiàng)定義的控制器。如果沒有設(shè)置 require 選項(xiàng),那么

controller 參數(shù)的值為 undefined 。

控制器在所有的指令間共享,因此指令可以將控制器當(dāng)作通信通道(公共API)。如果設(shè)置了

多個(gè) require ,那么這個(gè)參數(shù)會(huì)是一個(gè)由控制器實(shí)例組成的數(shù)組,而不只是一個(gè)單獨(dú)的控制器。

二.知識(shí)剖析

三.常見問題

四.解決方案

五.代碼實(shí)戰(zhàn)

六.拓展思考

為什么要使用封裝的自定義指令?

封裝的意思是說對(duì)象數(shù)據(jù)和操作該數(shù)據(jù)的指令都是對(duì)象自身的一部分,封裝能夠?qū)崿F(xiàn)盡可能對(duì)外部世界隱藏?cái)?shù)據(jù)。封裝簡單的說能屏蔽方法的復(fù)雜性,比如只要知道方法的參數(shù)類型就可以使用方法,再說降低模塊之間的耦合性,就是模塊之間的聯(lián)系,使之相互獨(dú)立,能提高系統(tǒng)的健壯性

七.參考文獻(xiàn)

angular 自定義指令詳解 Directive

學(xué)習(xí)AngularJs:Directive指令用法(完整版)

八.更多討論

為什么要使用封裝的自定義指令?

鳴謝

感謝大家觀看

BY : 徐恒



1.link函數(shù)和controller區(qū)別

2.ng-app和ng-controller聯(lián)系

3.replace和transclude區(qū)別


------------------------------------------------------------------------------------------------------------------------

技能樹.IT修真院

“我們相信人人都可以成為一個(gè)工程師,現(xiàn)在開始,找個(gè)師兄,帶你入門,掌控自己學(xué)習(xí)的節(jié)奏,學(xué)習(xí)的路上不再迷茫”。

這里是技能樹.IT修真院,成千上萬的師兄在這里找到了自己的學(xué)習(xí)路線,學(xué)習(xí)透明化,成長可見化,師兄1對(duì)1免費(fèi)指導(dǎo)。快來與我一起學(xué)習(xí)吧 !

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 指令定義 對(duì)于指令,可以把它簡單的理解成在特定DOM元素上運(yùn)行的函數(shù),指令可以擴(kuò)展這個(gè)元素的功能。??我們可以自己...
    oWSQo閱讀 1,206評(píng)論 0 0
  • AngularJS是什么?AngularJs(后面就簡稱ng了)是一個(gè)用于設(shè)計(jì)動(dòng)態(tài)web應(yīng)用的結(jié)構(gòu)框架。首先,它是...
    200813閱讀 1,646評(píng)論 0 3
  • 這個(gè)文檔解釋了何時(shí)你可能會(huì)想要在你的AngularJS應(yīng)用中創(chuàng)建自定義指令,以及如何實(shí)現(xiàn)它們 指令是什么?## 站...
    mochase閱讀 1,879評(píng)論 1 12
  • 原創(chuàng)性聲明:本文完全為筆者原創(chuàng),請(qǐng)尊重筆者勞動(dòng)力。轉(zhuǎn)載務(wù)必注明原文地址。 指令詳解 一個(gè)指令的定義應(yīng)當(dāng)是如下這個(gè)樣...
    東方一號(hào)藍(lán)閱讀 1,971評(píng)論 0 1
  • 以前去電影院觀看電影,都喜歡看歐美科技大片,有震感高科技元素在里面,顯得個(gè)人英雄味道比較濃厚。 但是觀看了戰(zhàn)狼2以...
    阿Q美食記閱讀 229評(píng)論 0 1