1. 程式人生 > >angular directive詳解

angular directive詳解

在前端開發的過程中,很多時候我們都希望能夠重複的使用某一個模組,比如說檔案上傳、組織架構樹等,angular的directive可以幫助我們輕鬆的實現元件的重複使用。

首先先讓我們解釋一個問題:什麼是directive?

directive是DOM元素的一個標記(可以是屬性、元素名稱、類名和文字內容),告訴angular的compiler給這個元素新增指定的行為,或者改變這個元素及其子元素。

angular提供了很多內建的directive(比如ngBind、ngModel和ngClass)方便我們使用,就像可以建立controller和service一樣,我們也可以建立自己的directive。

下面讓我們建立一個directive,這個directive僅僅用一個靜態的模板替換它的內容。

index.html

<divng-controller="Controller"><divmy-customer></div></div>

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}}'};});
展示結果

Name: Naomi Address: 1600 Amphitheatre。

當然,很多時候我們要在directive中展示的內容,絕不僅僅是一個靜態的文字,可能是一顆組織架構樹,有著複雜的樣式,這時候將html程式碼寫在directive的template屬性後就顯得程式碼很臃腫,我們可以使用templateUrl屬性,後面跟上需要載入的模板路徑,例如示例所示:

index.html

<divng-controller="Controller"><divmy-customer></div></div>

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'};});
my-customer.html
Name:{{customer.name}}Address:{{customer.address}}
需要說明的是,templateUrl後面可以是一個方法,方法返回需要載入的模板路徑,例如:
.directive('myCustomer',function(){return{
    templateUrl:function(elem, attr){return'customer-'+attr.type+'.html';}};});
該方法後面可以跟兩個引數,elem代表匹配到directive的元素,attr代表和元素關聯的物件。

當我們建立一個directive,這個directive預設按照元素和屬性進行匹配,我們可以通過restrict屬性進行匹配設定。

restrict 屬性可以設定為:

  • 'A' - 只匹配屬性名稱
  • 'E' - 只匹配元素名稱
  • 'C' - 只匹配樣式名稱

如果需要可以結合使用:

  • 'AEC' - 同時匹配屬性、元素和樣式名稱。
下面的示例中我們指定derective僅僅根據元素名稱匹配
.directive('myCustomer',function(){return{
    restrict:'E',
    templateUrl:'my-customer.html'};});
我們上面的directive寫的很棒,但是有一個致命的缺陷,就是我們的directive不能重複使用,換句話說,就是在一個指定的scope中,由於directive直接訪問controller中的customer,所以directive替換之後的結果是一樣,通過下面的示例簡單說明一下:

index.html

<divng-controller="NaomiController"><my-customer></my-customer></br><my-customer></my-customer></div>
展示結果:

Name: Naomi Address: 1600 Amphitheatre。

Name: Naomi Address: 1600 Amphitheatre。

解決問題的方法是將directive中的scope與外面的scope隔離,同時將需要在directive中使用的值對映到directive的scope中,我們將這種解決方法稱為isolate scope,下面,我們還是通過一個示例簡單演示一下:

index.html

<divng-controller="Controller"><my-customerinfo="naomi"></my-customer><hr><my-customerinfo="igor"></my-customer></div>
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'};});
my-customer-iso.html
Name:{{customerInfo.name}}Address:{{customerInfo.address}}
展示結果

Name: Naomi Address: 1600 Amphitheatre

Name: Igor Address: 123 Somewhere

在index.html中我們將naomi(定義在controller中:$scope.naomi)繫結在info屬性上面,在我們的directive中,我們通過customerInfo: '=info',將naomi繫結到customerInfo中,因此我們可以在template中通過customerInfo訪問使用者資訊。

scope:{// same as '=customer'
  customer:'='},
這是一種簡寫形式,等價於customer: '=customer'。

angular的宗旨之一是將業務邏輯和頁面展示分離,業務邏輯放在controller中,頁面展示放在template中,controller中是不推薦直接操作DOM的,所有的DOM操作應該放在directive中進行,下面我們還是用一個簡單的示例演示如何在directive中操作DOM,該示例在頁面中顯示一個時鐘,該時鐘每一秒更新一下時間。

index.html

<divng-controller="Controller">
  Date format: <inputng-model="format"><hr/>
  Current time is: <spanmy-current-time="format"></span></div>
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(newDate(), 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
  };}]);
前面我們學習了可以通過isolate scope傳遞值或者物件供directive使用,但有些時候我們需要傳遞整個模板,這時候我們需要使用transclude屬性選項,考慮下面程式碼的輸出結果是什麼:

index.html

<divng-controller="Controller"><my-dialog>Check out the contents, {{name}}!</my-dialog></div>
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, element){
      scope.name ='Jeff';}};});
my-dialog.html
<divclass="alert"ng-transclude></div>

我們在controller和directive的link方法中都定義了name屬性,根據前面講的isolate scope的知識,似乎顯示的結果應該是Jeff,但是,如果你這樣想就錯啦,因為transclude屬性修改了scope的嵌入方式,使得在directive的內容中能夠訪問所有外部的scope變數而不是內部的scope變數,所以最後的顯示結果為:

Check out the contents, Tobias!

下面我們在對話方塊中新增一個按鈕,允許使用者繫結特定的行為:

index.html

<divng-controller="Controller">
  {{message}}
  <my-dialogng-hide="dialogIsHidden"on-close="hideDialog(message)">
    Check out the contents, {{name}}!
  </my-dialog></div>
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'};});
my-dialog-close.html
<divclass="alert"><ahrefclass="close"ng-click="close({message: 'closing for now'})">&times;</a><divng-transclude></div></div>

展示結果:

×

Check out the contents, Tobias!

在上面的示例中,當我們點選×,通過ng-click呼叫close方法,'&'允許在directive中呼叫方法close,但是在close方法註冊的源scope中執行,也就是在controller中執行hideDialog方法,此外可以通過鍵值對的形式向directive外部傳遞引數,如示例中所示ng-click="close({message: 'close for now'})",此時可以在hideDialog方法中訪問message變數。

下面我們建立一個directive,這個directive允許使用者拖拽元素,讓我們以此來說明如何在directive中建立事件監聽。

index.html

<spanmy-draggable>Drag ME</span>
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 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);}}};}]);
最後讓我們建立一個directive,此directive根據我們點選的tab展示不同的內容。

index.html

<my-tabs><my-panetitle="Hello"><h4>Hello</h4><p>Lorem ipsum dolor sit amet</p></my-pane><my-panetitle="World"><h4>World</h4><em>Mauris elementum elementum enim at suscipit.</em><p><ahrefng-click="i = i + 1">counter: {{i || 0}}</a></p></my-pane></my-tabs>
script.js
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.html
<divclass="tabbable"><ulclass="nav nav-tabs"><ling-repeat="pane in panes"ng-class="{active:pane.selected}"><ahref=""ng-click="select(pane)">{{pane.title}}</a></li></ul><divclass="tab-content"ng-transclude></div></div>
my-pane.html
divclass="tab-pane"ng-show="selected"ng-transclude></div>
當我們在directive中使用require屬性,如果沒有找到指定的controller,$compile將會報錯,^字首指明在父controller中尋找,沒有^字首則在自己的元素中查詢。

如果directive中指定了require屬性,那麼可以在其link方法中將該controller作為第四個引數傳入。如果需要多個controller,那麼require屬性後面可以跟一個數組,同樣link方法的第四個引數傳入也是一個數組,

傳入需要的controller列表。