angular directive详解

在前端开发的过程中,很多时候我们都希望能够重复的使用某一个模块,比如说文件上传、组织架构树等,angular的directive可以帮助我们轻松的实现组件的重复使用。

首先先让我们解释一个问题:什么是directive?

directive是DOM元素的一个标记(可以是属性、元素名称、类名和文本内容),告诉angular的compiler给这个元素添加指定的行为,或者改变这个元素及其子元素。

angular提供了很多内置的directive(比如ngBind、ngModel和ngClass)方便我们使用,就像可以创建controller和service一样,我们也可以创建自己的directive。

下面让我们创建一个directive,这个directive仅仅用一个静态的模板替换它的内容。

index.html

<div ng-controller="Controller">
  <div my-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

<div ng-controller="Controller">
  <div my-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

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

<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="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

<div ng-controller="Controller">
  Date format: <input ng-model="format"> <hr/>
  Current time is: <span my-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(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
  };
}]);
前面我们学习了可以通过isolate scope传递值或者对象供directive使用,但有些时候我们需要传递整个模板,这时候我们需要使用transclude属性选项,考虑下面代码的输出结果是什么:

index.html

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

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

我们在controller和directive的link方法中都定义了name属性,根据前面讲的isolate scope的知识,似乎显示的结果应该是Jeff,但是,如果你这样想就错啦,因为transclude属性修改了scope的嵌入方式,使得在directive的内容中能够访问所有外部的scope变量而不是内部的scope变量,所以最后的显示结果为:

Check out the contents, Tobias!

下面我们在对话框中添加一个按钮,允许用户绑定特定的行为:

index.html

<div ng-controller="Controller">
  {{message}}
  <my-dialog ng-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

<div class="alert">
  <a href class="close" ng-click="close({message: 'closing for now'})">&times;</a>
  <div ng-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

<span my-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 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);
      }
    }
  };
}]);
最后让我们创建一个directive,此directive根据我们点击的tab展示不同的内容。

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

<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" ng-transclude>
</div>
当我们在directive中使用require属性,如果没有找到指定的controller,$compile将会报错,^前缀指明在父controller中寻找,没有^前缀则在自己的元素中查找。

如果directive中指定了require属性,那么可以在其link方法中将该controller作为第四个参数传入。如果需要多个controller,那么require属性后面可以跟一个数组,同样link方法的第四个参数传入也是一个数组,

传入需要的controller列表。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值