javascript
4、Angular JS 学习笔记 – 创建自定义指令 [翻译中]
2019獨角獸企業重金招聘Python工程師標準>>>
創建自定義指令
注意:本指南是針對已經熟悉AngularJS基礎的開發者。如果您只是想要開始,建議您先去看教程。如果你在尋找指令的API,我們最近把他移動到 $compile這個文檔解釋當我們想要在AngularJS 應用中建立自己的指令時,該如何實現。
什么是指令?
從一個高的層次看,指令是DOM元素(屬性、元素名稱、注釋、或CSS樣式類)上的標記告訴AngularJS的HTML 編譯器($compile)去附加特定的行為到DOM元素或者是變換DOM元素和它的子元素。
Angular 內置了一個指令集,比如ngBind,ngModel,和ngClass。非常像是你創建一個Controllers和Services,你可以創建自己的指令用于Angular。當Angular窮的那個你的應用,HTML編譯器通過DOM匹配指令。
“編譯”HTML模板是什么意思??對于AngularJS, "編譯" 表示附加事件到HTML上建立交互效果。我們使用“編譯”這個術語的原因是指令的遞歸處理借鑒了編譯程序語言編譯源代碼的過程。匹配指令
我們寫一個指令前,我們需要知道Angular的HTML編譯器決定何時使用給定的指令。
在下面的例子中,我們說這個<input>元素匹配ngModel指令。
<input ng-model="foo">
下面的代碼也匹配ngModel:
<input data-ng:model="foo">
標準化
Angular標準化一個元素的標簽和屬性名稱去確定一個元素匹配哪個指令。我們通常引用指令通過區分大寫小的駝峰標準名稱(例如 ngModel)。不過,HTML是不區分大小寫的,我們在DOM上引用指令通過小寫方式,通常在元素上使用中劃線分割屬性名(例如 ng-model)。
標準化的過程如下:
舉例來說,下面的方式是相同的都匹配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> angular.module('docsBindExample', []) .controller('Controller', ['$scope', function($scope) {$scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)'; }]); 最佳時間:?優先使用-分隔的格式。(例如 ng-bind?對應 ngBind)。如果你想使用HTML驗證工具,你可以替代使用data-為前綴的版本?(例如? data-ng-bind?對應 ngBind). 其他顯示在上面的形式出于兼容的原因也是支持的,不過我們建議你避免使用。指令的類型
$compile可以在元素名稱,屬性,樣式類名稱,甚至是注釋上匹配指令。下面演示了不同方式的指令能夠在模板中引用:
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span> 最佳實踐:?優先使用指令通過標簽名、屬性名,比注釋和class名要好。所以一般就簡單的提供一個元素匹配表示一個指令。 最佳實踐:注釋指令通常使用在DOM API 限制無法創建指令到多個元素的情況(例如內部的table元素)。AngularJS1.2 采用了ng-repeat-start 和 ng-repeat-end 更好的解決了這個問題。鼓勵開發者盡可能的去使用這個在自定義的注視指令上。文本和屬性的綁定
在編譯處理過程中,編譯器使用$interpolate服務匹配文本和屬性、查看是否包含嵌套的表達式。這些表達式注冊為watches并且將局部更新作為普通編譯周期的一部分,一個插值例子展示在下面:
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>ngAttr?屬性綁定
我們的瀏覽器有時候會更多的需要考慮有效的屬性。
舉例,就這個模板而言:
<svg><circle cx="{{cx}}"></circle> </svg>我們大概期望Angular能夠綁定,但是當我們檢查控制臺看到的類似于Errlr:Invalid value for attribute cx ="{{cx}}"。因為SVG DOM API 的約束,你不能簡單的寫作cx="{{cx}}"
用ng-attr-cx你可以繞過這個問題。
如果綁定的屬性前綴是ngAttr(標準化之前的是ng-attr-),則在綁定過程中它將應用于相應的沒有前綴的屬性。這允許你綁定到屬性,否則瀏覽器會著急的處理它(例如一個SVG元素的cricle[cx]屬性)。當使用ngAttr,$interpolate的allOrNothing標記已經在使用,所以如果任何的表達式在插值字符串結果是undefined,則這個屬性將會移除不會添加到元素。
舉例子,我們可以修復上面的例子,寫為:
<svg> <circle ng-attr-cx="{{cx}}"></circle> </svg>如果你想要修改一個駝峰命名的屬性(SVG元素具有有效的駝峰命名的屬性),比如viewBox屬性在svg元素,你可以使用下下劃線去表示這個屬性去綁定駝峰命名。
舉例,去綁定viewBox,我們可以寫成:
<svg ng-attr-view_box="{{viewBox}}"> </svg>創建指令
首先讓我們說一下關于注冊指令的API。非常像是控制器,指令也是注冊在模塊上。去注冊一個指令,你使用module.directive API,module.directive需要一個標準的指令名稱在工廠函數中。這個工廠函數應該返回一個基于不同選項的對象告訴$compile 當指令有匹配的時候如何做。
這個工廠函數在編譯器第一次匹配指令的時候執行。你可以在這里執行任何的初始化工作。這個函數使用$injector.invoke執行使得它可以接受注入,類似于控制器。
最佳實踐: Prefer using the definition object over returning a function.更好的是使用一個定義對象返回一個函數。我們將通過一些指令的通用的例子,深入的了解不同的選項和編譯過程。
最佳實踐:為了避免某些未來的標準,最好給您的指令使用前綴。舉個例子來說,如果你創建一個<carousel>指令,你可能會在HTML7引入的相同的元素發生而問題。2到3個短的前綴工作的很好。同樣的,不要給自己的指令使用ng前綴或者你認為未來版本的angular可能會引起沖突的名稱。在下面的例子中,我們將使用前綴my(例如 myCustomer)。
模板擴展指令
假設你有一塊表示客戶信息的模板。這個模板在你的代碼中重復了多次。當你修改一個地方,你需要去修改其他地方的幾個。這是一個好機會使用一個指令去簡化你的模板。
Let's create a directive that simply replaces its contents with a static template:
讓我們創建一個靜態的模板上的指令,簡單的替換它的內容:
angular.module('docsSimpleDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.customer = {name: 'Naomi',address: '1600 Amphitheatre'}; }]) .directive('myCustomer', function() {return {template: 'Name: {{customer.name}} Address: {{customer.address}}'}; }); <div ng-controller="Controller"><div my-customer></div> </div>Notice that we have bindings in this directive. After $compile compiles and links <div my-customer></div>, it will try to match directives on the element's children. This means you can compose directives of other directives. We'll see how to do that in an example below.
注意這樣我們就做了指令的綁定。$comple編譯和鏈接<div my-customer></div>之后,它將嘗試去匹配指令到元素的子元素。這意味著你可以將多個指令組合起來。下我們將看到如何去做。
In the example above we in-lined the value of the template option, but this will become annoying as the size of your template grows.
在上面的例子中,我們將template選項的值寫在了一行之中,但是這是我們為了使沉長的模板尺寸更好看。
最佳實踐: 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.除非你的模板很小,它通常最好拆分成幾個HTML文件并且使用templateUrl屬性來讀取。If you are familiar with ngInclude, templateUrl works just like it. Here's the same example using templateUrl instead:
如果你見過ngInclude,templateUrl的工作就和它是一樣的。這里有一個相同的例子,使用templateUrl替代演示:
angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.customer = {name: 'Naomi',address: '1600 Amphitheatre'}; }]) .directive('myCustomer', function() {return {templateUrl: 'my-customer.html'}; }); <div ng-controller="Controller"><div my-customer></div> </div>templateUrl can also be a function which returns the URL of an HTML template to be loaded and used for the directive. Angular will call the templateUrl function with two parameters: the element that the directive was called on, and an attr object associated with that element.
templateUrl 也可以是一個函數來返回HTML模板的url,用來讀取模板并且用于指令。Angular將調用templateUrl函數基于兩個參數,一個是指令是在哪個元素上被調用,和一個attr屬性關聯相關的元素。
注意: 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. 你沒有能力從templateUrl函數中訪問scope中的變量,因為這個模板是在作用域初始化完畢前加載的。 angular.module('docsTemplateUrlDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.customer = {name: 'Naomi',address: '1600 Amphitheatre'}; }]) .directive('myCustomer', function() {return {templateUrl: function(elem, attr){return 'customer-'+attr.type+'.html';}}; }); <div ng-controller="Controller"><div my-customer type="name"></div><div my-customer type="address"></div> </div> Name: {{customer.name}} Address: {{customer.address}} 注意: When you create a directive, it is restricted to attribute and elements only by default. In order to create directives that are triggered by class name, you need to use the restrict option.當你創建一個指令,它將默認受限于屬性和元素。為了創建指令將會根據class name觸發,你必須使用restrict組合。The restrict option is typically set to:
這些約束的選項通常有:
- 'A' - only matches attribute name
- A - 值匹配屬性名
- 'E' - only matches element name
- E - 只匹配元素名稱
- 'C' - only matches class name
- C - 只匹配css類名
These restrictions can all be combined as needed: 這些限制可以全部合并在需要的時候:
- 'AEC' - matches either attribute or element or class name
- AEC - 匹配屬性、元素名稱、或類名。
Let's change our directive to use restrict: 'E':
讓我們修改我們的指令去使用restrict:'E';
angular.module('docsRestrictDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.customer = {name: 'Naomi',address: '1600 Amphitheatre'}; }]) .directive('myCustomer', function() {return {restrict: 'E',templateUrl: 'my-customer.html'}; }); <div ng-controller="Controller"><my-customer></my-customer> </div>For more on the restrict property, see the API docs.
查看更多關于約束限制的屬性,可以查看API 文檔;
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. 什么時候我應該使用屬性而不是元素??當你在模板中創建一個控制器的組件的時候,你應該使用元素。通常情況是當你創建一個特定領域的語言給你的模板。當你裝飾一個已經存在的元素賦予更多的功能的時候,你應該使用屬性。Using an element for the myCustomer directive is clearly the right choice because you're not decorating an element with some "customer" behavior; you're defining the core behavior of the element as a customer component.
使用一個元素去對應myCustomer指令是一個顯然正確的選擇,因為你沒有裝飾一個元素一些“customer”的行為;你定義了customer組件的核心行為。
指令的作用域隔離
Our myCustomer directive above is great, but it has a fatal flaw. We can only use it once within a given scope.
我們的myCustomer指令很棒,但是它有一個致命的缺陷,我們只能在指定的作用域中使用它一次。
In its current implementation, we'd need to create a different controller each time in order to re-use such a directive:
現在的實現方式,為了重復使用我們的指令,我們必須每次創建不同的控制器。
angular.module('docsScopeProblemExample', []) .controller('NaomiController', ['$scope', function($scope) {$scope.customer = {name: 'Naomi',address: '1600 Amphitheatre'}; }]) .controller('IgorController', ['$scope', function($scope) {$scope.customer = {name: 'Igor',address: '123 Somewhere'}; }]) .directive('myCustomer', function() {return {restrict: 'E',templateUrl: 'my-customer.html'}; }); <div ng-controller="NaomiController"><my-customer></my-customer> </div> <hr> <div ng-controller="IgorController"><my-customer></my-customer> </div>This is clearly not a great solution.
這顯然不是一個好的解決辦法。
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
我們想要有效的分隔作用域內的指令,并且映射外面的作用域到一個指令的內部作用域,我們可以做到,通過創建創建成為隔離作用域。如果這樣使用,我們將創建隔離的作用域。為了這個,我們可以使用作用域的scope選項:
angular.module('docsIsolateScopeDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };$scope.igor = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() {return {restrict: 'E',scope: {customerInfo: '=info'},templateUrl: 'my-customer-iso.html'}; }); <div ng-controller="Controller"><my-customer info="naomi"></my-customer><hr><my-customer info="igor"></my-customer> </div>Looking at index.html, the first <my-customer> element binds the info attribute to naomi, which we have exposed on our controller's scope. The second binds info to igor.
?讓我們看卡index.html,第一個元素是<my-customer>元素綁定了info屬性到naomi,我們而且將他曝光在了我們的controller作用域。第二個綁定info到igor.
Let's take a closer look at the scope option:
我們近一點看看scope選項
//... scope: { customerInfo: '=info' }, //...The scope option is an object that contains a property for each isolate scope binding. In this case it has just one property:
scope 選項是一個對象,包含 每一個隔離的作用域板頂。這樣它本身就只是一個屬性。
- Its name (customerInfo) corresponds to the directive's isolate scope property customerInfo.
- 屬性名(customerInfo)相當于指令的隔離作用域中的屬性customerInfo。
- Its value (=info) tells $compile to bind to the info attribute.
- 它的值(=info)告訴$compile去綁定info屬性。
For cases where the attribute name is the same as the value you want to bind to inside the directive's scope, you can use this shorthand syntax:
舉例當你想要綁定到作用域的屬性名與值相同的話,你可以簡寫為以下的語法:
... scope: {// same as '=customer'customer: '=' }, ...Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another effect.除此之外,還使它可能去綁定不同的數據到指令的作用域。
We can show this by adding another property, vojta, to our scope and trying to access it from within our directive's template:
我們可以添加其他的屬性:vojta,到我們的作用域,并且嘗試在我們的指令中訪問它。
angular.module('docsIsolationExample', []) .controller('Controller', ['$scope', function($scope) {$scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };$scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' }; }]) .directive('myCustomer', function() {return {restrict: 'E',scope: {customerInfo: '=info'},templateUrl: 'my-customer-plus-vojta.html'}; }); <div ng-controller="Controller"><my-customer info="naomi"></my-customer> </div> Name: {{customerInfo.name}} Address: {{customerInfo.address}} <hr> Name: {{vojta.name}} Address: {{vojta.address}}Notice that {{vojta.name}} and {{vojta.address}} are empty, meaning they are undefined. Although we defined vojta in the controller, it's not available within the directive.
注意{{vojta.name}}和{{vojta.address}}是空的,說明他們是undefined。雖然我們定義了vojta在控制器中,但是它在指令中是無效的。
As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to thescope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
顧名思義,指令的隔離作用域隔離了除模塊中明確添加到scope對象的任何東西。這在構建可復用組件時很有用,因為它防止組件在修改你的model狀態時只是你明確允許的哪些。
注意: 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. 一般情況,一個作用域繼承自它的父類,一個隔離的作用域則不繼承。查看"DIrective Definition Object "了解更多關于隔離作用域的信息。 最佳實踐: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app.當你創建一個你希望能夠在你的應用內復用的組件,使用scope選項去創建一個隔離作用域。創建一個指令操作DOM
In this example we will build a directive that displays the current time. Once a second, it updates the DOM to reflect the current time.
在例子中,我們將創建一個指令來顯示當前的時間。每一秒,它更新DOM顯示當前時間。
Directives that want to modify the DOM typically use the link option. link takes a function with the following signature,function link(scope, element, attrs) { ... } where:
指令要修改DOM,通常使用link選項。link接受一個函數,是這個樣子:function link(scope, element, attrs) { ... }
- scope is an Angular scope object. 是angular作用域對象
- element is the jqLite-wrapped element that this directive matches.是一個指令匹配到的那個jqLite包裝后的元素
- attrs is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values. 是一個鍵值對對象,保存著屬性的名稱和值。
In our link function, we want to update the displayed time once a second, or whenever a user changes the time formatting string that our directive binds to. We will use the $interval service to call a handler on a regular basis. This is easier than using $timeout but also works better with end-to-end testing, where we want to ensure that all $timeouts have completed before completing the test. We also want to remove the $interval if the directive is deleted so we don't introduce a memory leak.
在我們的link函數,我們想每一秒更新顯示一次時間,或者我們的指令能夠處理任何時候用戶修改時間的格式。我們將使用$interval服務規律的調用handler方法。這是一個比使用$timeout簡單而且能更好的用于端到端的測試,因為我們要確保在完全測試前完成所有的$timeout調用。我們還希望如果指令刪除的時候能夠刪除$interval避免內存泄漏。
angular.module('docsTimeDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {function link(scope, element, attrs) {var format,timeoutId;function updateTime() {element.text(dateFilter(new Date(), format));}scope.$watch(attrs.myCurrentTime, function(value) {format = value;updateTime();});element.on('$destroy', function() {$interval.cancel(timeoutId);});// start the UI update process; save the timeoutId for cancelingtimeoutId = $interval(function() {updateTime(); // update DOM}, 1000);}return {link: link}; }]); <div ng-controller="Controller">Date format: <input ng-model="format"> <hr/>Current time is: <span my-current-time="format"></span> </div>There are a couple of things to note here. Just like the module.controller API, the function argument in module.directive is dependency injected. Because of this, we can use $interval and dateFilter inside our directive's link function.
這里有一兩件事需要注意。正像 module.controller API,module.directive的函數參數是依賴注入的。因為這樣,我們能夠在我們的指令內部link函數中使用$interval和 dateFilter。
We register an event element.on('$destroy', ...). What fires this $destroy event?
我們注冊一個事件 element.on('$destroy',...)。如何觸發這個$destroy事件?
There are a few special events that AngularJS emits. When a DOM node that has been compiled with Angular's compiler is destroyed, it emits a $destroy event. Similarly, when an AngularJS scope is destroyed, it broadcasts a $destroy event to listening scopes.
這里有幾個AngularJS觸發的特殊事件。當一個DOM節點被angular編譯后,銷毀了,它會觸發$destroy時間。同樣的,當時一個AngularJS作用域銷毀了,它將廣播$destroy事件到監聽的作用域。
By listening to this event, you can remove event listeners that might cause memory leaks. Listeners registered to scopes and elements are automatically cleaned up when they are destroyed, but if you registered a listener on a service, or registered a listener on a DOM node that isn't being deleted, you'll have to clean it up yourself or you risk introducing a memory leak.
基于監聽這個事件,你可以刪除事件監聽器可能會引起內存泄漏。監聽器注冊在作用域和元素,當它們銷毀的時候會被自動的清理,但是你要注冊一個監聽器到service或者注冊一個監聽器到DOM節點,并且不要刪除。你將收拾干凈或者引入一個內存泄漏問題。
最佳實踐: 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. 指令應該清理他們自身,你可以使用element.on('$destroy',...) 或者scope.$on('$destroy',...)去清理函數當指令刪除的時候。創建一個指令包含其他的元素
We've seen that you can pass in models to a directive using the isolate scope, but sometimes it's desirable to be able to pass in an entire template rather than a string or an object. Let's say that we want to create a "dialog box" component. The dialog box should be able to wrap any arbitrary content.
我們可以看到你能夠通過一個模型給一個指令使用隔離的作用域,但是有些時候,它理想的是能夠通過整個模板而不是一個字符串或者對象。讓我們去創建一個“對話窗口”組件,這個對話窗口應該能夠包含任何的內容。
To do this, we need to use the transclude option.
為此,我們要必須使用transclude選項。
angular.module('docsTransclusionDirective', []) .controller('Controller', ['$scope', function($scope) {$scope.name = 'Tobias'; }]) .directive('myDialog', function() {return {restrict: 'E',transclude: true,templateUrl: 'my-dialog.html'}; }); <div ng-controller="Controller"><my-dialog>Check out the contents, {{name}}!</my-dialog> </div>What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scopeoutside of the directive rather than inside.
trasclude選項到底做了什么?transclude標記一個指令中的內容基于這個選項可以訪問這個指令以外的作用域。
To illustrate this, see the example below. Notice that we've added a link function in script.js that redefines name as Jeff. What do you think the {{name}} binding will resolve to now?
為了演示,我們看下面的例子。注意我們添加了一個link函數到script.js重新定義了name為Jeff。你認為{{name}}現在被綁定到哪個值上了呢?
angular.module('docsTransclusionExample', []) .controller('Controller', ['$scope', function($scope) {$scope.name = 'Tobias'; }]) .directive('myDialog', function() {return {restrict: 'E',transclude: true,scope: {},templateUrl: 'my-dialog.html',link: function (scope, element) {scope.name = 'Jeff';}}; }); <div ng-controller="Controller"><my-dialog>Check out the contents, {{name}}!</my-dialog> </div>Ordinarily, we would expect that {{name}} would be Jeff. However, we see in this example that the {{name}} binding is still Tobias.
一般情況下我們預計{{name}}應該是Jeff。不過,我們在例子匯總看到的是{{name}}仍然是Tobias。
The transclude option changes the way scopes are nested. It makes it so that the contents of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope.
這個transclude選項更改作用域嵌套。它標記轉換后的指令里的內容無論如何會使用外部的作用域,而不是內部的作用域。在這樣的情況下,它讓內容訪問的是外部的作用域。
Note that if the directive did not create its own scope, then scope in scope.name = 'Jeff'; would reference the outside scope and we would see Jeff in the output.
注意,如果這個指令沒有創建它自己的作用域,然后將在作用域中設置scope.name='Jeff';它將引用外部的作用域,我們將在輸出中看到Jeff。
This behavior makes sense for a directive that wraps some content, because otherwise you'd have to pass in each model you wanted to use separately. If you have to pass in each model that you want to use, then you can't really have arbitrary contents, can you?
指令的這種行為是合理的,它包裝一些內容,否則你將不得不在每一個模型中傳遞。如果你不得不在你想要的每個模型中傳遞,那你將不能真正隨心所欲的寫內容,你能嗎?
最佳實踐: only use transclude: true when you want to create a directive that wraps arbitrary content. 只有當你想創建一個指令想自由的控制內容時,你才使用transclude:true.Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own behavior to it.
另外,我們希望添加一個按鈕到這個彈出窗口,并且允許使用這個指令去綁定自己的行為。
angular.module('docsIsoFnBindExample', []) .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {$scope.name = 'Tobias';$scope.message = '';$scope.hideDialog = function (message) {$scope.message = message;$scope.dialogIsHidden = true;$timeout(function () {$scope.message = '';$scope.dialogIsHidden = false;}, 2000);}; }]) .directive('myDialog', function() {return {restrict: 'E',transclude: true,scope: {'close': '&onClose'},templateUrl: 'my-dialog-close.html'}; }); <div ng-controller="Controller">{{message}}<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">Check out the contents, {{name}}!</my-dialog> </div>We want to run the function we pass by invoking it from the directive's scope, but have it run in the context of the scope where it's registered.
我們希望運行的函數通過指令的作用域來執行,而且要讓他它在注冊過的上下文中執行。
We saw earlier how to use =attr in the scope option, but in the above example, we're using &attr instead. The & binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time. Any legal expression is allowed, including an expression which contains a function call. Because of this, & bindings are ideal for binding callback functions to directive behaviors.
我們看到之前如何使用=attr在scope選項中,但是在上面的例子中,我們使用$attr替代。這種&綁定允許一個指令在特定的時間觸發在原始的作用域中的表達式求值。任何合法的表達式都允許,包括函數調用的表達式。因此,&綁定是一個理想的用來給指令的行為綁定回調函數的方法。
When the user clicks the x in the dialog, the directive's close function is called, thanks to ng-click. This call to close on the isolated scope actually evaluates the expression hideDialog(message) in the context of the original scope, thus running Controller'shideDialog function.
接下來我們點擊窗口中的x,這個指令的close函數被調用。感謝ng-click。這次調用close在隔離的作用的域實際上是表達式hideDialog(meesage),我們運行Controller的hideDialog函數。
Often it's desirable to pass data from the isolate scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden. This is specified in the directive by calling close({message: 'closing for now'}). Then the local variable message will be available within the on-close expression.
通常從隔離的作用域通過表達式獲取父級數據,它可以通過一個本地變量的name和value組成的map放到表達式包裝的函數。拿例子來說,hideDialog函數取得一個message在dialog隱藏的時候顯示。這個指定在指令中叫close({message:'closing for now'}),然后這個本地變量mesage將在on-close表達式中可用。
最佳實踐: use &attr in the scope option when you want your directive to expose an API for binding to behaviors. 當你想要你的指令暴露一個綁定行為的API的時候,使用&attr在scope選項中。創建一個指令添加事件監聽器
Previously, we used the link function to create a directive that manipulated its DOM elements. Building upon that example, let's make a directive that reacts to events on its elements.
For instance, what if we wanted to create a directive that lets a user drag an element?
angular.module('dragModule', []) .directive('myDraggable', ['$document', function($document) {return function(scope, element, attr) {var startX = 0, startY = 0, x = 0, y = 0;element.css({position: 'relative',border: '1px solid red',backgroundColor: 'lightgrey',cursor: 'pointer'});element.on('mousedown', function(event) {// Prevent default dragging of selected contentevent.preventDefault();startX = event.pageX - x;startY = event.pageY - y;$document.on('mousemove', mousemove);$document.on('mouseup', mouseup);});function mousemove(event) {y = event.pageY - startY;x = event.pageX - startX;element.css({top: y + 'px',left: x + 'px'});}function mouseup() {$document.off('mousemove', mousemove);$document.off('mouseup', mouseup);}}; }]); <span my-draggable>Drag ME</span>創建指令溝通
You can compose any directives by using them within templates.
Sometimes, you want a component that's built from a combination of directives.
Imagine you want to have a container with tabs in which the contents of the container correspond to which tab is active.
angular.module('docsTabsExample', []) .directive('myTabs', function() {return {restrict: 'E',transclude: true,scope: {},controller: function($scope) {var panes = $scope.panes = [];$scope.select = function(pane) {angular.forEach(panes, function(pane) {pane.selected = false;});pane.selected = true;};this.addPane = function(pane) {if (panes.length === 0) {$scope.select(pane);}panes.push(pane);};},templateUrl: 'my-tabs.html'}; }) .directive('myPane', function() {return {require: '^myTabs',restrict: 'E',transclude: true,scope: {title: '@'},link: function(scope, element, attrs, tabsCtrl) {tabsCtrl.addPane(scope);},templateUrl: 'my-pane.html'}; }); <my-tabs><my-pane title="Hello"><h4>Hello</h4><p>Lorem ipsum dolor sit amet</p></my-pane><my-pane title="World"><h4>World</h4><em>Mauris elementum elementum enim at suscipit.</em><p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p></my-pane> </my-tabs> <div class="tabbable"><ul class="nav nav-tabs"><li ng-repeat="pane in panes" ng-class="{active:pane.selected}"><a href="" ng-click="select(pane)">{{pane.title}}</a></li></ul><div class="tab-content" ng-transclude></div> </div> <div class="tab-pane" ng-show="selected" ng-transclude> </div>The myPane directive has a require option with value ^myTabs. When a directive uses this option, $compile will throw an error unless the specified controller is found. The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
So where does this myTabs controller come from? Directives can specify controllers using the unsurprisingly named controller option. As you can see, the myTabs directive uses this option. Just like ngController, this option attaches a controller to the template of the directive.
If it is necessary to reference the controller or any functions bound to the controller's scope in the template, you can use the optioncontrollerAs to specify the name of the controller as an alias. The directive needs to define a scope for this configuration to be used. This is particularly useful in the case when the directive is used as a component.
Looking back at myPane's definition, notice the last argument in its link function: tabsCtrl. When a directive requires a controller, it receives that controller as the fourth argument of its link function. Taking advantage of this, myPane can call the addPane function ofmyTabs.
If multiple controllers are required, the require option of the directive can take an array argument. The corresponding parameter being sent to the link function will also be an array.
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'}; });Savvy readers may be wondering what the difference is between link and controller. The basic difference is that controller can expose an API, and link functions can interact with controllers using require.
最佳實踐: use controller when you want to expose an API to other directives. Otherwise use link.總結
Here we've seen the main use cases for directives. Each of these samples acts as a good starting point for creating your own directives.
You might also be interested in an in-depth explanation of the compilation process that's available in the compiler guide.
The $compile API page has a comprehensive list of directive options for reference.
tips:
本文由wp2Blog導入,原文鏈接:http://devonios.com/4%e3%80%81angular-js-%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0-directives.html
轉載于:https://my.oschina.net/yangyan/blog/859449
總結
以上是生活随笔為你收集整理的4、Angular JS 学习笔记 – 创建自定义指令 [翻译中]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 设置时区
- 下一篇: gradle idea java ssm