angularJS自定义指令的高级指导
2017-06-03 04:06

此文是angularJS的directive指导的翻译,翻译自angularJS高级指导

创建自定义指令

这个文档讲解当你想在angularJS中创建自定义指令时,应该如何实现他们。

什么是指令

更进一步,指令是一个元素上的标记(例如属性,元素名称,注解或者CSS类),指令指示angularJS HTML模板绑定一个特定的逻辑行为到一个DOM元素上(例如,通过event监听器),甚至改变DOM元素或者他的子元素。


“compile”(编译)HTML模板意味着什么?对于angularJS,“compilation”(编译器)方法绑定指令和HTML以关联他们的交互性。我们使用“compile”这个词的原因是因为这个绑定指令的递归过程反映了编译程序语言编译源代码的过程。


指令匹配

在写指令之前,我们需要知道HTML compiler(编译器)如何决定何时使用给定的指令。

和元素匹配选择器时使用的术语相同,当指令是元素的表达式的一部分时,我们说元素匹配一个指令。

在下面的例子中,我们说<input>元素匹配"ngModel"指令:

<input ng-model="foo">

下面的例子<input>元素也匹配“ngModel”:

<input data-ng-model="foo">

并且下面的<person>元素匹配<person>指令:

<person>{{name}}</person>

规范化

angularJS规范一个元素的标签和属性用来决定哪一个元素匹配哪一个指令。我们通常通过区分大小写的驼峰式名称来应用一个指令。然而,由于HTML是不区分大小写的,我们通过小写形式来指定DOM中的指令,DOM属性通常使用短划线连接(例如 ng-model)。

规范化过程如下:

@1:从元素或属性的前面去除"x-"和"data-"。

@2:转化“:”、“-”或者“_”限定符名称为驼峰式写法。

例如,下面的形式都是相等效果的并且都匹配"ngBind"指令。

<div ng-controller="Controller">
  Hello <input ng-model='name'> <hr/>
  <span ng-bind="name"></span> <br/>
  <span ng:bind="name"></span> <br/>
  <span ng_bind="name"></span> <br/>
  <span data-ng-bind="name"></span> <br/>
  <span x-ng-bind="name"></span> <br/>
</div>
angular.module('docsBindExample', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)';
}]);
it('should show off bindings', function() {
  var containerElm = element(by.css('div[ng-controller="Controller"]'));
  var nameBindings = containerElm.all(by.binding('name'));

  expect(nameBindings.count()).toBe(5);
  nameBindings.each(function(elem) {
    expect(elem.getText())
    .toEqual('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)');
  });
});

最佳实践:优先使用中划线格式(例如:ng-bind 表示 ngBind)。如果你想要使用HTML验证工具,你应该使用"data-"前缀版本替代(例如:data-ng-bind表示 ngBind)。上面例子中的其他用法是历史遗留原因,但你应该避免使用他们。

指令类型

"$compile"可以匹配通过元素名称(E),属性(A),类名称(C),和注解(M)来匹配指令。

angularJS支持的内置指令类型可以在文档中查看。

下面示范在一个模板中匹配所有的4中类型指令的各种方法(以myDir为例):

<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>

在自定义指令定义对象时可以通过“restrict”(限定)属性来指定指令匹配4种类型中的哪些,默认的是“EA”。

最佳实践:指令中优先通过标签名和属性名,而不是注释和类名。这样通常能让根据给定的元素判断使用什么指令更加容易。

最佳实践:注释指令通常被用在限制嵌套的DOM API(例如<table>元素内)中不允许创建指令的地方,angularJS 1.2版本引入了“ng-repeat-start”和“ng-repeat-end”作为这个问题更好的解决方案。推荐开发人员在可能的情况下使用他们来代替注释指令。

创建指令

        和controllers很相似,directives也是注册在modules上。注册一个指令,你应该使用"module.directive"API。“module.directive”接收一个友好的指令名称和一个factory工厂函数。这个工厂函数应该返回一个对象,这个对象包含不同的属性用来指示"$compile"匹配的指令应该如何执行逻辑。

        工厂函数只有在“compiler”编译器第一次匹配指令时仅仅执行一次。你可以在这里做任何初始化工作。像controller中的注入一样,这个工厂函数通过“$injector.invoke”执行。

        我们来看一些自定义指令的例子,然后深入探讨编译过程中不同参数的意义。

最佳实践:为了避免和将来标准的冲突,最好在自定义指令名称上加上前缀。例如,如果你定义了一个"<carousel>"指令,并且在HTML7中引入了相同的元素,那么就会产生问题。两个或三个前缀(例如 btfCarousel)就能正常运行。类似的,你也不能使用“ng”前缀,否则他们可能和将来angularJS版本的内置指令冲突。

接下来的例子中,我们将使用“my”前缀(例如,myCustomer)。

模板扩展指令

假设我们已经有一个代表客户信息的模板。这个模板在代码中重复使用很多次。当你要改变模板中的某一处时,你必须在一系列模板中改变他们。这种情况就可以使用一个指令简化模板。

让我们创建一个指令来改变静态模板中的内容:

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}}'
  };
});
<div ng-controller="Controller">
  <div my-customer></div>
</div>

注意在“$comiple”编译并且链接“<div my-customer></div>”指令后,将会在元素的子元素上尝试匹配指令。这意味着你可以通过其他的指令来组合一个指令。我们将在下面的例子中讲解如何实现指令组合。

    在上面的例子中,我们使用了内联的"template"属性,但是这对于模板扩展来说是非常麻烦的。

最佳实践:除非你的模板非常小,通常最好是分散在HTML文件片段并且通过“templateUrl”属性加载。


如果你熟悉“ngInclude",”templateUrl“跟他类似。这里有几个用”templateUrl“实现的例子:

script.js,index.html,my-customer.html文件如下:

angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    templateUrl: 'my-customer.html'
  };
});
<div ng-controller="Controller">
  <div my-customer></div>
</div>
Name: {{customer.name}} Address: {{customer.address}}

"templateUrl"也可以是一个函数,返回一个HTML模板的URL供指令加载和使用。angularJS调用"templateUrl"方法传递两个参数:@1,指令绑定的DOM元素;@2,元素上的”attr“属性。

note:你不能在”templateUrl“函数中访问”scope“变量,因为”template“模板在”scope“初始化之前调用。

script.js

angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    templateUrl: function(elem, attr) {
      return 'customer-' + attr.type + '.html';
    }
  };
});

index.html

<div ng-controller="Controller">
  <div my-customer type="name"></div>
  <div my-customer type="address"></div>
</div>

customer-name.html

Name: {{customer.name}}

customer-address.html

Address: {{customer.address}}

note:当我们创建一个指令时,默认的”restrict“是属性和元素。为了创建能通过类名(class name)触发的指令,你需要使用”restrict“属性。

”restrict“属性的常用设置有:

@1:’A‘-只匹配属性名。

@2:’E‘-只匹配元素名。

@3:’C‘-只匹配类名。

@4:’M‘-只匹配注释。

这些属性可以根据需要结合:

@@:’AEC‘-匹配属性名、元素、或类名。

让我们使用”restrict:'E'“来改变指令:

script.js

angular.module('docsRestrictDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    templateUrl: 'my-customer.html'
  };
});

index.html

<div ng-controller="Controller">
  <my-customer></my-customer></div>

my-customer.html

Name: {{customer.name}} Address: {{customer.address}}


什么时候该用元素,什么时候该用属性呢?当你创建一个模板控制的组件时使用元素,这个通常的原因是在模板的一部分上创建特定领域的语言。当在一个已经存在的元素上添加新功能时使用属性。

对于”myCustomer“指令使用元素是非常正确的选择,因为我们不是要在元素上定义一个”myCustomer“行为;你已经作为一个"customer"组件定义了元素的核心行为。

一个指令的独立作用域

我们上面的”myCustomer“指令是正确的,但是它有一个致命的缺陷,我们只能使用一个给定的”scope“使用它一次。

为了正确的使用,我们每次需要创建不同的”controller“来重用这个指令:

script.js

angular.module('docsScopeProblemExample', [])
.controller('NaomiController', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.controller('IgorController', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Igor',
    address: '123 Somewhere'
  };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    templateUrl: 'my-customer.html'
  };
});


index.html

<div ng-controller="NaomiController">
  <my-customer></my-customer>
</div>
  <hr>
<div ng-controller="IgorController">
  <my-customer></my-customer>
</div>


my-customer.html

Name: {{customer.name}} Address: {{customer.address}}

这是正确的用法,但不是最好的解决方案。

我们可以把指令中的”scope“和外层的”scope“分离开来,然后匹配外层的”scope“和指令中的”scope“。我们可以通过创建”isolate scope“隔离作用域来实现。为了实现隔离作用域,需要使用指令的”scope“属性:

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'
  };
});


index.html

<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="igor"></my-customer>
</div>


my-customer-iso.html

Name: {{customerInfo.name}} Address: {{customerInfo.address}}


效果如下:

directive2.png

在index.html文件中,第一个”<my-customer>“元素通过”info“属性绑定了”naomi“值,暴露给”controller“的”scope“作用域上。第二绑定”info“属性”igor“值。

让我们单独看"scope"属性:

//...
scope: {
  customerInfo: '=info'},
//...

”scope option“ 是一个包含各个绑定到独立作用域的属性的对象。在这个例子中只有一个属性:

@1:  名字(customerInfo)和指令的属性名(customerInfo)一致。

@2:”=info“告诉”$compile“编译器绑定到”info“属性上。

note:指令中的”scope“属性的”=attr“和指令名一样要进行格式化(驼峰式写法)。绑定属性”<div bind-to-this='thing'></div>“,你需要指定一个”=bingToThis“的绑定。


假如我们想要在指令的”scope“上绑定的属性名和相关元素上的属性名相同,你可以使用简写:

...
scope: {
  // same as '=customer'
  customer: '='
},
...

使用隔离作用域还有其他的作用,这样可以在指令的”scope“绑定不同的数据。我们可以通过添加另一个属性”vojta“,从指令的模板中获取此属性添加到”scope“上。

script.js

angular.module('docsIsolationExample', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
  $scope.vojta = { name: 'Vojta', address: '3456 Somewhere Else' };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    scope: {
      customerInfo: '=info'
    },
    templateUrl: 'my-customer-plus-vojta.html'
  };
});


index.html

<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
</div>


my-customer-plus-vojta.html

Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}

效果如下:

directive.png

注意”{{vojta.name}}“和”{{vojta.address}}“是空值,代表他们是”undefined“。尽管我们在”controller“中定义了”vojta“,但是它在指令中不可用。

指令的隔离作用域隔离任何东西,除非我们明确的通过”scope:{}“hash对象添加的model。这对于创建可复用的组件是非常有用的,因为model的改变不会影响到组件,除非你已经明确的引入了该scope。

注意:通常,一个scope通过原型从它的父类中继承属性,除非是独立scope。

最佳实践:当你要在你的应用中复用一个组件时,要使用”scope“属性来设置独立作用域。

创建一个修改DOM的指令

在这个例子中,我们创建一个展示当前时间的指令,每过一秒,它将更新DOM来反映当前的时间。

指令要想修改DOM通常通过”link“选项来注册DOM监听器或者修改DOM。在模板被拷贝之后执行并且会添加到指令逻辑绑定的地方。

”link“接收一个带有以下签名的函数:function link(scope,element,attrs,controller,transcludeFn){ ...... }。

@1:”scope“是一个angularJS scope对象。

@2:”element“是指令匹配的jqLite包装的元素。

@3:”attrs“是一个格式化的属性名和属性值作为键值对的hash对象。

@4:”controller“是指令需要的指令实例或者指令自己的控制器,准确的值取决于指令“require”的属性值。

@5:”transcludeFn“是预绑定到对应的内嵌scope的替换连接函数。

在外面的link函数中,我们想要每一秒更新一次展示的时间,无论何时用户都可以通过指令绑定的格式化字符串。我们使用“$interval”服务在指定间隔时间调用回调函数。这比使用“$timeout”回调实现更简单。在指令被移除后我们想要移除“$interval”来释放内存。

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  
  };
}]);


index.html

<div ng-controller="Controller">
  Date format: <input ng-model="format"> 
  <hr/>
  Current time is: <span my-current-time="format"></span>
</div>


这里有一些地方需要注意。

和“module.controller”相似,“module.directive"的函数参数是通过注入器进行依赖注入的。因此,我们可以在”link“函数中使用"$interval"和”dateFilter“。

我们注册一个事件”element.on('$destory',. . .)“,那么什么会触发这个"$destory"事件?

这里有一些特殊的事件通过angularJS传递。当通过angularJS的编译器编译过的DOM 节点被销毁时,它会触发“$destory”事件。类似的,当一个angularJS的scope被销毁,它会把“$destory”事件广播给监听的"scopes"。

通过监听这个"$destory"事件,你可以移除事件监听以避免内存泄漏。注册到“scopes”和元素上的监听器在“scopes”,元素被销毁时会被自动清除。但是当你在“service”注册一个监听器,或者在一个没有移除的DOM元素上注册监听器,你需要自己来清除他,否则你就引入了内存泄漏的风险。

最佳实践:指令应该在他们自己最后清除。你应该使用“element.on('destory', . . .)”或者“scope.on('$destory', . . .)”在指令销毁的时候来运行清除函数。

创建一个包含其他元素的指令

我们已经看过通过指令的独立作用域传递models(模型)到一个指令,但是有时候传递整个模板比传递一个字符串或者一个对象更加符合要求。我们创建一个“dialog box”(对话框)组件。这个对话框可以包含任意的内容。

为了达到这一效果,我们需要使用“ transclude ”(嵌入)属性。

script.js

angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.name = 'Tobias';
}])
.directive('myDialog', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {},
    templateUrl: 'my-dialog.html'
  };
});


index.html

<div ng-controller="Controller">
  <my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>


my-dialog.html

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

directive3.png

"transclude"属性明确的做了什么?“transclude”使得使用此属性的指令的内容可以访问指令外的“scope”,而不是指令内的。

为了说明这一点,看下面的例子。注意我们在“script.js”中添加了一个“link”函数用来重新定义“name”属性值为“Jeff”。那么你认为绑定的“{{name}}”将会解析成什么?

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) {
      scope.name = 'Jeff';
    }
  };
});


index.html

<div ng-controller="Controller">
  <my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>


my-dialog.html

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

效果如下:

directive3.png

    按常理来说,我们预期“{{name}}”将会是“Jeff”。然而,我们在例子中看到绑定的“{{name}}”值仍然是“Tobias”。“transclude”属性改变了“scope”的嵌套。这使得“transclude”指令的内容无论何时拥有的“scope”都是在指令外的,而不是指令内的“scope”。通过这个属性,能够让内容访问外层的“scope”。

    注意如果指令没有创建它自己的“scope”,那么“scope.name='Jeff'”所代表的“scope”代表的是外层的作用域,并且我们可以通过输出看到。

    这种特性使得包含一些内容的指令能够实现,因为如果不这样,你就需要分别传入不同的model。如果你已经传入了你想要的各个model,那么你就不能包含任意的内容,你能吗?

最佳实践:只有使用“transclude :true”,你才能创建一个包含任意内容的指令。

下面,我们在对话框中添加按钮,并且允许某人使用指令绑定他们自己的行为逻辑。

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'
  };
});


index.html

<div ng-controller="Controller">
  {{message}}  
  <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
    Check out the contents, {{name}}!  
  </my-dialog>
</div>


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>

效果如下:

directive4.png

我们想要在指令的作用域中运行这个方法,然而却是在注册的作用域上下文中运行。

我们已经看过了在“scope”中怎么使用“=attr”,在上面的例子中,我们使用“&attr”替代。在特定时间,这个“&”绑定允许指令在原始作用域上下文中触发表达式的执行。任何合法的表达式都是允许的,包括包含回调函数的表达式。因此,"&"绑定是解决在指令上绑定回调函数的好办法。

当用户点击对话框的“x”时,指令通过“ng-click”绑定的“close”方法被调用。这里在独立作用域上调用“close”方法,事实上是在原始作用域上下文中运算表达式“hideDialog(message)”,因此运行“controller”的“hideDialog”方法。

通常这是通过表达式从独立作用域向父作用域传递数据的好方法,可以传递由本地变量名和变量值组成的map对象到表达式包裹的方法。例如,“hideDialog”方法在对话框被隐藏时接收一个“message”参数用来展示。通过指令上的“close({message:'closing for now'})”来指定message信息。然后“on-close”表达式的message参数就可以使用了。

最佳实践:当你想要暴露API让使用者绑定行为时可以在“scope”上使用“&attr”。

创建一个添加监听器的指令

以前,我们使用“link”方法创建指令来修改DOM元素。基于上面的例子,我们创建一个在元素上绑定事件的指令。

例如,我们创建一个可以让用户拖动元素的指令。

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);
      }
    }
  };
}]);


index.html

<span my-draggable>Drag Me</span>

angular自定义指令拖动效果例子:angularJS自定义拖动指令


创建相互作用的指令

你可以通过模板组合任何指令。

有时,你想要通过指令的组合创建一个组件。

想象你想要创建一个带有页签的容器,容器的内容和激活的tab页签的内容一致。

script.js

angular.module('docsTabsExample', [])
.directive('myTabs', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {},
    controller: ['$scope', function MyTabsController($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'
  };
});


index.html

<my-tabs>
  <my-pane title="Hello">
    <p>Lorem ipsum dolor sit amet</p>
  </my-pane>
  <my-pane title="World">
    <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>


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">
  <h4>{{title}}</h4>
  <div ng-transclude></div>
</div>


效果如下:

directive5.png

“myPane”指令有一个“require”属性值为“^^myTabs”。当一个指令使用此属性,“$compile”将会抛出一个错误,除非找到一个指定的"controller"。

“^^”前缀意味着在它的父作用域搜索“controller”(一个“^”前缀指示指令在它自己的元素或者父元素中寻找“controller”,没有前缀,指令只会在它自己的元素中寻找)。

所以“myTabs”controller来自哪里呢?指令可以使用"controller"属性指令controller控制器。正如你所见到的一样,“myTabs”指令使用此属性。就像“ngController”,这个属性指定一个指令模板的控制器。

引用控制器或者任何方法绑定模板的控制器是十分重要的,你可以使用属性“controllerAs”指定控制器的别名。这种构造要想能被使用,需要定义一个scope作用域。当一个指令被用作组件时,这种形式是十分有用的。

回头看“myPane”的定义,注意“link”方法的最后一个参数:“tabsCtrl”。当一个指令需要控制器时,它可以作为“link”函数的第四个参数接收。利用这一点,“myPane”可以调用“myTabs”的“addPane”方法。

如果需要多个控制器,指令的“require”可以接收一个数组参数。这传入到“link”函数的对应参数也将会使一个数组。

angular.module('docsTabsExample', [])
.directive('myPane', function() {
  return {
    require: ['^^myTabs', 'ngModel'],
    restrict: 'E',
    transclude: true,
    scope: {
      title: '@'
    },
    link: function(scope, element, attrs, controllers) {
      var tabsCtrl = controllers[0],
          modelCtrl = controllers[1];

      tabsCtrl.addPane(scope);
    },
    templateUrl: 'my-pane.html'
  };
});

聪明的读者对于“link”和“controller”之间的不同会感到疑惑。这基本的区别就是,“controller”可以暴露API,“link”方法可以使用“require”参数和“controller”进行交互。

最佳实践:当你想要暴露一个API时,使用“controller”,其他的就使用“link”。

总结

在此我们学习了指令的主要事项。这里每一个例子都是创建自定义指令的好的开端。你可能对“compiler guide”(编译指导)中的深入编译过程的解释感兴趣。“$compile”API页面有用指令参数的完整参考列表。

原创文章,转载请注明来自:妹纸前端-www.webfront-js.com.
阅读(4469)
辛苦了,打赏喝个咖啡
微信
支付宝
妹纸前端
妹纸前端工作室 | 文章不断更新中
京ICP备16005385号-1