angular 開發(fā)者指南 ----創(chuàng)建自定義指令

這個文檔解釋了何時你可能會想要在你的AngularJS應(yīng)用中創(chuàng)建自定義指令,以及如何實現(xiàn)它們

指令是什么?##

站在高的角度說, 指令是DOM元素上的一些標識符(例如屬性,元素名,注釋或者是css類),它們告訴AngularJS的html 編譯器($compile)來賦予DOM元素一些特定的行為(例如通過事件監(jiān)聽器),甚至是改變DOM元素和它的子節(jié)點

Angular擁有許多內(nèi)置的指令,像ngBind,ngModel,以及ngClass,就像你創(chuàng)建controller和service一樣,你能夠創(chuàng)建你自己的指令,當Angular初始化你的應(yīng)用后,html編譯器遍歷DOM樹以尋找指令并初始化它們

"編譯"html模板是什么意思?對于angularJS來說,"編譯"意味著把指令鏈接到html上使它變得可交互.我們之所以使用專業(yè)術(shù)語"編譯"的原因是鏈接指令的這個循環(huán)過程是編譯型語言中編譯源代碼過程的真實映射

匹配指令##

在我們寫指令之前,我們需要知道Angular的html編譯器是怎樣決定何時來使用那些給定的指令
就像當元素匹配選擇器時所使用的術(shù)語,我們說一個元素匹配一個指令,當這個指令是它聲明的一部分時
在接下來的例子中,我們說<input>元素匹配ngModel指令

<input ng-model="foo">

這個<input>元素同樣匹配ngModel

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

這個元素匹配person指令

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

標準化命名##

Angular根據(jù)元素的標簽和屬性名來決定哪個元素匹配哪個指令,我們首先想到的是通過小駝峰式的標準化命名(如ngModel)來區(qū)別指令.然而,由于html是忽略大小寫的,我們在DOM中通過小寫的形式來識別指令,使用有特色的dash-delimited屬性來描述(如ng-model)

標準的寫法如下所示:

  1. 在元素/屬性前添加x-或者data-前綴的形式
  2. :,-,'_'分隔過的命名來替代駝峰寫法
<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>

最佳實踐:最好是使用中劃線的形式(如ng-bind表示ngBind),如果你想要使用html驗證工具,你可以使用添加data-前綴的形式(如data-ng-bind表示ngBind).上面其他的那些聲明形式出于歷史遺留性原因不會報錯,但是我們建議你避免使用它們

指令類型##

$compile能夠通過元素名,屬性,類名和注釋來匹配到指令

angular的所有這些指令都是通過屬性,標簽名,注釋和類名這四種形式來匹配的.下面是具體例子

<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>

最佳實踐: 最好是通過標簽名或者屬性來聲明指令,這樣做通常會使元素匹配到相應(yīng)指令更容易

最佳實踐: 注釋型指令通常被使用在這些地方: DOM API限制了創(chuàng)建跨越多個元素的指令的能力(例如在<table>元素內(nèi)).angularJS 1.2版本引入了ng-repeat-statrtng-repeat-end指令來作為這個問題更好的解決方案,開發(fā)者被鼓勵盡可能使用這兩個指令來替代自定義注釋型指令

創(chuàng)建指令##

首先讓我們來看看注冊指令的API, 和controller非常相似,指令被注冊在modules上.為了注冊一個指令,你需要使用module.directiveAPI,module.directive接收兩個參數(shù):第一個是標準化的指令命名,第二個是一個工廠函數(shù).這個工廠函數(shù)應(yīng)該返回一個帶有不同配置選項的對象,來告訴$compile指令有著怎樣的行為以及何時匹配

這個工廠函數(shù)僅僅在編譯器第一次匹配到指令時被解析一次.你能夠在這里做任何初始化工作.這個函數(shù)在解析時同樣使用依賴注入,使得它像控制器一樣變得可注入

我們接下來會看一些普通的例子,然后深度挖掘下不同的配置項以及編譯流程

最佳實踐: 為了避免和將來的標準發(fā)生沖突,最好是給你的自定義指令名加上前綴.如果你創(chuàng)建了一個<carousel>指令,如果html7引入了同樣的元素就會發(fā)生問題,引入兩個字母或三個字母的前綴(如btfcarousel)看起來不錯;同樣的,不要給你的自定義指令加上ng前綴,因為它可能和angular未來版本引入的指令發(fā)生沖突

在接下來的例子中,我們會使用my作為前綴

模板可擴展的指令##

讓我們假設(shè)你有一大塊模板來表示用戶的信息.這個模板在你的代碼中重復(fù)了許多次,當你在一處更改它時,你不得不在其他的好幾個地方也進行更改.這是一個創(chuàng)建指令來簡化模板的好機會.
讓我們創(chuàng)建一個指令對這些內(nèi)容用靜態(tài)模板來做個簡單的變換.
script.js

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

index.html

<div ng-controller="Controller"> <div my-customer></div></div>

Paste_Image.png

請注意我們在這個指令中所綁定的東西.在$compile編譯,以及l(fā)ink函數(shù)把<div my-customer></div>鏈接到document樹之后,它會試圖匹配當前元素子節(jié)點上的指令.這意味著你能夠組合不同的指令,我們會在接下來的例子中看看如何去做

最佳實踐: 除非你的模板非常小,通常情況下把模板放到html文件中通過templateUrl選項來引入會是更好的選擇

如果你很熟悉ngInclude,會發(fā)現(xiàn)templateUrl和它很相似.這里是使用templateUrl的例子:
script.js

angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    templateUrl: 'my-customer.html'
  };
});

index.html

<div ng-controller="Controller"> <div my-customer></div></div>

my-customer.html

Name: {{customer.name}} Address: {{customer.address}}
Paste_Image.png

templateUrl也可以是一個函數(shù),它返回html模板的url來被指令使用和加載.這個函數(shù)有兩個參數(shù):調(diào)用指令的元素本身element,以及這個元素上所關(guān)聯(lián)的屬性對象attr.

你現(xiàn)在還沒有能力在templateUrl函數(shù)上獲取到scope變量,這是因為在scope被初始化之前這個模板就已經(jīng)被引用了.

script.js

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

index.html

<div ng-controller="Controller"> <div my-customer type="name"></div> <div my-customer type="address"></div></div>

customer-name.html

Name: {{customer.name}}

customer-address.html

Address: {{customer.address}}
Paste_Image.png

注意:當你創(chuàng)建指令時,默認情況下只能識別通過屬性或元素聲明的指令,為了能夠通過譬如類名的方式來創(chuàng)建指令,你需要使用restrict選項.

restrict選項通常是下面這四種:

  • A -僅僅匹配屬性名
  • E -僅僅匹配元素名
  • C -僅僅匹配類名
  • M -僅僅匹配注釋

這些選擇項是能夠結(jié)合使用的:

  • AEC -會匹配屬性,元素或者是類名

讓我們使用restrict: 'E'來更改我們的指令.
script.js

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

index.html

<div ng-controller="Controller"> <my-customer></my-customer></div>

my-customer.html

Name: {{customer.name}} Address: {{customer.address}}
Paste_Image.png

我們何時應(yīng)該使用屬性 vs 元素?當你創(chuàng)建一個組件,它完全控制模板時,你應(yīng)該使用元素element,一個常見的案例是:當你為你的模板創(chuàng)建一個特定領(lǐng)域的語言;當你為你已經(jīng)存在的元素添加新功能時,你應(yīng)該使用屬性作為聲明方式

在這里使用元素作為指令的聲明方式myCustomer無疑是正確的選擇.因為你不是在給一個元素添加某些自定義的行為;你是在定義元素作為一個自定義組件的核心行為.

指令的隔離作用域##

我們上面的指令myCustomer看起來很不錯,但是它有一個致命的瑕疵.我們僅僅只能在一個給定的作用域中使用它一次
在當前的實現(xiàn)中,我們需要每次創(chuàng)建一個不同的控制器來復(fù)用這個指令
script.js

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

index.html

<div ng-controller="NaomiController">
  <my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorController">
  <my-customer></my-customer>
</div>

my-cutomser.html

Name: {{customer.name}} Address: {{customer.address}}
Paste_Image.png

這顯然不是一個好的解決方案
我們想要有能力去把指令的作用域和外部作用域隔離開來,并且可以把外部作用域傳遞進來.我們能夠通過創(chuàng)建"隔離作用域"來實現(xiàn)它.為了這個目的,我們使用指令的scope選項.
script.js

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

index.html

<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="igor"></my-customer>
</div>

my-customer-iso.html

Name: {{customerInfo.name}} Address: {{customerInfo.address}}

Paste_Image.png

讓我們看index.html,第一個<my-customer>元素把info屬性綁定到naomi上,我們在控制器的作用域中暴露出這個naomi對象;第二個元素把info綁定到igor
讓我們近距離看看作用域選項:

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

scope選項是一個對象:它包含一組綁定的屬性用于隔離作用域間的綁定.在這個例子中僅僅只有一個屬性

  • 它的名字(customerInfo)與指令的隔離作用域中屬性customerInfo一致
  • 它的值(=info)告訴$compile去綁定info屬性

注意: 這個在指令scope選項中的=attr屬性的命名規(guī)則和指令的命名方式一樣,為了綁定屬性<div bind-to-this="thing">,你需要這樣聲明=bindToThis.

如果你想要綁定的屬性名和指令內(nèi)部的變量名一樣的話,你可以采取下面縮寫的形式:

...
scope: {
  // same as '=customer'
  customer: '='
},
...

除了能把不同的數(shù)據(jù)綁定到指令內(nèi)部的作用域中,使用隔離作用域還有其他的作用.
看接下來這個例子:添加另一個屬性vojta到我們的控制器作用域中,并且試圖在指令內(nèi)部的模板中獲取它
script.js

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

index.html

<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
</div>

my-customer-plus-vojta.thml

Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}

Paste_Image.png

請注意{{vojta.name}}和{{vojta.address}}為空,意味著他們未定義,盡管我們在控制器中定義了vojta,在指令內(nèi)部它是不可獲取的
就像名字所暗示的那樣,指令的隔離作用域會隔離除了你顯式地在scope: {}中添加的哈希對象之外的一切內(nèi)容.這在你創(chuàng)建可復(fù)用組件時是非常有用的,因為它阻止了除了你顯式引入以外的模型來更改你的組件狀態(tài)

最佳實踐: 使用scope選項來創(chuàng)建隔離作用域,當你想要在你的app中創(chuàng)建可復(fù)用組件時

創(chuàng)建一個操作DOM的指令##

在這個例子中我們會創(chuàng)建一個展示當前時間的指令.每過一秒,它會更新DOM來展示當前時間

指令如果想要操作DOM,一般是使用link選項來注冊DOM監(jiān)聽器同時更新DOM,它在模板被復(fù)制以后執(zhí)行,并且指令的具體邏輯代碼會放在這里.
link接收一個函數(shù)如下所示function link(scope, element, attrs, controller, transcludeFn):

  • scope是Angular 的scope對象
  • element是指令匹配到的用jqLite包裹了以后的元素
  • attrs是一個標準化屬性名的key-value對象.通過它可以訪問到指令上的全部屬性
  • controller表示指令自己的控制器或指令引入的控制器實例(如果有的話).確切值依賴于指令的require選項

在我們的link函數(shù)中,我們想要每過一秒就更新一次時間,或是無論何時用戶改變指令所綁定的時間格式字符串.我們會使用$interval服務(wù) ,這會比使用$timeout更容易,并且也能更好的進行端到端測試(因為在我們完成測試前我們需要確保所有的$timeout都已經(jīng)執(zhí)行完);我們同時也希望當指令被刪除時移除$interval',這樣我們能避免內(nèi)存泄漏
script.js

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 canceling
    timeoutId = $interval(function() {
      updateTime(); // update DOM
    }, 1000);
  }

  return {
    link: link
  };
}]);

index.html

<div ng-controller="Controller">
  Date format: <input ng-model="format"> <hr/>
  Current time is: <span my-current-time="format"></span>
</div>

Paste_Image.png

這里有不少事情需要注意下.就像module.controllerAPI,這里的函數(shù)參數(shù)同樣是被依賴注入的,因為這個,我們能夠在link函數(shù)中使用$intervaldataFilter

我們注冊了一個event事件element.on('$destroy', ....),什么時候會觸發(fā)這個$destroy事件呢?

在AngularJS的事件冒泡機制中有一些特別的時刻,當被Angular的編譯器編譯過的DOM節(jié)點被銷毀時,它會冒泡一個$destroy事件;相似的,當一個AngularJS的作用域被銷毀時,它會向那些監(jiān)聽的作用域廣播一個$destroy事件

通過監(jiān)聽這個事件,你能夠移除可能會導(dǎo)致內(nèi)存泄漏的事件監(jiān)聽器.scope上注冊的監(jiān)聽器和元素當它們被銷毀時會被自動清除.但如果你是在service,或者是還未被刪除的DOM節(jié)點上注冊了一個監(jiān)聽器,你不得不自己手動銷毀它們,否則會有內(nèi)存泄漏的風(fēng)險

最佳實踐: 你應(yīng)該在指令的自動銷毀機制之外手動地進行銷毀.你能夠使用element.on('$destroy', ...)或者是scope.$on('$destroy', ...)來當指令被移除時執(zhí)行清除函數(shù)

創(chuàng)建一個包裹了其他元素的指令##

我們已經(jīng)見到:你能過通過使用隔離作用域來給指令傳遞一些數(shù)據(jù);但有時比起字符串或者對象,我們更想要能夠傳遞整個模板;讓我們創(chuàng)建一個對話框組件,這個組件應(yīng)該能包裹任意內(nèi)容
為了這個目的,我們使用transclude選項.
script.js

angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.name = 'Tobias';
}])
.directive('myDialog', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {},
    templateUrl: 'my-dialog.html'
  };
});

index.html

<div ng-controller="Controller"> <my-dialog>Check out the contents, {{name}}!</my-dialog></div>

my-dialog.html

<div class="alert" ng-transclude></div>
Paste_Image.png

transclude選項實際上做了些啥?transclude使得擁有這個選項的指令的內(nèi)容有能力去獲取指令外部的作用域

為了說明這一點,讓我們看接下來的例子.注意我們在script.jslink函數(shù)內(nèi)重新定義了nameJeff.你覺得下面{{name}}最終會顯示啥?
script.js

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) {
      scope.name = 'Jeff';
    }
  };
});

index.html

<div ng-controller="Controller">
  <my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>

my-dialog.html

<div class="alert" ng-transclude></div>

Paste_Image.png

通常來說,我們可能會以為{{name}}的值會是Jeff,然而在這個例子中我們看到{{name}}的值仍然是Tobias

transclude選項改變了作用域嵌套的方式.實際上指令調(diào)用時標簽中所嵌套的內(nèi)容是和外部作用域綁定(而不是內(nèi)部),這使得我們能夠讓內(nèi)容獲取到外部作用域

請注意:如果我們在指令中沒有創(chuàng)建它自己的作用域(這里即scope: false),我們在指令內(nèi)聲明scope.name="Jeff",會更改外部作用域中name的值,即最后輸出會是Jeff

這種行為對于那些包裹了某些內(nèi)容的指令是很有意義的,否則的話你不得不一個個手動引入你想要使用的值;如果你真的這樣做了,那是不是就意味著你其實無法真正的獲取到任意內(nèi)容了呢?

最佳實踐: 僅僅在你想要創(chuàng)建一個可以包裹任意元素的指令時,使用transclude:true

下一步,讓我們給這個對話框添加一些按鈕,允許某人使用指令來綁定一些自定義行為的函數(shù)
script.js

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

index.html

<div ng-controller="Controller">
  {{message}}
  <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
    Check out the contents, {{name}}!
  </my-dialog>
</div>

my-dialog-close.html

<div class="alert">
  <a href class="close" ng-click="close({message: 'closing for now'})">×</a>
  <div ng-transclude></div>
</div>

我們想要運行通過指令scope選項引用進來的函數(shù),但是想讓它運行在它注冊時的上下文環(huán)境中.

我們之前見過了如何在scope選項中使用=attr綁定,在上面的例子中我們使用了&attr綁定.&綁定允許指令執(zhí)行表達式在它原先的上下文作用域中的值,在某個特定的時機;任何合法的表達式都是允許的,包括包含了函數(shù)調(diào)用的表達式.因為這個原因,&綁定非常適合指令行為需要綁定函數(shù)回調(diào)的情景.

當用戶點擊了對話框中的x,指令的close函數(shù)被調(diào)用,讓我們感謝下ng-click.這個在隔離作用域中被稱為call的函數(shù)實際上計算了表達式hideDialog(message)在它原先的作用域上下文中的值,即運行ControllerhideDialog函數(shù)

通常情況下,能夠把隔離作用域中的數(shù)據(jù)通過表達式來傳遞給父級作用域這一特性是相當誘人的.我們能夠通過在指令內(nèi)部給引入的函數(shù)傳入一組key:value來實現(xiàn)這一特性.例如,hideDialog函數(shù)獲取到當對話框隱藏時需要展示的信息.這個信息是通過我們在指令內(nèi)部調(diào)用close({message: 'closing for now'})來聲明,然后本地變量message會被on-close表達式獲取到.

最佳實踐: 當你想要你的指令暴露出綁定行為的API時,使用scope選項的&attr綁定.

創(chuàng)建一個添加了事件監(jiān)聽器的指令##

以前我們使用link函數(shù)來生成一個可操作DOM元素的指令,在這個例子的基礎(chǔ)上,讓我們創(chuàng)建一個指令來實時的反映元素上的事件
不如讓我們來寫一個允許用戶拖動元素的指令吧?
script.js

angular.module('dragModule', [])
.directive('myDraggable', ['$document', function($document) {
  return {
    link: 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 content
        event.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);
      }
    }
  };
}]);

index.html

<span my-draggable>Drag Me</span>

創(chuàng)建一個可通信的指令##

你能夠在模板中使用任意指令來組合使用它們
有時,你會需要一個由不同指令的結(jié)合構(gòu)建而成的組件
想象一下你需要一個容器,容器內(nèi)激活的tab和對應(yīng)的展示內(nèi)容一致
script.js

angular.module('docsTabsExample', [])
.directive('myTabs', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {},
    controller: ['$scope', function MyTabsController($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'
  };
});

index.html

<my-tabs>
  <my-pane title="Hello">
    <p>Lorem ipsum dolor sit amet</p>
  </my-pane>
  <my-pane title="World">
    <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>

my-tabs.html

<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>

my-pane.html

<div class="tab-pane" ng-show="selected">
  <h4>{{title}}</h4>
  <div ng-transclude></div>
</div>

Paste_Image.png

指令myPane有一個require選項,值時^^myTabs,當指令使用這個選項時,$ compile會拋出錯誤,除非找到了聲明的控制器.^^前綴表示指令會在它的父元素上尋找指定的控制器.(一個^前綴表示會在元素自身或父元素上尋找指定控制器;沒有前綴的話表明指令只會在元素自身尋找)

因此這個myTabs控制器是從哪兒來的呢?指令能夠使用controller選項來聲明控制器,這毫不意外.就像你看到的這樣,myTabs指令使用了這個選項.就像ngController一樣.這個選項給指令的模板綁定了一個控制器.

如果你需要在這個模板之外調(diào)用控制器或者是任何綁定到這個控制器上的函數(shù),你能夠使用選項controllerAs來給這個控制器定義一個別名.這時指令需要定義一個作用域使得這個配置生效.這在指令被作為一個組件來使用時是特別有效的

讓我們回頭看看myPane的定義,注意link函數(shù)的最后一個參數(shù): tabsCtrl,當一個指令引入了一個控制器時,link函數(shù)接受這個控制器作為第四個參數(shù).得益于它,myPane能夠調(diào)用myTabsaddPane函數(shù)

如果需要引入多個控制器,指令的require選項能夠接收數(shù)組參數(shù).相應(yīng)的,link函數(shù)同樣接收數(shù)組作為參數(shù).

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

聰明的讀者可能會思考linkcontroller之間的區(qū)別.基本的區(qū)別是controller能夠暴露出API,link函數(shù)可以通過使用require來引入控制器

最佳實踐: 當你想要向其他指令暴露出API時使用controller,其他情況下使用link

總結(jié)##

這里我們已經(jīng)見識了指令的常見使用場景.這里的每一個例子對于你創(chuàng)建自定義指令都是一個好的開始.
你也許會對指令編譯過程深層次的解釋很感興趣.這些東西會在下一篇編譯指南中講到.

寫在后面

  • 首先是起因,使用angular進行項目開發(fā)大半年了,這兩天要帶新入職的妹子,被問到一些問題,發(fā)現(xiàn)有些東西自己都記得不是很清楚了,以前也沒做過系統(tǒng)性的總結(jié),于是打算重新看一遍官方文檔,于是就有了這篇官網(wǎng)上開發(fā)指南的翻譯
  • 指令在angular中無疑非常重要,于是第一篇先從自定義指令開始吧,后續(xù)應(yīng)該會再挑幾篇進行翻譯.
  • 自己水平,文筆有限,翻譯難免有錯誤疏漏之處,不吝指正,
  • 原文在此, AngularJS開發(fā)者指南之自定義指令
  • 文中所有實例代碼有功夫的話后續(xù)會上傳到github
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末头谜,一起剝皮案震驚了整個濱河市症概,隨后出現(xiàn)的幾起案子残腌,更是在濱河造成了極大的恐慌,老刑警劉巖吼畏,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件来屠,死亡現(xiàn)場離奇詭異篮条,居然都是意外死亡凉逛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玛痊,“玉大人汰瘫,你說我怎么就攤上這事±奚罚” “怎么了混弥?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長对省。 經(jīng)常有香客問我蝗拿,道長,這世上最難降的妖魔是什么蒿涎? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任哀托,我火速辦了婚禮,結(jié)果婚禮上劳秋,老公的妹妹穿的比我還像新娘仓手。我一直安慰自己,他們只是感情好玻淑,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布嗽冒。 她就那樣靜靜地躺著,像睡著了一般补履。 火紅的嫁衣襯著肌膚如雪添坊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天干像,我揣著相機與錄音帅腌,去河邊找鬼驰弄。 笑死麻汰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的戚篙。 我是一名探鬼主播五鲫,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岔擂!你這毒婦竟也來了位喂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乱灵,失蹤者是張志新(化名)和其女友劉穎塑崖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痛倚,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡规婆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抒蚜。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡掘鄙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗡髓,到底是詐尸還是另有隱情操漠,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布饿这,位于F島的核電站浊伙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏长捧。R本人自食惡果不足惜吧黄,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唆姐。 院中可真熱鬧拗慨,春花似錦、人聲如沸奉芦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽声功。三九已至烦却,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間先巴,已是汗流浹背其爵。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伸蚯,地道東北人摩渺。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像剂邮,于是被迫代替她去往敵國和親摇幻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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