AngularJS学习笔记

18.8. 指令定义时的参数

指令定义时的参数如下:

  • name
  • priority
  • terminal
  • scope
  • controller
  • require
  • restrict
  • template
  • templateUrl
  • replace
  • transclude
  • compile
  • link

现在我们开始一个一个地吃掉它们……,但是并不是按顺序讲的。

priority
这个值设置指令的权重,默认是 0 。当一个节点中有多个指令存在时,就按着权限从大到小的顺序依次执行它们的 compile 函数。相同权重顺序不定。
terminal
是否以当前指令的权重为结束界限。如果这值设置为 true ,则节点中权重小于当前指令的其它指令不会被执行。相同权重的会执行。
restrict
指令可以以哪些方式被使用,可以同时定义多种方式。
  • E 元素方式 <my-directive></my-directive>
  • A 属性方式 <div my-directive="exp"> </div>
  • C 类方式 <div class="my-directive: exp;"></div>
  • M 注释方式 <!-- directive: my-directive exp -->
transclude
前面已经讲过基本的用法了。可以是 'element'true 两种值。
compile
基本的定义函数。 function compile(tElement, tAttrs, transclude) { ... }
link
前面介绍过了。大多数时候我们不需要单独定义它。只有 compile 未定义时 link 才会被尝试。 function link(scope, iElement, iAttrs, controller) { ... }
scope
scope 的形式。 false 节点的 scope , true 继承创建一个新的 scope , {} 不继承创建一个新的隔离 scope 。 {@attr: '引用节点属性', =attr: '把节点属性值引用成scope属性值', &attr: '把节点属性值包装成函数'}
controller
为指令定义一个 controller , function controller($scope, $element, $attrs, $transclude) { ... }
name
指令的 controller 的名字,方便其它指令引用。
require
要引用的其它指令 conroller 的名字, ?name 忽略不存在的错误, ^name 在父级查找。
template
模板内容。
templateUrl
从指定地址获取模板内容。
replace
是否使用模板内容替换掉整个节点, true 替换整个节点, false 替换节点内容。
  <a b></a>
  var app = angular.module('Demo', [], angular.noop);

  app.directive('a', function(){
    var func = function(element, attrs, link){
      console.log('a');
    }

    return {compile: func,
            priority: 1,
            restrict: 'EA'};
  });

  app.directive('b', function(){
    var func = function(element, attrs, link){
      console.log('b');
    }

    return {compile: func,
            priority: 2,
            //terminal: true,
            restrict: 'A'};
  });

上面几个参数值都是比较简单且容易理想的。

再看 scope 这个参数:

  <div ng-controller="TestCtrl">
    <div a b></div>
  </div>
 1   var app = angular.module('Demo', [], angular.noop);
 2
3 app.directive('a', function(){ 4 var func = function(element, attrs, link){ 5 return function(scope){ 6 console.log(scope); 7 } 8 } 9
10 return {compile: func, 11 scope: true, 12 restrict: 'A'}; 13 }); 14
15 app.directive('b', function(){ 16 var func = function(element, attrs, link){ 17 return function(scope){ 18 console.log(scope); 19 } 20 } 21
22 return {compile: func, 23 restrict: 'A'}; 24 }); 25
26 app.controller('TestCtrl', function($scope){ 27 $scope.a = '123'; 28 console.log($scope); 29 });

对于 scope

  • 默认为 falselink 函数接受的 scope 为节点所在的 scope
  • true 时,则 link 函数中第一个参数(还有 controller 参数中的 $scope ), scope 是节点所在的 scopechild scope ,并且如果节点中有多个指令,则只要其中一个指令是 true 的设置,其它所有指令都会受影响。

这个参数还有其它取值。当其为 {} 时,则 link 接受一个完全隔离(isolate)的 scope ,于 true 的区别就是不会继承其它 scope 的属性。但是这时,这个 scope 的属性却可以有很灵活的定义方式:

@attr 引用节点的属性。

  <div ng-controller="TestCtrl">
    <div a abc="here" xx="{{ a }}" c="ccc"></div>
  </div>
  var app = angular.module('Demo', [], angular.noop);

  app.directive('a', function(){
    var func = function(element, attrs, link){
      return function(scope){
        console.log(scope);
      }
    }

    return {compile: func,
            scope: {a: '@abc', b: '@xx', c: '@'},
            restrict: 'A'};
  });

  app.controller('TestCtrl', function($scope){
    $scope.a = '123';
  });
  • @abc 引用 div 节点的 abc 属性。
  • @xx 引用 div 节点的 xx 属性,而 xx 属性又是一个变量绑定,于是 scopeb 属性值就和 TestCtrla 变量绑定在一起了。
  • @ 没有写 attr name ,则默认取自己的值,这里是取 div 的 c 属性。

=attr 相似,只是它把节点的属性值当成节点 scope 的属性名来使用,作用相当于上面例子中的 @xx

  <div ng-controller="TestCtrl">
    <div a abc="here"></div>
  </div>
  var app = angular.module('Demo', [], angular.noop);

  app.directive('a', function(){
    var func = function(element, attrs, link){
      return function(scope){
        console.log(scope);
      }
    }

    return {compile: func,
            scope: {a: '=abc'},
            restrict: 'A'};
  });

  app.controller('TestCtrl', function($scope){
    $scope.here = '123';
  });

&attr 是包装一个函数出来,这个函数以节点所在的 scope 为上下文。来看一个很爽的例子:

  <div ng-controller="TestCtrl">
    <div a abc="here = here + 1" ng-click="show(here)">这里</div>
    <div>{{ here }}</div>
  </div>
 1   var app = angular.module('Demo', [], angular.noop);
 2
3 app.directive('a', function(){ 4 var func = function(element, attrs, link){ 5 return function llink(scope){ 6 console.log(scope); 7 scope.a(); 8 scope.b(); 9
10 scope.show = function(here){ 11 console.log('Inner, ' + here); 12 scope.a({here: 5}); 13 } 14 } 15 } 16
17 return {compile: func, 18 scope: {a: '&abc', b: '&ngClick'}, 19 restrict: 'A'}; 20 }); 21
22 app.controller('TestCtrl', function($scope){ 23 $scope.here = 123; 24 console.log($scope); 25
26 $scope.show = function(here){ 27 console.log(here); 28 } 29 });

scope.a 是 &abc ,即:

  scope.a = function(){here = here + 1}

只是其中的 hereTestCtrl 的。

scope.b 是 &ngClick ,即:

  scope.b = function(){show(here)}

这里的 show()here 都是 TestCtrl 的,于是上面的代码最开始会在终端输出一个 124

当点击“这里”时,这时执行的 show(here) 就是 llink 中定义的那个函数了,与 TestCtrl 无关。但是,其间的 scope.a({here:5}) ,因为 a 执行时是 TestCtrl 的上下文,于是向 a 传递的一个对象,里面的所有属性 TestCtrl 就全收下了,接着执行 here=here+1 ,于是我们会在屏幕上看到 6

这里是一个上下文交错的环境,通过 & 这种机制,让指令的 scope 与节点的 scope 发生了互动。真是鬼斧神工的设计。而实现它,只用了几行代码:

  case '&': {
    parentGet = $parse(attrs[attrName]);
    scope[scopeName] = function(locals) {
      return parentGet(parentScope, locals);
    }
    break;
  }

再看 controller 这个参数。这个参数的作用是提供一个 controller 的构造函数,它会在 compile 函数之后, link 函数之前被执行。

  <a>haha</a>
 1   var app = angular.module('Demo', [], angular.noop);
 2
3 app.directive('a', function(){ 4 var func = function(){ 5 console.log('compile'); 6 return function(){ 7 console.log('link'); 8 } 9 } 10
11 var controller = function($scope, $element, $attrs, $transclude){ 12 console.log('controller'); 13 console.log($scope); 14
15 var node = $transclude(function(clone_element, scope){ 16 console.log(clone_element); 17 console.log('--'); 18 console.log(scope); 19 }); 20 console.log(node); 21 } 22
23 return {compile: func, 24 controller: controller, 25 transclude: true, 26 restrict: 'E'} 27 });

controller 的最后一个参数, $transclude ,是一个只接受 cloneAttachFn 作为参数的一个函数。

按官方的说法,这个机制的设计目的是为了让各个指令之间可以互相通信。参考普通节点的处理方式,这里也是处理指令 scope 的合适位置。

  <a b>kk</a>
 1   var app = angular.module('Demo', [], angular.noop);
 2
3 app.directive('a', function(){ 4 var func = function(){ 5 } 6
7 var controller = function($scope, $element, $attrs, $transclude){ 8 console.log('a'); 9 this.a = 'xx'; 10 } 11
12 return {compile: func, 13 name: 'not_a', 14 controller: controller, 15 restrict: 'E'} 16 }); 17
18 app.directive('b', function(){ 19 var func = function(){ 20 return function($scope, $element, $attrs, $controller){ 21 console.log($controller); 22 } 23 } 24
25 var controller = function($scope, $element, $attrs, $transclude){ 26 console.log('b'); 27 } 28
29 return {compile: func, 30 controller: controller, 31 require: 'not_a', 32 restrict: 'EA'} 33 });

name 参数在这里可以用以为 controller 重起一个名字,以方便在 require 参数中引用。

require 参数可以带两种前缀(可以同时使用):

  • ? ,如果指定的 controller 不存在,则忽略错误。即:

      require: '?not_b'
    

    如果名为 not_b 的 controller 不存在时,不会直接抛出错误, link 函数中对应的 $controllerundefined

  • ^ ,同时在父级节点中寻找指定的 controller ,把上面的例子小改一下:

      <a><b>kk</b></a>
    

    arequire 改成(否则就找不到 not_a 这个 controller ):

      require: '?^not_a'
    

还剩下几个模板参数:

template 模板内容,这个内容会根据 replace 参数的设置替换节点或只替换节点内容。
templateUrl 模板内容,获取方式是异步请求。
replace 设置如何处理模板内容。为 true 时为替换掉指令节点,否则只替换到节点内容。
  <div ng-controller="TestCtrl">
    <h1 a>原始内容</h1>
  </div>
  var app = angular.module('Demo', [], angular.noop);

  app.directive('a', function(){
    var func = function(){
    }

    return {compile: func,
            template: '<p>标题 {{ name }} <button ng-click="name=\'hahaha\'">修改</button></p>',
            //replace: true,
            //controller: function($scope){$scope.name = 'xxx'},
            //scope: {},
            scope: true ,
            controller: function($scope){console.log($scope)},
            restrict: 'A'}
  });

  app.controller('TestCtrl', function($scope){
    $scope.name = '123';
    console.log($scope);
  });

template 中可以包括变量引用的表达式,其 scope 遵寻 scope 参数的作用(可能受继承关系影响)。

templateUrl 是异步请求模板内容,并且是获取到内容之后才开始执行指令的 compile 函数。

最后说一个 compile 这个参数。它除了可以返回一个函数用为 link 函数之外,还可以返回一个对象,这个对象能包括两个成员,一个 pre ,一个 post 。实际上, link 函数是由两部分组成,所谓的 preLinkpostLink 。区别在于执行顺序,特别是在指令层级嵌套的结构之下, postLink 是在所有的子级指令 link 完成之后才最后执行的。 compile 如果只返回一个函数,则这个函数被作为 postLink 使用:

  <a><b></b></a>
 1   var app = angular.module('Demo', [], angular.noop);
 2
3 app.directive('a', function(){ 4 var func = function(){ 5 console.log('a compile'); 6 return { 7 pre: function(){console.log('a link pre')}, 8 post: function(){console.log('a link post')}, 9 } 10 } 11
12 return {compile: func, 13 restrict: 'E'} 14 }); 15
16 app.directive('b', function(){ 17 var func = function(){ 18 console.log('b compile'); 19 return { 20 pre: function(){console.log('b link pre')}, 21 post: function(){console.log('b link post')}, 22 } 23 } 24
25 return {compile: func, 26 restrict: 'E'} 27 });