咸鱼开发修炼之路

angular中的directive

  angular也用了大半年了,写个文章总结一下一些重要的机制吧。
  angular1.x版本中的一个一个重要特性就是指令(Directive)。指令功能十分强大,他的作用简单来说就是可以给HTML元素赋予特殊或自定义的行为,比如监听事件、视图模板代理等。
19.jpg

指令的形式

在AngularJS中,指令有四种表现形式:

  • E(元素):<directiveName></directiveName>
  • A(属性):<div directiveName='expression'></div>
  • C(类):<div class='directiveName'></div>
  • M(注释):<--directive:directiveName expression-->

可以在创建指令时通过设置restrict属性来定义指令的形式,一个指令可以同时有多种表现形式,Angular 1.3 +默认使用EA。

指令的命名

一般情况下在定义指令时推荐使用驼峰命名法,比如ngModel、ngApp,但是在HTML中大小写是不敏感的,所以在HTML中使用指令时推荐使用小写字母加破折号的形式,比如ng-modelng-app
除了使用小写破折号这种方式,还有以下几种使用写法比较少用

  • ng:model
  • ng_model
  • data-ng-bind
  • x-ng-bind

创建指令

指令也是通过AngularJS的Model创建的,使用directive(name, directiveFactory)方法创建指令,第一个参数是指令名称,第二个参数是一个工厂函数,该函数需要返回一个对象,通过配置该对象中的不同属性从而令AngularJS内置的$compile服务实现指令的不同功能。
下面是一个创建指令的简单demo( 来源 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
angular
.module('app')
.directive('myExample', myExample);
function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
link: linkFunc,
controller : ExampleController,
controllerAs: 'vm',
bindToController: true // because the scope is isolated
};
return directive;
function linkFunc(scope, el, attr, ctrl) {
console.log('LINK: scope.min = %s *** should be undefined', scope.min);
console.log('LINK: scope.max = %s *** should be undefined', scope.max);
console.log('LINK: scope.vm.min = %s', scope.vm.min);
console.log('LINK: scope.vm.max = %s', scope.vm.max);
}
}
ExampleController.$inject = ['$scope'];
function ExampleController($scope) {
// Injecting $scope just for comparison
var vm = this;
vm.min = 3;
console.log('CTRL: $scope.vm.min = %s', $scope.vm.min);
console.log('CTRL: $scope.vm.max = %s', $scope.vm.max);
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
1
2
3
4
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="{{vm.max}}"/></div>
<div>min={{vm.min}}<input ng-model="{{vm.min}}"/></div>

在HTML页面中使用:
<div my-example max="77"></div>

指令属性配置

下面详细介绍各个可配置的属性:

restrict

上文已经介绍了指令的四种形式,restrict属性用于指定指令的形式,一个指令可以存在多种形式.例如restrict:'EA'则表示指令在DOM里面可用元素形式和属性形式被声明,如果想支持IE8,最好使用属性和类形式来定义。

template:

指定一个内嵌模板,可以使字符串(一段HTML文本)或者函数。当为函数时,可接受两个参数tElement和tAttrs。第一个参数代表当前的HTML DOM元素,而第二个参数为实例的属性,它是一个由元素上所有的属性组成的集合(对象)

templateUrl

指定模板加载的URL,当已经设置template时不会生效。
由于加载html模板是通过异步加载的,若加载大量的模板会拖慢网站的速度。
可以通过先缓存模板来加快加载模板的速度。
当模板很多时,可以通过给模板指令设置相关属性,从而动态的加载UI模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// modules.js
var mainModule = angular.module("mainModule", []);
mainModule.controller("MyController", function() {
this.name = "Jason";
this.job = "Developer";
});
mainModule.directive("myDirective", function() {
return {
templateUrl: function(elem, attr) {
return "myTemplate-" + attr.type + ".html";
}
};
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo for Directive</title>
<script src="../angular-1.5.8.js"></script>
<script src="modules.js"></script>
</head>
<body ng-app="mainModule">
<div ng-controller="MyController as mc">
<my-directive type="name"></my-directive>
<my-directive type="job"></my-directive>
<!-- <div my-directive type="name"></div> -->
<!-- <div my-directive type="job"></div> -->
</div>
</body>
</html>
<!-- myTemplate-name.html -->
Name: {{mc.name}}
<!-- myTemplate-job.html -->
Job: {{mc.job}}

scope

该属性用于配置指令的作用域。关于AngularJS作用域的详细说明可以参考 AngularJS作用域Scope的继承

  1. 默认值false:表示继承父作用域;
    除非该directive与html不存在数据绑定,一般情况下建议使用后几种方式。
  2. true:表示继承父作用域,并创建自己的作用域(子作用域);
    directive创建一个子作用域, 并且会从父作用域进行原型继承。
    如果同一个DOM element存在多个directives要求创建子作用域,那么只有一个子作用域被创建,多个directives将共用该子作用域。
  3. {}:表示创建一个全新的隔离(Isolate)作用域,没有原型继承。
    这是创建可复用directive组件的最佳选择。因为它不会直接访问/修改父作用域的属性,不会产生意外的副作用。这种directive与父作用域进行数据通信有如下几种方式,如果绑定的隔离作用域属性名与元素的属性名相同,则可以采取缺省写法(前)。
    1. = or =attr “Isolate”作用域的属性与父作用域的属性进行双向绑定
      任何一方的修改均影响到对方,这是最常用的方式;
    2. @ or @attr “Isolate”作用域的属性与父作用域的属性进行单向绑定
      即“Isolate”作用域只能读取父作用域的值,并且该值永远的String类型
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
       
      app.controller('MainController',function(){});
          app.directive('helloWorld',function(){
           return {
              scope: {color:'@colorAttr'},  //指明了隔离作用域中的属性color应该绑定到属性colorAttr
              restrict: 'AE',
              replacetrue,
              template'<p style="background-color:">Hello World</p>'      
           }
          });
      <hello-world color-attr=''></hello-world>
    3. & or &attr “Isolate”作用域把父作用域的属性包装成一个函数,可用于调用父作用域的函数,包装方法是$parse,详情请见 API-$parse
      “Isolate”作用域的proto是一个标准Scope object (the picture below needs to be updated to show an orange ‘Scope’ object instead of an ‘Object’.)“Isolate”作用域的$parent同样指向父作用域。它虽然没有原型继承,但它仍然是一个子作用域。
      如下directive:
      <my-directive interpolated="" twowayBinding="parentProp2">
      scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
      link函数中:
      scope.someIsolateProp = "I'm isolated"
      请注意,我们在link函数中使用attrs.$observe(‘interpolated’, function(value) { … }来监测@属性的变化。
      更多请参考 这里

transclude

(布尔值或者字符‘element’),默认值为false;用于设置是否开启指令的内嵌机制。
如果为true,directive会新建一个“transcluded”子作用域,并且会从父作用域进行原型继承。
指令标签之间的内容可以被指定嵌入UI模板中被ng-transclude内置指令标记过的DOM元素中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
  <div ng-controller='MainController'>
     
          <hello-world>
            {{name}}
          </hello-world>
       
  </div>
  <script>
    var app = angular.module('myApp',[]);
    app.controller('MainController',function($scope){
      $scope.name = 'hehe';
    });
    app.directive('helloWorld',function(){
     return {
        scope:{},  
        restrict: 'AE',
        transclude: true,
        template: '<div class="b"><div ng-transclude>你看不见我</div></div>'
     }
    });
  </script>
</body>

生成html时可以发现文本“你看不见我”被transclude内容替换掉了。

‘element’与true的区别?
当transclude:true时候,嵌入的内容为,而当transclude:“element”时候,嵌入的内容为<hello-world></hello-world>。需要注意的是,当transclude设置为‘element’时,指令的repalce属性需要设置为true,详见 这里

replace

当被设置为true时,渲染出来的HTML会替换原标签。

priority

一个数字,用于设置该指令优先级,若在单个DOM上有多个指令,则优先级高的会先执行,
angularjs内置指令的ng-repeat的优先级为1000,ng-init的优先级为450。不常用。

terminal

布尔类型,若设置为true,则优先级低于此指令的其他指令则无效,不会被调用(优先级相同的还是会执行)

controller

可以是一个字符串或者函数。若是为字符串,则将字符串当做是控制器的名字,来查找注册在应用中的控制器的构造函数。
和在Module中创建Controller很类似,可以在参数中注入需要的AngularJS服务,和Module中的Controller相比,指令中的Controller有一些特殊的服务(参数)可以注入
(1)$scope,与指令元素相关联的作用域
(2)$element,当前指令对应的元素
(3)$attrs,由当前元素的属性组成的对象
(4)$transclude,嵌入链接函数,实际被执行用来克隆元素和操作DOM的函数(不建议写在Controller中,DOM操作最好写在link中)
controller和后面会介绍到的link函数非常相似,但其实两者职能不同。

controllerAs

指定Controller的名称,和view使用controller as保持一致,注意:当你把controller注入到link的函数或可访问的directive的attributes时,你可以把它命名为控制器的属性。
function linkFunc(scope, el, attr, vm) //把vm直接传递进来

bindToController

当directive中使用了controller as语法时,如果你想把父级作用域绑定到directive的controller作用域时,使用bindToController = true。这使得把外部作用域绑定到directive controller中变得更加简单。
注意:Angular 1.3.0才介绍了bindToController。

require

字符串或者数组。
指令之间的交互主要是以指令的Controller为桥梁来实现的,这里的交互指的是子指令与父指令之间的交互,我们可以使用指令的require属性设置要引用的父指令的Controller,
这里有几种配置方式:

  • require: "controllerName":只查找指令自己的Controller,如果找不到任何控制器,则会抛出一个error。
  • require: "?controllerName":只查找指令自己的Controller,如果找不到任何控制器,则会将null传给link连接函数的第四个参数。
  • require: "^controllerName":查找指令自己的Controller以及父指令的Controller。
  • require: "^^controllerName":只查找父指令的Controller。
  • require: ["^controllerName1", "^controllerName2"]:引用多个Controller。

link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM。会晚于controller函数执行。
该函数共有五个参数:

- scope:指令的作用域,默认是父节点Controller的作用域,如果指令有创建自己的作用域,那么则指指令自己的作用域。  
- element:指令的jQLite(jQuery的子集)包装的DOM元素,可以通过该参数操作指令所在的DOM元素。   
- attrs:指令所在DOM元素的属性对象,通过.语法可以获取到给DOM元素添加的属性。   
- controller:指令通过require属性引用的Controller实例。   
- transcludeFn:嵌入函数。  

通过指令操作DOM元素:

1
2
3
4
5
6
7
8
9
10
11
var mainModule = angular.module("mainModule", []);
mainModule.directive("myDirective", function($interval) {
return {
restrict: "A",
link: function(scope, element, attrs) {
$interval(function() {
element.text(new Date());
}, 1000);
}
};
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo for Directive</title>
<script src="../angular-1.5.8.js"></script>
<script src="modules.js"></script>
</head>
<body ng-app="mainModule">
<div>
Current Date is: <span my-directive></span>
</div>
</body>
</html>