Heim  >  Artikel  >  Web-Frontend  >  Ausführliche Erklärung zum Erstellen benutzerdefinierter Anweisungen in AngularJS

Ausführliche Erklärung zum Erstellen benutzerdefinierter Anweisungen in AngularJS

高洛峰
高洛峰Original
2016-12-07 17:17:491059Durchsuche

Das Beispiel in diesem Artikel beschreibt, wie Sie benutzerdefinierte Anweisungen in AngularJS erstellen. Teilen Sie es als Referenz mit allen. Die Details lauten wie folgt:

Dies ist eine Übersetzung von Anweisungen von Angular-Entwicklern. Hauptsächlich für Entwickler gedacht, die bereits mit den Grundlagen der Angular-Entwicklung vertraut sind. In diesem Dokument wird erklärt, wann es notwendig ist, eigene Anweisungen zu erstellen und wie diese erstellt werden.

Was ist eine Direktive?

Auf hoher Ebene ist eine Direktive eine Beschreibung des eckigen $compile-Dienstes DOM-Zeit wird der Compiler angewiesen, das angegebene Verhalten an das DOM anzuhängen.

Der Vorgang ist sehr einfach. Angular verfügt über viele integrierte Anweisungen, wie z. B. ngBind und ngView. So wie Sie Controller und Dienste erstellen, können Sie auch Ihre eigenen Anweisungen erstellen. Wenn Angular gestartet wird, analysiert der Compiler von Angular den HTML-Code, um den Anweisungen zu entsprechen, sodass Anweisungen das Verhalten registrieren oder das DOM ändern können.

Übereinstimmende Anweisungen

Bevor wir Anweisungen schreiben, müssen wir zunächst wissen, wie angle mit einer Anweisung übereinstimmt. Im folgenden Beispiel sagen wir, dass das Eingabeelement mit der ngModel-Anweisung übereinstimmt.

<input ng-model="foo">

Die folgende Methode passt auch zu ngModel:

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

Angular normalisiert das Tag und Attributnamen eines Elements, um zu bestimmen, welches Element mit welcher Direktive übereinstimmt. Wir verweisen auf Direktiven (wie ngModel), indem wir standardisierte CamelCase-Namen in js verwenden. In HTML werden durch „-“ getrennte Attributnamen häufig zum Aufrufen von Anweisungen verwendet (z. B. ng-model). Standardisierungsprozess:

-Entfernen Sie das x- auf dem Element oder Attribut und Daten-Präfix

-konvertieren Sie „:“, „-“ und „_-“ zur Benennung in Kamel-Schreibweise


Die folgenden Beispiele zeigen verschiedene Möglichkeiten, die ngBind-Direktive abzugleichen

<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/>

Best Practice: Geben Sie der Benennung im „-“-Format Vorrang (z. B. ng-bind stimmt mit ngBind überein). Wenn Sie das HTML-Validierungstool übergeben möchten, können Sie das Präfix „data-“ verwenden (z. B. data-ng-bind). Aus historischen Gründen gibt es andere Formatnamen. Vermeiden Sie deren Verwendung.

Der $compile-Dienst kann Anweisungen basierend auf Elementnamen, Attributnamen, Klassennamen und Anmerkungen abgleichen.

Alle intern von Angular bereitgestellten Anweisungen stimmen mit Attributnamen, Tag-Namen, Anmerkungen oder Klassennamen überein . Die folgenden verschiedenen Arten können in

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

Best Practice analysiert werden: Geben Sie der Verwendung von Tag-Namen und Attributnamen Vorrang, um Anweisungen zu verwenden. Dies erleichtert das Verständnis, mit welchem ​​Element das angegebene Element übereinstimmt.

Best Practice: Die Annotationsmethode wird normalerweise in der DOM-API verwendet, um die Erstellung von Anweisungen einzuschränken, die sich über mehrere Elemente, wie z. B. Tabellenelemente, erstrecken, und um wiederholte Verschachtelungen zu begrenzen, daher wird die Annotationsmethode verwendet. In AngularJS Version 1.2 wird dieses Problem durch die Verwendung von ng-repeat-start und ng-repeat-end als bessere Lösung gelöst. Dieser Ansatz wird nach Möglichkeit empfohlen.

Bindung von Text und Attributen

Während des Kompilierungsprozesses verwendet der Compiler den $interpolate-Dienst, um zu erkennen, ob die übereinstimmenden Text- und Attributwerte eingebettete Ausdrücke enthalten. Diese Ausdrücke werden als Uhren registriert und können während des Digest-Zyklus aktualisiert werden.

<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>

Bindung des ngAttr-Attributs

Browser sind manchmal sehr wählerisch, was die Attributwerte angeht, die sie für zulässig halten (d. h. a bestimmte Die Attribute einiger Elemente können nicht willkürlich zugewiesen werden, andernfalls wird ein Fehler gemeldet.

Zum Beispiel:

<svg>
 <circle cx="{{cx}}"></circle>
</svg>

Bei Verwendung dieser Schreibmethode wird in der Konsole ein Fehler gemeldet: Fehler: Ungültiger Wert für das Attribut cx="{{cx}}". Dies liegt an den Einschränkungen der SVG DOM API. Sie können nicht einfach cx="{{cx}}" schreiben.

Verwenden Sie ng-attr-cx um dieses Problem zu lösen

Wenn ein gebundenes Attribut das Präfix ngAttr (oder ng-attr) verwendet, wird es beim Binden auf das entsprechende Attribut ohne Präfix angewendet. Mit dieser Methode können Sie sofort an das Attribut binden, das Sie benötigen. Vom Browser verarbeitete Attribute (z. B. das Attribut „circle[cx]“ des SVG-Elements).

Wir können also so schreiben, um das obige Problem zu beheben:

<svg>
 <circle ng-attr-cx="{{cx}}"></circle>
</svg>

Befehl erstellen

Zuerst wir Lassen Sie uns über die API zum Registrieren von Anweisungen sprechen. Die Anweisungen werden auf dem Modul registriert. Der Unterschied besteht darin, dass die Anweisungen über die module.directive-API registriert werden. module.directive akzeptiert einen standardisierten Namen und eine Factory-Funktion. Diese Factory-Funktion gibt ein Objekt zurück, das verschiedene Konfigurationen enthält. Dieses Objekt wird verwendet, um dem $compile-Dienst mitzuteilen, wie mit dem nächsten Schritt fortgefahren werden soll.

Die Factory-Funktion wird nur einmal aufgerufen, wenn der Compiler zum ersten Mal mit der Anweisung übereinstimmt. Normalerweise wird die Initialisierungsarbeit in der Factory-Funktion durchgeführt. Diese Funktion wird mit $injector.invoke aufgerufen und kann daher wie ein Controller für die Abhängigkeitsinjektion verwendet werden.

Best Practice: Geben Sie der Rückgabe eines definierten Objekts Vorrang vor der Rückgabe einer Funktion.

Als nächstes werden wir uns zunächst einige gängige Beispiele ansehen und dann ausführlich auf die Prinzipien und den Kompilierungsprozess verschiedener Konfigurationselemente eingehen.

Best Practice: 为了避免与某些未来的标准命名冲突,最好前缀化你自己的指令,比如你创建一个dfdf839e4285f6d29afc838443bb892c指令,它可能会产生冲突,加入HTML7引入相同的元素。推荐使用两三个单词的前缀(比如btfCarousel),同样不能使用ng或者其他可能与angular未来版本起冲突的前缀。

以下的例子中,我们统一使用my前缀。

模板扩展的指令

当你有大量代表客户信息的模板。这个模板在你的代码中重复了很多次,当你改变一个地方的时候,你不得不在其他地方同时改动,这时候,你就要使用指令来简化你的模板。

我们来创建一个指令,简单的时候静态模板来替换它的内容。

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

   

JS

angular.module(&#39;docsSimpleDirective&#39;, [])
 .controller(&#39;Ctrl&#39;, function($scope) {
 $scope.customer = {
  name: &#39;Naomi&#39;,
  address: &#39;1600 Amphitheatre&#39;
 };
 })
 .directive(&#39;myCustomer&#39;, function() {
 return {
  template: &#39;Name: {{customer.name}} Address: {{customer.address}}&#39;
 };
 });

   

注意我们这里做了一些绑定,$compile编译链接dc2b607441da0f63f26f57c02dd103ad 16b28748ea4df4d9c2150843fecfba68之后,它将会匹配子元素的指令。这意味着你可以组合一些指令。以下例子中你会看到如何做到这一点。

这个例子中,我们直接在template配置项里写上模板,但是随着模板大小的增加,这样非常不优雅。

Best Practice: 除非你的模板非常小,否则更好的是分割成单独的hmtl文件,然后使用templateUrl选项来加载。

假如你熟悉ngInclude,templateUrl跟它非常类似。现在我们使用templateUrl方式重写上面的例子:

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

   

JS:

angular.module(&#39;docsTemplateUrlDirective&#39;, [])
 .controller(&#39;Ctrl&#39;, function($scope) {
 $scope.customer = {
  name: &#39;Naomi&#39;,
  address: &#39;1600 Amphitheatre&#39;
 };
 })
 .directive(&#39;myCustomer&#39;, function() {
 return {
  templateUrl: &#39;my-customer.html&#39;
 };
 });

   

my-customer.html

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

   

非常好,但是如果我们想让我们的指令匹配标签名b5ec7581f2dd2b6bdff0e91d4906055c? 如果我们只是简单的把b5ec7581f2dd2b6bdff0e91d4906055c元素放在hmtl上面,会发现没有效果。

Note: 创建指令的时候,默认仅使用属性的方式。为了创建一个能由元素名字触发的指令,你需要用到restrict配置。

restrict配置可以按如下方式设置:
-'A' 仅匹配属性名字
-'E' 仅匹配元素名字
-'AE' 可以匹配到属性名字或者元素名

所以,我们可以使用 restrict: 'E'配置我们指令。

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

   

JS

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

   

my-customer.html

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

   

Note: 什么时候使用属性名或元素名呢? 当创建一个含有自己模板的组件的时候,需要使用元素名,如果仅仅是为已有的元素添加功能的话,使用属性名。

使用元素名做为myCustomer指令是非常正确的决定,因为你不是用一些'customer'行为来点缀元素,而是定义一个具有自己行为的元素作为customer组件。

隔离指令的作用域

上面我们的myCustomer指令已经非常好了,但是它有个致命的缺陷,我们在给定的作用域内仅能使用一次。

它现在的实现是,我们每次重用该指令的时候都要为它新创一个控制器。

<div ng-controller="NaomiCtrl">
 <my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorCtrl">
 <my-customer></my-customer>
</div>

   

JS

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

   

my-customer.html

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

   

这很明显不是一个好的解决方案。

我们想要做的是能够把指令的作用域与外部的作用域隔离开来,然后映射外部的作用域到指令内部的作用域。可以通过创建isolate scope来完成这个目的。这样的话,我们使用指令的scope配置。

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

   

JS

angular.module(&#39;docsIsolateScopeDirective&#39;, [])
 .controller(&#39;Ctrl&#39;, function($scope) {
 $scope.naomi = { name: &#39;Naomi&#39;, address: &#39;1600 Amphitheatre&#39; };
 $scope.igor = { name: &#39;Igor&#39;, address: &#39;123 Somewhere&#39; };
 })
 .directive(&#39;myCustomer&#39;, function() {
 return {
  restrict: &#39;E&#39;,
  scope: {
  customer: &#39;=customer&#39;
  },
  templateUrl: &#39;my-customer.html&#39;
 };
 });

   

my-customer.html

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

   

首先看hmtl,第一个b5ec7581f2dd2b6bdff0e91d4906055c绑定内部作用域的customer到naomi。这个naomi我们在控制器中已经定义好了。第二个是绑定customer到igor。

现在看看scope是如何配置的。

//...
scope: {
 customer: &#39;=customer&#39;
},
//...

   

属性名(customer)是myCustomer指令上isolated scope的变量名。它的值(=customer)告诉编译器绑定到customer属性。

Note: 指令作用域配置中的'=attr'属性名是被规范化过后的名字,比如要绑定a3b95b23d96360596431d5277e3a09ca中的属性,你就要使用'=bindToThis'的绑定。

对于属性名和你想要绑定的值的名字一样,你可以使用这样的快捷语法:

//...
scope: {
 // same as &#39;=customer&#39;
 customer: &#39;=&#39;
},
//...

   

使用isolated scope还有另外一个用处,那就是可以绑定不同的数据到指令内部的作用域。

在我们的例子中,我们可以添加另外一个属性vojta到我们的作用域,然后在我们的指令模板中访问它。

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

   

JS

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

   

my-customer-plus-vojta.html

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

   

注意到,{{vojta.name}}和{{vojta.address}}都是空的,意味着他们是undefined, 虽然我们在控制器中定义了vojta,但是在指令内部访问不到。

就像它的名字暗示的一样, 指令的isolate scope隔离了除了你添加到作用域:{}对象中的数据模型外的一切东西。这对于你要建立一个可重复使用的组件是非常有用的,因为它阻止了除了你想要传入的数据模型外其他东西改变你数据模型的状态。

Note: 正常情况下,作用域是原型继承自父作用域。但是isolate scope没有这样的继承。

Best Practice: 当你想要使你的组件在应用范围内可重用,那么使用scope配置去创建一个isolate scopes

创建一个操作DOM的指令

在这个例子中,我们会创建一个显示当前时间的指令,每秒一次更新DOM以正确的显示当前的时间。

指令修改DOM通常是在link配置中,link选项接受一个带有如下标签的函数function link(scope,element,attrs) {...}其中:

-scope是angular scope对象
-element指令匹配的jqLite封装的元素(angular内部实现的类jquery的库)
-attrs是一个带有规范化后属性名字和相应值的对象

在我们的link函数中,我们更新显示时间每秒一次,或者当用户改变指定绑定的时间格式字符串的时候。我们也要移除定时器,当指令被删除的时候,以避免引入内存泄露。

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

   

JS

angular.module(&#39;docsTimeDirective&#39;, [])
 .controller(&#39;Ctrl2&#39;, function($scope) {
 $scope.format = &#39;M/d/yy h:mm:ss a&#39;;
 })
 .directive(&#39;myCurrentTime&#39;, function($timeout, 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();
  });
  function scheduleUpdate() {
  // save the timeoutId for canceling
  timeoutId = $timeout(function() {
   updateTime(); // update DOM
   scheduleUpdate(); // schedule the next update
  }, 1000);
  }
  element.on(&#39;$destroy&#39;, function() {
  $timeout.cancel(timeoutId);
  });
  // start the UI update process.
  scheduleUpdate();
 }
 return {
  link: link
 };
 });

   

这里有很多东西值得注意的,就像module.controller API, module.directive中函数参数是依赖注入,因此,我们可以在Link函数内部使用$timeout和dataFilter服务。

我们注册了一个事件element.on('$destroy', ...), 是什么触发了这个$destory事件呢?

AngularJS会触发一些特定的事件,当一个被angular编译过的DOM元素被移除的时候,它会触发一个$destory事件,同样的,当一个angular作用域被移除的时候,它会向下广播$destory事件到所有监听的作用域。

通过监听事件,你可以移除可能引起内存泄露的事件监听器,注册在元素和作用域上的监听器在它们被移除的时候,会自动会清理掉,但是假如注册一个事件在服务或者没有被删除的DOM节点上,你就必须手工清理,否则会有内存泄露的风险。

Best Practice:执行被移除的时候应该做一些清理的操作, 可以使用element.on('$destroy', ...)或者scope.on('$destroy', ...)来运行解除绑定的函数,

创建包裹其他元素的指令

我们现在已经实现了,使用isolate scopes传递数据模型到指令里面。但是有时候我们需要能够传递一整个模板而不是字符串或者对象。让我们通过创建'dialog box'组件来说明。这个'dialog box'组件应该能够包裹任意内容。

要实现这个,我们使用transclude配置

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

   

JS

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

   

my-dialog.html

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

   

这个transclude配置用来干嘛呢? transclude使带有这个配置的指令的内容能够访问指令外部的作用域。

参照以下例子,注意到我们增加了一个link函数,在这个link函数内部我们重定义了name属性的值为Jeff,那么现在这个{{name}}会被解析成哪个值呢?

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

   

JS

angular.module(&#39;docsTransclusionDirective&#39;, [])
 .controller(&#39;Ctrl&#39;, function($scope) {
 $scope.name = &#39;Tobias&#39;;
 })
 .directive(&#39;myDialog&#39;, function() {
 return {
  restrict: &#39;E&#39;,
  transclude: true,
  templateUrl: &#39;my-dialog.html&#39;,
  link: function (element, scope) {
  scope.name = &#39;Jeff&#39;;
  }
 };
 });

   

my-dialog.html

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

   

一般,我们会认为{{name}}会被解析为Jeff,然而这里,我们看到这个例子中的{{name}}还是被解析成了Tobias.

transclude配置改变了指令相互嵌套的方式,他使指令的内容拥有任何指令外部的作用域,而不是内部的作用域。为了实现这个,它给指令内容一次访问外部作用域的机会。

这样的行为对于包裹内容的指令是非常有意义的。因为如果不这样的话,你就必须分别传入每个你需要使用的数据模型。如果你需要传入每个要使用的数据模型,那么你就无法做到适应各种不同内容的情况。

Best Practice: 仅当你要创建一个包裹任意内容的指令的时候使用transclude:true。

下一步,我们增加一个按钮到'dialog box'组件里面,允许用户使用指令绑定自己定义的行为。

<div ng-controller="Ctrl">
  <my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true">
  Check out the contents, {{name}}!
  </my-dialog>
</div>

   

JS

angular.module(&#39;docsIsoFnBindExample&#39;, [])
 .controller(&#39;Ctrl&#39;, function($scope, $timeout) {
 $scope.name = &#39;Tobias&#39;;
 $scope.hideDialog = function () {
  $scope.dialogIsHidden = true;
  $timeout(function () {
  $scope.dialogIsHidden = false;
  }, 2000);
 };
 })
 .directive(&#39;myDialog&#39;, function() {
 return {
  restrict: &#39;E&#39;,
  transclude: true,
  scope: {
  &#39;close&#39;: &#39;&onClose&#39;
  },
  templateUrl: &#39;my-dialog-close.html&#39;
 };
 });

   

my-dialog-close.html

<div class="alert">
 <a href class="close" ng-click="close()">×</a>
 <div ng-transclude></div>
</div>

   

我们想要通过在指令的作用域上调用,来运行我们传递进去的函数,但是这个函数是运行在定义时候的上下文(js通常都是这样子的)。

先前我们看到如何scope配置使用'=prop',但是在上文的例子中,我们使用'&prop','&'绑定开放了一个函数到isolated scope,允许 isolated scope调用它,同时维持原来函数的作用域(这里的作用域都是指$scope)。所以当一个用户点击x时候,就会运行Ctrl控制器的close函数。

Best Practice: 当你的指令想要开放一个API去绑定特定的行为,在scope配置中使用'&prop'.

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

先前,我们使用link函数创建一个操作DOM元素的指令,基于上面的例子,我们创建一个在元素上添加事件监听的指令。

举个例子,假如我们想要创建一个让用户可拖拽的元素,该怎么做呢?

Drag ME

JS

angular.module(&#39;dragModule&#39;, []).
 directive(&#39;myDraggable&#39;, function($document) {
 return function(scope, element, attr) {
  var startX = 0, startY = 0, x = 0, y = 0;
  element.css({
  position: &#39;relative&#39;,
  border: &#39;1px solid red&#39;,
  backgroundColor: &#39;lightgrey&#39;,
  cursor: &#39;pointer&#39;
  });
  element.on(&#39;mousedown&#39;, function(event) {
  // Prevent default dragging of selected content
  event.preventDefault();
  startX = event.screenX - x;
  startY = event.screenY - y;
  $document.on(&#39;mousemove&#39;, mousemove);
  $document.on(&#39;mouseup&#39;, mouseup);
  });
  function mousemove(event) {
  y = event.screenY - startY;
  x = event.screenX - startX;
  element.css({
   top: y + &#39;px&#39;,
   left: x + &#39;px&#39;
  });
  }
  function mouseup() {
  $document.unbind(&#39;mousemove&#39;, mousemove);
  $document.unbind(&#39;mouseup&#39;, mouseup);
  }
 }
 });

   

创建相互通信的指令

你可以通过在模板使用指令来组合任何指令。

有时候,你想要一个指令从其他的指令上面创建

想象你想要一个带有tab的容器,容器的内容对应于激活的tab。

<my-tabs>
  <my-pane title="Hello">
  <h5 id="creating-custom-directives_source_hello">Hello</h5>
  <p>Lorem ipsum dolor sit amet</p>
  </my-pane>
  <my-pane title="World">
  <h5 id="creating-custom-directives_source_world">World</h5>
  <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>

   

JS

angular.module(&#39;docsTabsExample&#39;, [])
 .directive(&#39;myTabs&#39;, function() {
 return {
  restrict: &#39;E&#39;,
  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: &#39;my-tabs.html&#39;
 };
 })
 .directive(&#39;myPane&#39;, function() {
 return {
  require: &#39;^myTabs&#39;,
  restrict: &#39;E&#39;,
  transclude: true,
  scope: {
  title: &#39;@&#39;
  },
  link: function(scope, element, attrs, tabsCtrl) {
  tabsCtrl.addPane(scope);
  },
  templateUrl: &#39;my-pane.html&#39;
 };
 });

   

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>

   

myPane指令有一个require:'^myTabs'的配置,当指令使用这个配置,$compile服务叫myTabs的指令并获取它的控制器实例,如果没有找到,将会抛出一个错误。'^'前缀意味着指令在它的父元素上面搜索控制器(没有'^'前缀的话,指令默认会在自身的元素上面搜索指定的指令)。

这里myTabs的控制器是来自何处呢?通过使用controller配置可以为指令指定一个控制器, 上问例子中myTab就是使用这个配置。就像ngController, 这个配置为指令的模板绑定了一个控制器。

再看我们的myPane's定义,注意到link函数的最后一个参数: tabCtrl,当一个指令包含另一个指令(通过require方式),它会接收该指令的控制器实例作为link函数的第四个参数,利用这个,myPane可以调用myTabs的addPane函数。

精明的读者可能想知道link跟controller之间的区别,最基本的区别就是控制器开放一个API(就是这个控制器实例可以被其他实例读取到),link函数可以通过require与控制器交互。

Best Practice: 当你要开放一个API给其他指令的时候使用控制器,否则使用link函数。

总结

这里我们讲解了一个些指令的主要使用案例。每一个都可以作为你创建自己指令的很好的起点。

如果你想更深入的了解编译的处理过程,可以查看compiler guide相关内容

$compile API页面有directive每个配置项的具体解释,可以参阅。

希望本文所述对大家AngularJS程序设计有所帮助。


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn