首頁  >  文章  >  web前端  >  angularjs如何建立自訂指令?用angularjs建立自訂指令詳細流程

angularjs如何建立自訂指令?用angularjs建立自訂指令詳細流程

寻∝梦
寻∝梦原創
2018-09-07 15:04:121206瀏覽

文件翻譯至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> <!-- as an attr -->
以下<input>元素也符合ngModel:

<input>
以下元素也符合ngModel:

<person>{{name}}</person>
以下元素也符合ngModel:

  Hello <input> 


   
   
   
   
   

以下元素也符合ngModel:

A - attributes    <p> </p>
C - class name    <p> </p>
E - element name   <person></person>
M - comments    <!-- directive: person -->
以下
//... 
scope: { customerInfo: '=info' },
//...

Normalization (暫譯規範化)

AngularJS規範化元素的標籤和屬性名稱,以決定哪些元素與哪些指令相符。我們通常透過其區分大小寫的camelCase規範化名稱(例如,ngModel)來定義(refer to)指令。然而,由於HTML是大小寫不敏感的,我們透過小寫形式在DOM中引用指令,通常使用dash(-)分割符分割不同的單字(例如ng-model)

規範化過程如下:

1.剔除元素/屬性開頭的x- , data- ;

2.將- , _ ,: 分隔符號轉換為小駝峰式camelCas例如,以下形式都是等同的,並且與ngBind指令相符:

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'
  };
});
指令類型

rrreeeBest Practice:  Prefer using directives via tag name and attributes over comment and class names.Doing so tiveerally makes it easier to determine 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 <ments); . 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 directive, instance, if you created a directive, instance. 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#jture ng or they might conflict with directives included in#Jversion. #作為後續範例,我們將使用my前綴(例如myCustomer)。

Template-expanding 指令

假設您有一大塊代表客戶資訊的範本。這個模板在您的程式碼中重複多次。當你在一個地方改變它時,你必須在其他幾個地方改變它。這是使用指令簡化模板的好機會。

讓我們建立一個指令,用一個靜態範本簡單地取代它的內容:


#注意我們在這個指令中有bindings。在$compile編譯和連結

後,它將會嘗試在元素的子元素上匹配指令。這意味著你可以組成指令的指令(巢狀指令)。我們將在後續看到如何寫 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代替的相同示例:

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阶段).

angularjs如何建立自訂指令?用angularjs建立自訂指令詳細流程

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组建。(想看更多就到PHP中文网angularjs参考手册中学习)

隔离指令的Scope

我们以上的myCustomer指令很好,但是它有一个致命缺陷。我们只有在一个给定的scope下使用。

在其目前的实现上,我们应该需要去创建一些不同点控制器用来重用这个指令。
https://plnkr.co/edit/CKEgb1e...

这明显不是一个好的解决方案。

我们说项的是把指令内部的scope与外部scope(controller scope)分离,并且映射外部scope到指令内部scope。我们可以通过创建一个isolate scope来做。为此,我们可以使用指令的scope选项。

https://plnkr.co/edit/E6dTrgm...
看index.html文件,第一个元素绑定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選項。

transclude選項到底做了什麼呢? transclude使指令的內容透過此選項擁有可存取外部指令的scope不是內部的scope。

為了說明了這一點,請看下面的例子。注意,我們在script.js中加入了一個link函數,將名稱重新定義為Jeff。您認為{{name}}綁定將會得到什麼結果?

照常,我們以為{{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.

接下來,我們要在此對話框中加入按鈕,並允許使用該指令的使用者將自己的行為綁定到該對話框。

我們希望透過從指令的作用域呼叫它來運行我們傳遞的函數,但是它會在註冊作用域的上下文中運行。

我們在之前已經看到在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元素的指令。在這個例子的基礎上,讓我們制定一個對其元素事件做出反應的指令。

例如,如果我們想要建立一個允許使用者拖曳元素的指令呢?

创建一个通信的指令

你可以组建任何指令通过模板使用他们。

有时,你需要一个由指令组合构建的组件。

想象你想要有一个容器,其中容器的内容对应于哪个选项卡处于活动状态的选项卡。

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 有一个全面的指令清单选项以供参考。

最后就是本篇文章到这结束了(想看更多就到PHP中文网angularjs学习手册中学习),有问题的可以在下方留言提问

以上是angularjs如何建立自訂指令?用angularjs建立自訂指令詳細流程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn