angular directive详解

来源:互联网 发布:python 多次try 编辑:程序博客网 时间:2024/05/22 00:46

在前端开发的过程中,很多时候我们都希望能够重复的使用某一个模块,比如说文件上传、组织架构树等,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列表。

0 0
原创粉丝点击