建立自定義指令

文檔翻譯至angularjs.org. 文檔解釋了您什麼時候想在AngularJS應用程序中建立本身的指令,以及如何實現它們。 | 建議搭配原文食用 |html

什麼是指令?

在高層次上,指令時DOM元素上的標記(做爲屬性,元素名,註釋和CSS類)用來告訴Angularjs的HTML Compiler($compile)附加特定的行爲在此DOM元素上(例如,經過事件監聽),或者甚至去轉換DOM元素和他的子元素。angularjs

Angularjs附加了一系列內建的執行,像ngBind, ngModel, and ngClass. 和你建立的控制器和服務同樣,你能夠建立你本身的指令來供Angularjs使用. 當Angularjs 啓動(bootstraps)你的應用時,HTML compiler遍歷DOM匹配DOM元素對應的指令。bootstrap

What does it mean to "compile" an HTML template? For AngularJS, "compilation" means attaching directives to the HTML to make it interactive. The reason we use the term "compile" is that the recursive process of attaching directives mirrors the process of compiling source code in compiled programming languages.數組

指令匹配
在咱們開始寫指令以前, 咱們須要知道當使用一個給定的指令, Angularjs的 HTML Compiler是如何判斷的微信

與元素匹配選擇器(element matches a selector)時使用的術語相似,當指令是其聲明的一部分時,咱們說元素匹配指令。app

在下面的例子中,咱們說<input>元素匹配ngModel指令less

<input ng-model="foo"> <!-- as an attr -->

如下<input>元素也匹配ngModel:ide

<input data-ng-model="foo">

如下<person>元素與person指示相匹配:函數

<person>{{name}}</person>

Normalization (暫譯 規範化)

AngularJS規範化元素的標籤和屬性名稱,以肯定哪些元素與哪些指令相匹配。咱們一般經過其區分大小寫的camelCase規範化名稱(例如,ngModel)來定義(refer to)指令。然而,因爲HTML是大小寫不敏感的,咱們經過小寫形式在DOM中引用指令,一般使用dash(-)分割符分割不一樣的單詞(例如ng-model)post

規範化過程以下:
1.剔除 元素/屬性開頭的 x- , data- ;
2.將 - , _ ,: 分隔符轉換爲小駝峯式 camelCas
例如,如下形式都是等同的,而且與ngBind指令相匹配:

<div ng-controller="Controller">
  Hello <input ng-model='name'> <hr/>
  <span ng-bind="name"></span> <br/>
  <span ng:bind="name"></span> <br/>
  <span ng_bind="name"></span> <br/>
  <span data-ng-bind="name"></span> <br/>
  <span x-ng-bind="name"></span> <br/>
</div>

指令類型

A - attributes    <div person> </div>
C - class name    <div class="person"> </div>
E - element name   <person></person>
M - comments    <!-- directive: person -->

Best Practice: Prefer using directives via tag name and attributes over comment and class names.Doing so generally makes it easier to determine what directives a given element matches.

Best Practice: Comment directives were commonly used in places where the DOM API limits the ability to create directives that spanned multiple elements (e.g. inside <table> elements). AngularJS 1.2 introduces ng-repeat-start and ng-repeat-end as a better solution to this problem. Developers are encouraged to use this over custom comment directives when possible..

建立指令

首先讓咱們討論下注冊指令的API(API for registering directives). 和控制器同樣,指令也是註冊在模塊之上的。爲了註冊一個指令,你須要使用 module.directive API。module.directive接受標準化的指令名稱,後跟一個工廠函數。這個工廠函數應該返回一個具備不一樣選項的對象來告訴$compile指令在匹配時應該如何表現。

當$conpile第一次匹配指令時,工廠函數僅被調用一次。你能夠在這裏指令任意的初始化工做。該(工廠)函數使用 $injector.invoke 來調用這使得它能夠像控制器同樣是可注射。

咱們將經過一些常見的指令示例,而後深刻探討不一樣的選項和編譯過程。

Best Practice: In order to avoid collisions with some future standard, it's best to prefix your own directive names. For instance, if you created a <carousel> directive, it would be problematic if HTML7 introduced the same element. A two or three letter prefix (e.g. btfCarousel) works well. Similarly, do not prefix your own directives with ng or they might conflict with directives included in a future version of AngularJS.

做爲後續示例,咱們將使用my前綴(例如 myCustomer)。

Template-expanding 指令

假設您有一大塊表明客戶信息的模板。這個模板在您的代碼中重複屢次。當你在一個地方改變它時,你必須在其餘幾個地方改變它。這是使用指令簡化模板的好機會。

讓咱們建立一個指令,用一個靜態模板簡單地替換它的內容:
https://jsfiddle.net/TommyLee...

注意咱們在這個指令中有bindings。在$compile編譯和連接<div my-customer></div>後,它將會嘗試在元素的子元素上匹配指令。這意味着你能夠組建指令的指令(嵌套指令)。咱們將在後續看到如何編寫 an example 。

在上面的例子中,咱們列出了模板選項(template attribute of return object in factory function),但隨着模板大小的增加,這將變得使人討厭。

Best Practice: Unless your template is very small, it's typically better to break it apart into its own HTML file and load it with the templateUrl option.

若是你熟悉ngInclude,templateUrl就像它同樣工做。下面是使用templateUrl代替的相同示例:
https://plnkr.co/edit/idFOZ8Q...

templateUrl也能夠是一個函數,它返回要加載和用於指令的HTML模板的URL。AngularJS將使用兩個參數調用templateUrl函數:指令被調用的元素以及與該元素相關聯的attr對象。

Note: You do not currently have the ability to access scope variables from the templateUrl function, since the template is requested before the scope is initialized
注:(要訪問socpe上的值,應該在post-link階段).

https://plnkr.co/edit/gaSYwnp...

restrict 選項一般設置爲:
圖片描述

When should I use an attribute versus an element? Use an element when you are creating a component that is in control of the template.The common case for this is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.

用元素來使用myCustomer指令時明智的選擇,由於你不用一些「customer」行爲修飾一個元素,你定義一個元素核心行爲做爲一個costomer組建。

隔離指令的Scope

咱們以上的myCustomer指令很好,可是它有一個致命缺陷。咱們只有在一個給定的scope下使用。

在其目前的實現上,咱們應該須要去建立一些不一樣點控制器用來重用這個指令。
https://plnkr.co/edit/CKEgb1e...

這明顯不是一個好的解決方案。

咱們說項的是把指令內部的scope與外部scope(controller scope)分離,而且映射外部scope到指令內部scope。咱們能夠經過建立一個isolate scope來作。爲此,咱們可使用指令的scope選項。

https://plnkr.co/edit/E6dTrgm...
看index.html文件,第一個<my-customer>元素綁定info屬性值爲naomi,它是咱們已經暴露在咱們的控制器上的scope。第二個綁定info爲igor。

讓咱們仔細看看scope選項

//... 
scope: { customerInfo: '=info' },
//...

除了能夠將不一樣的數據綁定到指令中的做用域外,使用isolated scope還有其餘做用。

咱們能夠經過添加另外一個屬性vojta來展現,到咱們的scope並嘗試從咱們的指令模板中訪問它:
https://plnkr.co/edit/xLVqnzt...

請注意{{vojta.name}}和{{vojta.address}}爲空,意味着它們未定義(undefined)。雖然咱們在控制器中定義了vojta,但它在指令中不可用。

顧名思義,該指令的 isolate scope隔離了除顯式添加到做用域的模型以外的全部內容:scope: {}散列對象. 這在構建可重用組件時頗有用,由於它能夠防止組件改變模型狀態,除了顯式傳入。

Note: Normally, a scope prototypically inherits from its parent. An isolated scope does not. See the "Directive Definition Object - scope"section for more information about isolate scopes.

Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app.

建立一個操縱DOM的指令

在這個例子中,咱們將創建一個顯示當前時間的指令。每秒一次,它會更新DOM以反映當前時間。

想要修改DOM的指令一般使用link選項來註冊DOM監聽器以及更新DOM。它在模板被克隆以後執行,而且是放置指令邏輯的地方。

link接受一個帶有一下簽名的函數function link(scope, element, attrs, controller, transcludeFn) { ... }, 其中:

  • scope是一個Angularjs scope 對象
  • element 是一個此指令匹配的jqLite包裝元素
  • attrs是一個具備標準化屬性名稱及其對應屬性值的鍵值對的散列對象。
  • controller是指令所需的控制器實例或其本身的控制器(若是有的話)。確切的值取決於指令的 require屬性。
  • transcludeFn是預先綁定到正確的包含範圍的transclude連接函數。

For more details on the link option refer to the $compile API page.

在咱們的link函數中,咱們想每秒鐘更新顯示時間,或者一個用戶改變了咱們指令綁定的時間格式字符串。咱們將會使用$interval服務按期調用處理程序。這比使用$ timeout更容易,但對於端到端測試也更好,咱們但願確保在完成測試以前完成全部$timeout。若是指令被刪除,咱們也想刪除$ interval,因此咱們不會引入內存泄漏
https://plnkr.co/edit/vIhhmNp...

這裏有幾件事須要注意。就像module.controller API同樣,module.directive中的函數參數是依賴注入的。所以,咱們能夠在指令的連接函數中使用$ interval和dateFilter。

咱們註冊一個事件element.on('$ destroy',...)。什麼引起了這個$ destroy事件?

AngularJS發佈了一些特殊事件。當用AngularJS的編譯器編譯的DOM節點被銷燬時,它會發起$ destroy事件。一樣,當Angularjs scope被銷燬,他會廣播(broadcasts)一個$destory事件監聽scopes。

經過監聽此事件,能夠刪除可能致使內存泄漏的事件偵聽器。註冊到scope和element的監聽事件在銷燬DOM時會自動清理,可是若是您在服務上註冊了偵聽器,或者在未被刪除的DOM節點上註冊了偵聽器,你必須本身清理它,不然你有冒險引入內存泄漏的風險。

Best Practice: Directives should clean up after themselves. You can use element.on('$destroy', ...) or scope.$on('$destroy', ...) to run a clean-up function when the directive is removed.

建立包裝其餘元素的指令

咱們已經看到,您可使用isolate scope將模型傳遞給指令,可是有時候想要能傳入一整個模板而不是一個字符串或者對象。咱們說咱們想要建立一個「dialog box」組建。dialog box應該有能力包裝任意的內容(any arbitrary content)。

爲此,咱們須要使用transclude選項。
https://plnkr.co/edit/empMwVW...

transclude選項到底作了什麼呢?transclude使指令的內容經過此選項擁有可訪問外部指令的scope不是內部的scope。

爲了說明了這一點,請看下面的例子。注意,咱們在script.js中添加了一個link函數,將名稱從新定義爲Jeff。您認爲{{name}}綁定將會獲得什麼結果?
https://plnkr.co/edit/OEdkXY4...

照常,咱們覺得{{name}}應該是Jeff。可是,咱們看見的是Tobias。

transclude選項改變了scope的嵌套方式。它使得一個transcluded指令的內容具備在指令以外的任何scope內容,而不是任何內部的scope。這樣作,它可讓內容訪問外部scope。

請注意,若是指令沒有建立本身的獨立做用域,那麼scope.name ='Jeff'中的做用域將引用外部做用域,咱們會在輸出中看到Jeff。

這種行爲對於封裝某些內容的指令是有意義的,由於不然,您必須分別傳入每一個您想要使用的模型。若是你必須傳入每個你想要的model,那麼你不能真正的使用任意的內容,對嗎?

Best Practice: only use transclude: true when you want to create a directive that wraps arbitrary content.

接下來,咱們要在此對話框中添加按鈕,並容許使用該指令的用戶將本身的行爲綁定到該對話框。
https://plnkr.co/edit/Bo5lona...

咱們但願經過從指令的做用域調用它來運行咱們傳遞的函數,可是它會在註冊做用域的上下文中運行。

咱們在以前已經看到在scope選項中如何使用 =attr,可是在上面的例子中,咱們使用了&attr代替。 &綁定容許一個指令去觸發一個原始範圍內的表達式的評估,在一個特定時間點上。任何合法的表達式都是容許的,包括一個含有函數調用的表達式。如此,& 綁定是理想的將回調函數綁定到指令行爲。

當用戶點擊dialog中的 x,指令的close函數被調用,多虧於ng-click。這個close調用在isolated scope之上,實際上會在原始scope的上下文中評估表達式 hideDialog(message),致使運行Controller中的hideDialog function。

一般指望經過一個表達式從isolate scope傳入數據到父scope,這能夠經過將局部變量名稱和值的映射傳遞到表達式包裝函數來完成。例如,hideDialkog函數接受一個message來顯示當dialog被隱藏是。這被指令調用 close({message: 'closing for now'})指明。接着局部變量message將在on-close表達式內被訪問(is available).

Best Practice: use &attr in the scope option when you want your directive to expose an API for binding to behaviors.

建立一個添加事件監聽器的指令

之前,咱們使用連接函數來建立操縱其DOM元素的指令。在這個例子的基礎上,讓咱們制定一個對其元素事件作出反應的指令。

例如,若是咱們想建立一個容許用戶拖拽元素的指令呢?
https://plnkr.co/edit/hcUyuBY...

建立一個通訊的指令

你能夠組建任何指令經過模板使用他們。

有時,你須要一個由指令組合構建的組件。

想象你想要有一個容器,其中容器的內容對應於哪一個選項卡處於活動狀態的選項卡。
https://plnkr.co/edit/kqLjcwG...

myPane指令有require選項值爲^^myTabs. 當指令使用此選項,&compile將拋出一個錯誤除非特定的controller被找到。 ^^前綴表示該指令在其父元素上搜索控制器。(^前綴將使指令在自身元素或她的父元素上尋找控制器;又沒任何前綴,指令將值操做自身)

因此這個myTabs contoller從哪裏來的?指令能夠特定一個controllers經過使用 controller選項。如你所見,myTabs指令使用了此選項。就像ngController,此選項附加一個控制器到指令的模板上。

若是須要從模板中引用控制器或綁定到控制器的任何功能,則可使用選項controllerAs將控制器的名稱指定爲別名。該指令須要定義要使用的此配置的範圍。這在指令被用做組件的狀況下特別有用。

回頭看myPane的定義,注意到link函數的最後一個參數:tabCtrl。當指令須要控制器時,它將接收該控制器做爲其link函數的第四個參數。利用這一點,myPane能夠調用myTabs的addPane函數。

若是須要多個控制器,則指令的require選項能夠採用數組參數。發送給連接函數的相應參數也將是一個數組。

angular.module('docsTabsExample', [])
.directive('myPane', function() {
  return {
    require: ['^^myTabs', 'ngModel'],
    restrict: 'E',
    transclude: true,
    scope: {
      title: '@'
    },
    link: function(scope, element, attrs, controllers) {
      var tabsCtrl = controllers[0],
          modelCtrl = controllers[1];

      tabsCtrl.addPane(scope);
    },
    templateUrl: 'my-pane.html'
  };
});

明的讀者可能想知道連接和控制器之間的區別。基本的區別是控制器能夠暴露一個API,而且連接函數可使用require與控制器交互。

Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.

總結

到此咱們已經看了大多數指令的用法,每個樣例演示了一個建立你本身指令的好的起始點。

你可能深刻感興趣於編譯過程的解釋能夠在這裏得到compiler guide.

$compile API 有一個全面的指令清單選項以供參考。

最後

若有任何問題和建議歡迎發送至郵箱討論:<Tommy.White.h.li@gmail.com>
翻譯不易,若您以爲對您有幫助,歡迎打賞

微信:圖片描述

支付寶:圖片描述

相關文章
相關標籤/搜索