Angular學(xué)習(xí)筆記(5)—指令詳解

指令定義

對于指令疾嗅,可以把它簡單的理解成在特定DOM元素上運(yùn)行的函數(shù)黍衙,指令可以擴(kuò)展這個元素的功能。
??我們可以自己創(chuàng)造新的指令料扰。directive()這個方法是用來定義指令的:

angular.module('myApp', [])
    .directive('myDirective', function ($timeout, UserDefinedService) {
        // 指令定義放在這里
    });

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

  • name(字符串)
    指令的名字凭豪,用來在視圖中引用特定的指令。要用駝峰式寫法晒杈。
  • factory_function (函數(shù))
    這個函數(shù)返回一個對象嫂伞,其中定義了指令的全部行為。$compile服務(wù)利用這個方法返回的對象,在DOM調(diào)用指令時來構(gòu)造指令的行為帖努。
angular.application('myApp', [])
    .directive('myDirective', function() {
        return {
            // 通過設(shè)置項(xiàng)來定義指令撰豺,在這里進(jìn)行覆寫
        };
    });

我們也可以返回一個函數(shù)代替對象來定義指令,但是像上面的例子一樣拼余,通過對象來定義是最佳的方式污桦。當(dāng)返回一個函數(shù)時,這個函數(shù)通常被稱作鏈接傳遞函數(shù)匙监,利用它我們可以定義指令的鏈接功能凡橱。由于返回函數(shù)而不是對象會限制定義指令時的自由度,因此只在構(gòu)造簡單的指令時才比較有用亭姥。
??當(dāng)AngularJS啟動應(yīng)用時稼钩,它會把第一個參數(shù)當(dāng)作一個字符串,并以此字符串為名來注冊第二個參數(shù)返回的對象达罗。AngularJS編譯器會解析主HTML的DOM中的元素坝撑、屬性、注釋和CSS類名中使用了這個名字的地方氮块,并在這些地方引用對應(yīng)的指令。當(dāng)它找到某個已知的指令時诡宗,就會在頁面中插入指令所對應(yīng)的DOM元素滔蝉。

<div my-directive></div>

指令的工廠函數(shù)只會在編譯器第一次匹配到這個指令時調(diào)用一次。和controller函數(shù)類似塔沃,我們通過$injetor.invoke來調(diào)用指令的工廠函數(shù)蝠引。當(dāng)AngularJS在DOM中遇到具名的指令時,會去匹配已經(jīng)注冊過的指令蛀柴,并通過名字在注冊過的對象中查找螃概。此時,就開始了一個指令的生命周期鸽疾,指令的生命周期開始于$compile方法并結(jié)束于link方法吊洼。
??定義一個指令時可以使用的全部設(shè)置選項(xiàng):

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: // 返回一個對象或連接函數(shù),如下所示:
               function(tElement, tAttrs, transclude) {
                   return {
                       pre: function(scope, iElement, iAttrs, controller) { ... },
                       post: function(scope, iElement, iAttrs, controller) { ... }
                   }
                   // 或者
                   return function postLink(...) { ... }
               }
           };
       });

restrict(字符串)

restrict是一個可選參數(shù)制肮。它告訴AngularJS這個指令在DOM中可以何種形式被聲明冒窍。默認(rèn)restrict的值是A,即以屬性的形式來進(jìn)行聲明豺鼻。
可選值如下:

  • E(元素)
<my-directive></my-directive>
  • A(屬性综液,默認(rèn)值)
<div my-directive="expression"></div>
  • C(類名)
<div class="my-directive:expression;"></div>
  • M(注釋)
<--directive:my-directive expression-->

這些選項(xiàng)可以單獨(dú)使用,也可以混合在一起使用:

angular.module('myDirective', function(){
    return {
        restrict: 'EA' // 輸入元素或?qū)傩?    };
});

上面的配置可以同時用屬性或元素的方式來聲明指令:

<-- 作為一個屬性 -->
<div my-directive></div>
<-- 或者作為一個元素 -->
<my-directive></my-directive>

屬性是用來聲明指令最常用的方式儒飒,因?yàn)樗茉诎ɡ习姹镜腎E瀏覽器在內(nèi)的所有瀏覽器中正常工作谬莹,并且不需要在文檔頭部注冊新的標(biāo)簽。

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

優(yōu)先級參數(shù)可以被設(shè)置為一個數(shù)值。大多數(shù)指令會忽略這個參數(shù)附帽,使用默認(rèn)值0埠戳,但也有些場景設(shè)置高優(yōu)先級是非常重要的。
??如果一個元素上具有兩個優(yōu)先級相同的指令士葫,聲明在前面的那個會被優(yōu)先調(diào)用乞而。具有更高優(yōu)先級的指令總是優(yōu)先運(yùn)行。
??ngRepeat是所有內(nèi)置指令中優(yōu)先級最高的慢显,它總是在其他指令之前運(yùn)行爪模。

terminal(布爾型)

這個參數(shù)用來告訴AngularJS停止運(yùn)行當(dāng)前元素上比本指令優(yōu)先級低的指令。但同當(dāng)前指令優(yōu)先級相同的指令還是會被執(zhí)行荚藻。
??如果元素上某個指令設(shè)置了terminal參數(shù)并具有較高的優(yōu)先級屋灌,就不要再用其他低優(yōu)先級的指令對其進(jìn)行修飾了,因?yàn)椴粫徽{(diào)用应狱。但是具有相同優(yōu)先級的指令還是會被繼續(xù)調(diào)用共郭。
??使用了terminal參數(shù)的例子是ngViewngIfngIf的優(yōu)先級略高于ngView疾呻,如果ngIf的表達(dá)式值為true除嘹,ngView就可以被正常執(zhí)行,但如果ngIf表達(dá)式的值為false岸蜗,由于ngView的優(yōu)先級較低就不會被執(zhí)行尉咕。

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

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

  • 一段HTML文本璃岳;
  • 一個可以接受兩個參數(shù)的函數(shù)年缎,參數(shù)為tElementtAttrs,并返回一個代表模板的字符串铃慷。tElementtAttrs中的t代表template单芜,是相對于instance的。

AngularJS會同處理HTML一樣處理模板字符串犁柜。模板中可以通過大括號標(biāo)記來訪問作用域洲鸠,例如{{ expression }}。
如果模板字符串中含有多個DOM元素馋缅,或者只由一個單獨(dú)的文本節(jié)點(diǎn)構(gòu)成坛怪,那它必須被包含在一個父元素內(nèi)。換句話說股囊,必須存在一個根DOM元素:

template: '\
    <div> <-- single root element -->\
    <a >Click me</a>\
    <h1>When using two elements, wrap them in a parent element</h1>\
</div>\

另外袜匿,注意每一行末尾的反斜線,這樣AngularJS才能正確解析多行字符串稚疹。在實(shí)際生產(chǎn)中居灯,更好的選擇是使用templateUrl參數(shù)引用外部模板祭务。

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

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

  • 一個代表外部HTML文件路徑的字符串怪嫌;
  • 一個可以接受兩個參數(shù)的函數(shù)义锥,參數(shù)為tElementtAttrs,并返回一個外部HTML文件路徑的字符串岩灭。

默認(rèn)情況下拌倍,調(diào)用指令時會在后臺通過Ajax來請求HTML模板文件。有兩件事情需要知道噪径。

  • 在本地開發(fā)時柱恤,需要在后臺運(yùn)行一個本地服務(wù)器,用以從文件系統(tǒng)加載HTML模板找爱,否則會導(dǎo)致Cross Origin Request Script(CORS)錯誤梗顺。
  • 模板加載是異步的,意味著編譯和鏈接要暫停车摄,等待模板加載完成寺谤。

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

replace(布爾型)

replace是一個可選參數(shù)粟关,如果設(shè)置了這個參數(shù),值必須為true摄职,因?yàn)槟J(rèn)值為false誊役。默認(rèn)值意味著模板會被當(dāng)作子元素插入到調(diào)用此指令的元素內(nèi)部:

<div some-directive></div>
.directive('someDirective', function() {
    return {
        template: '<div>some stuff here<div>'
    };
});
<!--調(diào)用指令之后的結(jié)果如下(默認(rèn)replace為false時的情況):-->
<div some-directive>
    <div>some stuff here<div>
</div>

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

.directive('someDirective', function() {
    return {
        replace: true 
        template: '<div>some stuff here<div>'
    };
});
<!--指令調(diào)用后的結(jié)果將是:-->
<div>some stuff here<div>

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

scope參數(shù)是可選的获列,可以被設(shè)置為true或一個對象谷市。默認(rèn)值是false。當(dāng)scope設(shè)置為true時击孩,會從父作用域繼承并創(chuàng)建一個新的作用域?qū)ο蟆?br> ??如果一個元素上有多個指令使用了隔離作用域迫悠,其中只有一個可以生效。只有指令模板中的根元素可以獲得一個新的作用域巩梢。因此创泄,對于這些對象來說scope默認(rèn)被設(shè)置為true。
??內(nèi)置指令ng-controller的作用括蝠,就是從父級作用域繼承并創(chuàng)建一個新的子作用域鞠抑。它會創(chuàng)建一個新的從父作用域繼承而來的子作用域。
??為了進(jìn)一步證明作用域的繼承機(jī)制是向下而非向上進(jìn)行的忌警,下面的例子展示的是{{aThirdProperty}}從父作用域繼承而來:

<!--HTML-->
<div ng-app="myApp" ng-init="someProperty = 'some data'"></div>
<div ng-init="siblingProperty='moredata'">
    Inside Div Two: {{aThirdProperty}}
    <div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
        Inside Div Three: {{aThirdProperty}}
        <div ng-controller="SecondController">
            Inside Div Four: {{aThirdProperty}}
        </div>
    </div>
</div>
<!--JS-->
angular.module('myApp', [])
    .controller('SomeController', function($scope) {
        // 可以留空搁拙,但需要被定義
    })
    .controller('SecondController', function($scope) {
        // 同樣可以留空
    })

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

angular.module('myApp', [])
    .directive('myDirective', function() {
        return {
            restrict: 'A',
            scope: true
        };
    });

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

<body ng-app="myApp">
<div ng-app="myApp" ng-init="someProperty = 'some data'">
<div ng-init="siblingProperty='moredata'">
    Inside Div Two: {{aThirdProperty}}
    <div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
        Inside Div Three: {{aThirdProperty}}
        <div ng-controller="SecondController">
            Inside Div Four: {{aThirdProperty}}
            <br>
            Outside myDirective: {{myProperty}}
            <div my-directive ng-init="myProperty = 'wow, this is cool'">
                Inside myDirective: {{myProperty}}
            </div>
        </div>
    </div>
</div>
</div>
</body>

隔離作用域

具有隔離作用域的指令最主要的使用場景是創(chuàng)建可復(fù)用的組件,組件可以在未知上下文中使用箕速,并且可以避免污染所處的外部作用域或不經(jīng)意地污染內(nèi)部作用域酪碘。
??創(chuàng)建具有隔離作用域的指令需要將scope屬性設(shè)置為一個空對象{}。如果這樣做了盐茎,指令的模板就無法訪問外部作用域了:

<div ng-controller='MainController'>
    Outside myDirective: {{myProperty}}
    <div my-directive ng-init="myProperty = 'wow, this is cool'">
        Inside myDirective: {{myProperty}}
    </div>
</div>
angular.module('myApp', [])
    .controller('MainController', function($scope) {
    })
    .directive('myDirective', function() {
        return {
            restrict: 'A',
            scope: {},
            priority: 100,
            template: '<div>Inside myDirective {{myProperty}}</div>'
        };
    });

注意兴垦,這里為myDirective設(shè)置了一個高優(yōu)先級。由于ngInit指令會以非零的優(yōu)先級運(yùn)行字柠,這個例子將會優(yōu)先運(yùn)行ngInit指令探越,然后才是我們定義的指定,并且這個myProperty$scope對象中是有效的募谎。
??示例代碼的效果與將scope設(shè)置為true幾乎是相同的扶关。下面看一下使用繼承作用域的指令的例子,對比一下二者:

<div ng-init="myProperty='wow,thisiscool'">
    Surrounding scope: {{ myProperty }}
    <div my-inherit-scope-directive></div>
    <div my-directive></div>
</div>

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
        };
    });

綁定策略

使用無數(shù)據(jù)的隔離作用域并不常見数冬。AngularJS提供了幾種方法能夠?qū)⒅噶顑?nèi)部的隔離作用域节槐,同指令外部的作用域進(jìn)行數(shù)據(jù)綁定。
??為了讓新的指令作用域可以訪問當(dāng)前本地作用域中的變量拐纱,需要使用下面三種別名中的一種铜异。
??本地作用域?qū)傩裕菏褂聾符號將本地作用域同DOM屬性的值進(jìn)行綁定。指令內(nèi)部作用域可以使用外部作用域的變量:

  • @ (or @attr)
    現(xiàn)在秸架,可以在指令中使用綁定的字符串了揍庄。
    雙向綁定:通過=可以將本地作用域上的屬性同父級作用域上的屬性進(jìn)行雙向的數(shù)據(jù)綁定。就像普通的數(shù)據(jù)綁定一樣东抹,本地屬性會反映出父數(shù)據(jù)模型中所發(fā)生的改變蚂子。
  • = (or =attr)
    父級作用域綁定 通過&符號可以對父級作用域進(jìn)行綁定,以便在其中運(yùn)行函數(shù)缭黔。意味著對這個值進(jìn)行設(shè)置時會生成一個指向父級作用域的包裝函數(shù)食茎。
    要使調(diào)用帶有一個參數(shù)的父方法,我們需要傳遞一個對象馏谨,這個對象的鍵是參數(shù)的名稱别渔,值是要傳遞給參數(shù)的內(nèi)容。
  • & (or &attr)

例如惧互,假設(shè)我們在開發(fā)一個電子郵件客戶端哎媚,并且要創(chuàng)建一個電子郵件的文本輸入框:

<input type="text" ng-model="to"/>
<!-- 調(diào)用指令 -->
<div scope-example ng-model="to" on-send="sendMail(email)" from-name="ari@fullstack.io" ></div>

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

scope: {
    ngModel: '=', // 將ngModel同指定對象綁定
    onSend: '&', // 將引用傳遞給這個方法
    fromName: '@' // 儲存與fromName相關(guān)聯(lián)的字符串
}

transclude

transclude是一個可選的參數(shù)。如果設(shè)置了喊儡,其值必須為true拨与,它的默認(rèn)值是false。
??嵌入通常用來創(chuàng)建可復(fù)用的組件艾猜,典型的例子是模態(tài)對話框或?qū)Ш綑凇?br> ??我們可以將整個模板买喧,包括其中的指令通過嵌入全部傳入一個指令中攀甚。這樣做可以將任意內(nèi)容和作用域傳遞給指令。transclude參數(shù)就是用來實(shí)現(xiàn)這個目的的岗喉,指令的內(nèi)部可以訪問外部指令的作用域秋度,并且模板也可以訪問外部的作用域?qū)ο蟆?br> ??為了將作用域傳遞進(jìn)去,scope參數(shù)的值必須通過{}或true設(shè)置成隔離作用域钱床。如果沒有設(shè)置scope參數(shù)荚斯,那么指令內(nèi)部的作用域?qū)⒈辉O(shè)置為傳入模板的作用域。
??只有當(dāng)你希望創(chuàng)建一個可以包含任意內(nèi)容的指令時查牌,才使用transclude: true事期。
??嵌入允許指令的使用者方便地提供自己的HTML模板,其中可以包含獨(dú)特的狀態(tài)和行為纸颜,并對指令的各方面進(jìn)行自定義兽泣。
??下面一起來實(shí)現(xiàn)個小例子,創(chuàng)建一個可以被自定義的可復(fù)用指令胁孙。
??例如唠倦,假設(shè)我們想創(chuàng)建一個包括標(biāo)題和少量HTML內(nèi)容的側(cè)邊欄,如下所示:

<div sideboxtitle="Links">
    <ul>
        <li>First link</li>
        <li>Second link</li>
    </ul>
</div>
angular.module('myApp', [])
    .directive('sidebox', function() {
        return {
            restrict: 'EA',
            scope: {
                title: '@'
            },
            transclude: true,
            template: '<div class="sidebox">\
                <div class="content">\
                <h2 class="header">{{ title }}</h2>\
                <span class="content" ng-transclude></span>\
                </div>\
                </div>'
        };
    });

這段代碼告訴AngularJS編譯器涮较,將它從DOM元素中獲取的內(nèi)容放到它發(fā)現(xiàn)ng-transclude指令的地方稠鼻。
??借助transclusion,我們可以將指令復(fù)用到第二個元素上狂票,而無須擔(dān)心樣式和布局的一致性問題候齿。
??例如,下面的代碼會產(chǎn)生兩個樣式完全一致的側(cè)邊欄闺属。

<div sideboxtitle="Links">
    <ul>
        <li>First link</li>
        <li>Second link</li>
    </ul>
</div>
<div sideboxtitle="TagCloud">
    <div class="tagcloud">
        <a href="">Graphics</a>
        <a href="">AngularJS</a>
        <a href="">D3</a>
        <a href="">Front-end</a>
        <a href="">Startup</a>
    /div>
</div>

如果指令使用了transclude參數(shù)慌盯,那么在控制器中就無法正常監(jiān)聽數(shù)據(jù)模型的變化了。這就是最佳實(shí)踐總是建議在鏈接函數(shù)里使用$watch服務(wù)的原因掂器。

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

controller參數(shù)可以是一個字符串或一個函數(shù)亚皂。當(dāng)設(shè)置為字符串時,會以字符串的值為名字唉匾,來查找注冊在應(yīng)用中的控制器的構(gòu)造函數(shù):

angular.module('myApp', [])
    .directive('myDirective', function() {
        restrict: 'A', // 始終需要
        controller: 'SomeController'
    })
// 應(yīng)用中其他的地方孕讳,可以是同一個文件或被index.html包含的另一個文件
angular.module('myApp')
    .controller('SomeController', function($scope, $element, $attrs, $transclude) {
        // 控制器邏輯放在這里
    });

可以在指令內(nèi)部通過匿名構(gòu)造函數(shù)的方式來定義一個內(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)前指令對應(yīng)的元素肪康。
  3. $attrs
    由當(dāng)前元素的屬性組成的對象荚恶。例如,下面的元素:
    <div id="aDiv"class="box"></div>
    具有如下的屬性對象:
{
    id: "aDiv",
    class: "box"
}
  1. $transclude
    嵌入鏈接函數(shù)會與對應(yīng)的嵌入作用域進(jìn)行預(yù)綁定磷支。
    transclude鏈接函數(shù)是實(shí)際被執(zhí)行用來克隆元素和操作DOM的函數(shù)谒撼。

在控制器內(nèi)部操作DOM是和AngularJS風(fēng)格相悖的做法,但通過鏈接函數(shù)就可以實(shí)現(xiàn)這個需求雾狈。僅在compile參數(shù)中使用transcludeFn是推薦的做法廓潜。
??例如,我們想要通過指令來添加一個超鏈接標(biāo)簽善榛”绲埃可以在控制器內(nèi)的$transclude函數(shù)中實(shí)現(xiàn),如下所示:

angular.module('myApp')
    .directive('link', function() {
        return {
            restrict: 'EA',
            transclude: true,
            controller:
            function($scope, $element, $transclude, $log) {
                $transclude(function(clone) {
                    var a = angular.element('<a>');
                    a.attr('href', clone.text());
                    a.text(clone.text());
                    $log.info("Created new a tag in link directive");
                    $element.append(a);
                });
            }
        };
    });

指令的控制器和link函數(shù)可以進(jìn)行互換移盆〉吭海控制器主要是用來提供可在指令間復(fù)用的行為,但鏈接函數(shù)只能在當(dāng)前內(nèi)部指令中定義行為咒循,且無法在指令間復(fù)用据途。
??link函數(shù)可以將指令互相隔離開來,而controller則定義可復(fù)用的行為叙甸。
??由于指令可以require其他指令所使用的控制器昨凡,因此控制器常被用來放置在多個指令間共享的動作。
??如果我們希望將當(dāng)前指令的API暴露給其他指令使用蚁署,可以使用controller參數(shù)便脊,否則可以使用link來構(gòu)造當(dāng)前指令元素的功能性。如果我們使用了scope.$watch()或者想要與DOM元素做實(shí)時的交互光戈,使用鏈接會是更好的選擇哪痰。
??技術(shù)上講,$scope會在DOM元素被實(shí)際渲染之前傳入到控制器中久妆。在某些情況下晌杰,例如使用了嵌入,控制器中的作用域所反映的作用域可能與我們所期望的不一樣筷弦,這種情況下肋演,$scope對象無法保證可以被正常更新。
??當(dāng)想要同當(dāng)前屏幕上的作用域交互時烂琴,可以使用被傳入到link函數(shù)中的scope參數(shù)作儿。

controllerAs(字符串)

controllerAs參數(shù)用來設(shè)置控制器的別名,可以以此為名來發(fā)布控制器锅铅,并且作用域可以訪問controllerAs借浊。這樣就可以在視圖中引用控制器,甚至無需注入$scope号醉。
??例如反症,創(chuàng)建一個MainController辛块,然后不要注入$scope,如下所示:

angular.module('myApp')
.controller('MainController', function() {
this.name = "Ari";
});

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

<div ng-app ng-controller="MainController">
<input type="text" ng-model="main.name" />
<span>{{ main.name }}</span>
</div>

這個參數(shù)看起來好像沒什么大用,但它給了我們可以在路由和指令中創(chuàng)建匿名控制器的強(qiáng)大能力胞谈。這種能力可以將動態(tài)的對象創(chuàng)建成為控制器授药,并且這個對象是隔離的、易于測試的呜魄。
例如悔叽,可以在指令中創(chuàng)建匿名控制器,如下所示:

angular.module('myApp')
.directive('myDirective', function() {
return {
restrict: 'A',
template: '<h4>{{ myController.msg }}</h4>',
controllerAs: 'myController',
controller: function() {
this.msg = "Hello World"
}
};
});

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

require參數(shù)可以被設(shè)置為字符串或數(shù)組爵嗅,字符串代表另外一個指令的名字娇澎。require會將控制器注入到其值所指定的指令中,并作為當(dāng)前指令的鏈接函數(shù)的第四個參數(shù)睹晒。
??字符串或數(shù)組元素的值是會在當(dāng)前指令的作用域中使用的指令名稱趟庄。
??scope會影響指令作用域的指向,是一個隔離作用域伪很,一個有依賴的作用域或者完全沒有作用域戚啥。在任何情況下,AngularJS編譯器在查找子控制器時都會參考當(dāng)前指令的模板锉试。
??如果不使用^前綴猫十,指令只會在自身的元素上查找控制器。

//...
restrict: 'EA',
require: 'ngModel'
//...

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

<!-- 指令會在本地作用域查找ng-model -->
<div my-directive ng-model="object"></div>

require參數(shù)的值可以用下面的前綴進(jìn)行修飾拖云,這會改變查找控制器時的行為:
?
如果在當(dāng)前指令中沒有找到所需要的控制器,會將null作為傳給link函數(shù)的第四個參數(shù)应又。
^
如果添加了^前綴宙项,指令會在上游的指令鏈中查找require參數(shù)所指定的控制器。
?^
將前面兩個選項(xiàng)的行為組合起來株扛,我們可選擇地加載需要的指令并在父指令鏈中進(jìn)行查找尤筐。
沒有前綴
如果沒有前綴,指令將會在自身所提供的控制器中進(jìn)行查找洞就,如果沒有找到任何控制器(或具有指定名字的指令)就拋出一個錯誤盆繁。

AngularJS 的生命周期

在AngularJS應(yīng)用起動前,它們以HTML文本的形式保存在文本編輯器中奖磁。應(yīng)用啟動后會進(jìn)行編譯和鏈接改基,作用域會同HTML進(jìn)行綁定繁疤,應(yīng)用可以對用戶在HTML中進(jìn)行的操作進(jìn)行實(shí)時響應(yīng)咖为。
在這個過程中總共有兩個主要階段秕狰。

編譯階段

第一個階段是編譯階段。在編譯階段躁染,AngularJS會遍歷整個HTML文檔并根據(jù)JavaScript中的指令定義來處理頁面上聲明的指令鸣哀。
??每一個指令的模板中都可能含有另外一個指令,另外一個指令也可能會有自己的模板吞彤。當(dāng)AngularJS調(diào)用HTML文檔根部的指令時我衬,會遍歷其中所有的模板,模板中也可能包含帶有模板的指令饰恕。
??模板樹可能又大又深挠羔,但有一點(diǎn)需要注意,盡管元素可以被多個指令所支持或修飾埋嵌,這些指令本身的模板中也可以包含其他指令破加,但只有屬于最高優(yōu)先級指令的模板會被解析并添加到模板樹中。這里有一個建議雹嗦,就是將包含模板的指令和添加行為的指令分離開來范舀。如果一個元素已經(jīng)有一個含有模板的指令了,永遠(yuǎn)不要對其用另一個指令進(jìn)行修飾了罪。只有具有最高優(yōu)先級的指令中的模板會被編譯锭环。
??一旦對指令和其中的子模板進(jìn)行遍歷或編譯,編譯后的模板會返回一個叫做模板函數(shù)的函數(shù)泊藕。我們有機(jī)會在指令的模板函數(shù)被返回前辅辩,對編譯后的DOM樹進(jìn)行修改。
??在這個時間點(diǎn)DOM樹還沒有進(jìn)行數(shù)據(jù)綁定娃圆,意味著如果此時對DOM樹進(jìn)行操作只會有很少的性能開銷汽久。基于此點(diǎn)踊餐,ng-repeatng-transclude等內(nèi)置指令會在這個時候景醇,也就是還未與任何作用域數(shù)據(jù)進(jìn)行綁定時對DOM進(jìn)行操作。
ng-repeat為例吝岭,它會遍歷指定的數(shù)組或?qū)ο笕担跀?shù)據(jù)綁定之前構(gòu)建出對應(yīng)的DOM結(jié)構(gòu)。
??如果我們用ng-repeat來創(chuàng)建無序列表窜管,其中的每一個<li>都會被ng-click指令所修飾散劫,這個過程會使得性能比手動創(chuàng)建列表要快得多,尤其是列表中含有上百個元素時幕帆。與克隆<li>元素获搏,再將其與數(shù)據(jù)進(jìn)行鏈接,然后對每個元素都循環(huán)進(jìn)行此操作的過程不同失乾,我們僅需要先將無需列表構(gòu)造出來常熙,然后將新的DOM(編譯后的DOM)傳遞給指令生命周期中的下一個階段纬乍,即鏈接階段。
??一個指令的表現(xiàn)一旦編譯完成裸卫,馬上就可以通過編譯函數(shù)對其進(jìn)行訪問仿贬,編譯函數(shù)的簽名包含有訪問指令聲明所在的元素(tElemente)及該元素其他屬性(tAttrs)的方法。這個編譯函數(shù)返回前面提到的模板函數(shù)墓贿,其中含有完整的解析樹茧泪。
??這里的重點(diǎn)是,由于每個指令都可以有自己的模板和編譯函數(shù)聋袋,每個模板返回的也都是自己的模板函數(shù)队伟。鏈條頂部的指令會將內(nèi)部子指令的模板合并在一起成為一個模板函數(shù)并返回,但在樹的內(nèi)部幽勒,只能通過模板函數(shù)訪問其所處的分支缰泡。
??最后,模板函數(shù)被傳遞給編譯后的DOM樹中每個指令定義規(guī)則中指定的鏈接函數(shù)代嗤。

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

compile選項(xiàng)可以返回一個對象或函數(shù)棘钞。
??compile選項(xiàng)本身并不會被頻繁使用,但是link函數(shù)則會被經(jīng)常使用干毅。本質(zhì)上宜猜,當(dāng)我們設(shè)置了link選項(xiàng),實(shí)際上是創(chuàng)建了一個postLink()鏈接函數(shù)硝逢,以便compile()函數(shù)可以定義鏈接函數(shù)姨拥。
??通常情況下,如果設(shè)置了compile函數(shù)渠鸽,說明我們希望在指令和實(shí)時數(shù)據(jù)被放到DOM中之前進(jìn)行DOM操作叫乌,在這個函數(shù)中進(jìn)行諸如添加和刪除節(jié)點(diǎn)等DOM操作是安全的。
??compilelink選項(xiàng)是互斥的徽缚。如果同時設(shè)置了這兩個選項(xiàng)憨奸,那么會把compile所返回的函數(shù)當(dāng)作鏈接函數(shù),而link選項(xiàng)本身則會被忽略凿试。

// ...
compile: function(tEle, tAttrs, transcludeFn) {
    var tplEl = angular.element('<div><h2></h2></div>');
    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í)例可能是不同的對象。因此在編譯函數(shù)內(nèi)部那婉,我們只能轉(zhuǎn)換那些可以被安全操作的克隆DOM節(jié)點(diǎn)板甘。不要進(jìn)行DOM事件監(jiān)聽器的注冊:這個操作應(yīng)該在鏈接函數(shù)中完成。
??編譯函數(shù)負(fù)責(zé)對模板DOM進(jìn)行轉(zhuǎn)換详炬。
??鏈接函數(shù)負(fù)責(zé)將作用域和DOM進(jìn)行鏈接盐类。在作用域同DOM鏈接之前可以手動操作DOM。在實(shí)踐中,編寫自定義指令時這種操作是非常罕見的在跳,但有幾個內(nèi)置指令提供了這樣的功能枪萄。

鏈接

link函數(shù)創(chuàng)建可以操作DOM的指令。
??鏈接函數(shù)是可選的硬毕。如果定義了編譯函數(shù)呻引,它會返回鏈接函數(shù)礼仗,因此當(dāng)兩個函數(shù)都定義了時吐咳,編譯函數(shù)會重載鏈接函數(shù)。如果我們的指令很簡單元践,并且不需要額外的設(shè)置韭脊,可以從工廠函數(shù)(回調(diào)函數(shù))返回一個函數(shù)來代替對象。如果這樣做了单旁,這個函數(shù)就是鏈接函數(shù)沪羔。
??下面兩種定義指令的方式在功能上是完全一樣的:

angular.module('myApp', [])
    .directive('myDirective', function() {
        return {
            pre: function(tElement, tAttrs, transclude) {
                // 在子元素被鏈接之前執(zhí)行
                // 在這里進(jìn)行Don轉(zhuǎn)換不安全
                // 之后調(diào)用'lihk'函數(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ù)是我們能提供給返回對象的第二個方法象浑,也就是postLink函數(shù)蔫饰。本質(zhì)上講,這個事實(shí)正說明了鏈接函數(shù)的作用愉豺。它會在模板編譯并同作用域進(jìn)行鏈接后被調(diào)用篓吁,因此它負(fù)責(zé)設(shè)置事件監(jiān)聽器,監(jiān)視數(shù)據(jù)變化和實(shí)時的操作DOM蚪拦。
??link函數(shù)對綁定了實(shí)時數(shù)據(jù)的DOM具有控制能力杖剪,因此需要考慮性能問題。
鏈接函數(shù)的簽名如下:

link: function(scope, element, attrs) {
// 在這里操作DOM
}

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

// require 'SomeController',
link: function(scope, element, attrs, SomeController) {
// 在這里操作DOM括袒,可以訪問required指定的控制器
}

如果require選項(xiàng)提供了一個指令數(shù)組次兆,第四個參數(shù)會是一個由每個指令所對應(yīng)的控制器組成的數(shù)組。
下面看一下鏈接函數(shù)中的參數(shù):

  • scope
    指令用來在其內(nèi)部注冊監(jiān)聽器的作用域锹锰。
  • iElement
    iElement參數(shù)代表實(shí)例元素类垦,指使用此指令的元素。在postLink函數(shù)中我們應(yīng)該只操作此元素的子元素城须,因?yàn)樽釉匾呀?jīng)被鏈接過了蚤认。
  • iAttrs
    iAttrs參數(shù)代表實(shí)例屬性,是一個由定義在元素上的屬性組成的標(biāo)準(zhǔn)化列表糕伐,可以在所有指令的鏈接函數(shù)間共享砰琢。會以JavaScript對象的形式進(jìn)行傳遞。
  • controller
    controller參數(shù)指向require選項(xiàng)定義的控制器。如果沒有設(shè)置require選項(xiàng)陪汽,那么controller參數(shù)的值為undefined训唱。
    控制器在所有的指令間共享,因此指令可以將控制器當(dāng)作通信通道(公共API)挚冤。如果設(shè)置了多個require况增,那么這個參數(shù)會是一個由控制器實(shí)例組成的數(shù)組,而不只是一個單獨(dú)的控制器训挡。

ngModel

ngModel是一個用法特殊的指令澳骤,它提供更底層的API來處理控制器內(nèi)的數(shù)據(jù)。當(dāng)我們在指令中使用ngModel時能夠訪問一個特殊的API澜薄,這個API用來處理數(shù)據(jù)綁定为肮、驗(yàn)證、CSS更新等不實(shí)際操作DOM的事情肤京。
??ngModel控制器會隨ngModel被一直注入到指令中颊艳,其中包含了一些方法。為了訪問ngModelController必須使用require設(shè)置:

angular.module('myApp')
    .directive('myDirective',function(){
        return {
            require: '?ngModel',
            link: function(scope, ele, attrs, ngModel) {
                if (!ngModel) return;
                // 現(xiàn)在我們的指令中已經(jīng)有ngModelController的一個實(shí)例
            }
        };
    });

如果不設(shè)置require選項(xiàng)忘分,ngModelController就不會被注入到指令中棋枕。
??注意,這個指令沒有隔離作用域妒峦。如果給這個指令設(shè)置隔離作用域重斑,將導(dǎo)致內(nèi)部ngModel無法更新外部ngModel的對應(yīng)值:AngularJS會在本地作用域以外查詢值。
??為了設(shè)置作用域中的視圖值舟山,需要調(diào)用ngModel.$setViewValue()函數(shù)绸狐。ngModel.$setViewValue()函數(shù)可以接受一個參數(shù)。
value(字符串):value參數(shù)是我們想要賦值給ngModel實(shí)例的實(shí)際值累盗。這個方法會更新控制器上本地的$viewValue寒矿,然后將值傳遞給每一個$parser函數(shù)。
??當(dāng)值被解析若债,且$parser流水線中所有的函數(shù)都調(diào)用完成后符相,值會被賦給$modelValue屬性,并且傳遞給指令中ng-model屬性提供的表達(dá)式蠢琳。
??最后啊终,所有步驟都完成后,$viewChangeListeners中所有的監(jiān)聽器都會被調(diào)用傲须。
??注意蓝牲,單獨(dú)調(diào)用$setViewValue()不會喚起一個新的digest循環(huán),因此如果想更新指令泰讽,需要在設(shè)置$viewValue后手動觸發(fā)digest例衍。
??$setViewValue()方法適合于在自定義指令中監(jiān)聽自定義事件昔期,我們會希望在回調(diào)時設(shè)置$viewValue并執(zhí)行digest循環(huán)。

angular.module('myApp')
    .directive('myDirective', function() {
        return {
            require: '?ngModel',
            link: function(scope, ele, attrs, ngModel) {
                if (!ngModel) return;
                $(function() {
                    ele.datepicker({
                        onSelect: function(date) {
                            // 設(shè)置視圖和調(diào)用apply
                            scope.$apply(function() {
                                ngModel.$setViewValue(date);
                            });
                        }
                    });
                });
            }
        };
    });

自定義渲染

在控制器中定義$render方法可以定義視圖具體的渲染方式佛玄。這個方法會在$parser流水線完成后被調(diào)用硼一。
由于這個方法會破壞AngularJS的標(biāo)準(zhǔn)工作方式,因此一定要謹(jǐn)慎使用:

angular.module('myApp')
    .directive('myDirective', function() {
        return {
            require: '?ngModel',
            link: function(scope, ele, attrs, ngModel) {
                if (!ngModel) return;
                ngModel.$render = function() {
                    element.html(ngModel.$viewValue() || 'None');
                };
            }
        };
    });

屬性

ngModelController中有幾個屬性可以用來檢查甚至修改視圖梦抢。

  1. $viewValue
    $viewValue屬性保存著更新視圖所需的實(shí)際字符串般贼。
  2. $modelValue
    $modelValue由數(shù)據(jù)模型持有。$modelValue$viewValue可能是不同的奥吩,取決于$parser流水線是否對其進(jìn)行了操作哼蛆。
  3. $parsers
    $parsers的值是一個由函數(shù)組成的數(shù)組,其中的函數(shù)會以流水線的形式被逐一調(diào)用圈驼。ngModel從DOM中讀取的值會被傳入$parsers中的函數(shù)人芽,并依次被其中的解析器處理望几。
  4. $formatters
    $formatters的值是一個由函數(shù)組成的數(shù)組绩脆,其中的函數(shù)會以流水線的形式在數(shù)據(jù)模型的值發(fā)生變化時被逐一調(diào)用。它和$parser流水線互不影響橄抹,用來對值進(jìn)行格式化和轉(zhuǎn)換靴迫,以便在綁定了這個值的控件中顯示。
  5. $viewChangeListeners
    $viewChangeListeners的值是一個由函數(shù)組成的數(shù)組楼誓,其中的函數(shù)會以流水線的形式在視圖中的值發(fā)生變化時被逐一調(diào)用玉锌。通過$viewChangeListeners,可以在無需使用$watch的情況下實(shí)現(xiàn)類似的行為疟羹。由于返回值會被忽略主守,因此這些函數(shù)不需要返回值。
  6. $error
    $error對象中保存著沒有通過驗(yàn)證的驗(yàn)證器名稱以及對應(yīng)的錯誤信息榄融。
  7. $pristine
    $pristine的值是布爾型的参淫,可以告訴我們用戶是否對控件進(jìn)行了修改。
  8. $dirty
    $dirty的值和$pristine相反愧杯,可以告訴我們用戶是否和控件進(jìn)行過交互涎才。
  9. $valid
    $valid值可以告訴我們當(dāng)前的控件中是否有錯誤。當(dāng)有錯誤時值為false力九,沒有錯誤時值為true耍铜。
  10. $invalid
    $invalid值可以告訴我們當(dāng)前控件中是否存在至少一個錯誤,它的值和$valid相反跌前。

自定義驗(yàn)證

要驗(yàn)證username在數(shù)據(jù)庫中是否合法棕兼,可以實(shí)現(xiàn)一個指令,用來在表單發(fā)生變化時發(fā)送Ajax請求:

angular.module('validationExample', [])
    .directive('ensureUnique',function($http) {
        return {
            require: 'ngModel',
            link: function(scope, ele, attrs, c) {
                scope.$watch(attrs.ngModel, function() {
                    $http({
                        method: 'POST',
                        url: '/api/check/' + attrs.ensureUnique,
                        data: {field: attrs.ensureUnique, valud:scope.ngModel}
                    }).success(function(data,status,headers,cfg) {
                        c.$setValidity('unique', data.isUnique);
                    }).error(function(data,status,headers,cfg) {
                        c.$setValidity('unique', false);
                    });
                });
            }
        };
    });

<input type="text"
    placeholder="Desired username"
    name="username"
    ng-model="signup.username"
    ng-minlength="3"
    ng-maxlength="20"
    ensure-unique="username" required />

在這個自定義驗(yàn)證中抵乓,每當(dāng)ngModel中對應(yīng)的字段發(fā)生變化就會向服務(wù)器發(fā)送請求伴挚,以檢查用戶名是否是唯一的蹭沛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市章鲤,隨后出現(xiàn)的幾起案子摊灭,更是在濱河造成了極大的恐慌,老刑警劉巖败徊,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帚呼,死亡現(xiàn)場離奇詭異,居然都是意外死亡皱蹦,警方通過查閱死者的電腦和手機(jī)煤杀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沪哺,“玉大人沈自,你說我怎么就攤上這事」技耍” “怎么了枯途?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長籍滴。 經(jīng)常有香客問我酪夷,道長,這世上最難降的妖魔是什么孽惰? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任晚岭,我火速辦了婚禮,結(jié)果婚禮上勋功,老公的妹妹穿的比我還像新娘坦报。我一直安慰自己,他們只是感情好狂鞋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布片择。 她就那樣靜靜地躺著,像睡著了一般要销。 火紅的嫁衣襯著肌膚如雪构回。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天疏咐,我揣著相機(jī)與錄音纤掸,去河邊找鬼。 笑死浑塞,一個胖子當(dāng)著我的面吹牛借跪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酌壕,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼掏愁,長吁一口氣:“原來是場噩夢啊……” “哼歇由!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起果港,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤沦泌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辛掠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谢谦,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年萝衩,在試婚紗的時候發(fā)現(xiàn)自己被綠了回挽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡猩谊,死狀恐怖千劈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牌捷,我是刑警寧澤墙牌,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站宜鸯,受9級特大地震影響憔古,放射性物質(zhì)發(fā)生泄漏遮怜。R本人自食惡果不足惜淋袖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锯梁。 院中可真熱鬧即碗,春花似錦、人聲如沸陌凳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽合敦。三九已至初橘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間充岛,已是汗流浹背保檐。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崔梗,地道東北人夜只。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蒜魄,于是被迫代替她去往敵國和親扔亥。 傳聞我的和親對象是個殘疾皇子场躯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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