建立自定義指令

文檔翻譯至angularjs.org. 文檔詮釋了您什麼時候想在AngularJS應用順序中豎立本身的指令,以及怎樣完成它們。 | 提議搭配原文食用 |

什麼是指令?

在高層次上,指令時DOM元素上的標記(作為屬性,元素名,解釋和CSS類)用來通知Angularjs的HTML Compiler($compile)附加特定的行動在此DOM元素上(比方,經由歷程事宜監聽),或許以至去轉換DOM元素和他的子元素。

Angularjs附加了一系列內建的實行,像ngBind, ngModel, and ngClass. 和你豎立的控制器和效勞一樣,你能夠豎立你本身的指令來供Angularjs運用. 當Angularjs 啟動(bootstraps)你的應用時,HTML compiler遍歷DOM婚配DOM元素對應的指令。

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)時運用的術語相似,當指令是其聲明的一部分時,我們說元素婚配指令。

鄙人面的例子中,我們說<input>元素婚配ngModel指令

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

以下<input>元素也婚配ngModel:

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

以下<person>元素與person指導相婚配:

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

Normalization (暫譯 規範化)

AngularJS規範化元素的標籤和屬性稱號,以肯定哪些元素與哪些指令相婚配。我們一般經由歷程其辨別大小寫的camelCase規範化稱號(比方,ngModel)來定義(refer to)指令。然則,由於HTML是大小寫不敏感的,我們經由歷程小寫情勢在DOM中援用指令,一般運用dash(-)支解符支解差別的單詞(比方ng-model)

規範化歷程以下:
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>
翻譯不容易,若您以為對您有協助,迎接打賞

微信:《建立自定義指令》

支付寶:《建立自定義指令》

    原文作者:TommY
    原文地址: https://segmentfault.com/a/1190000015084337
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞