创建自定义指令
注意:本指南是针对已经熟悉AngularJS基础的开发者。如果您只是想要开始,建议您先去看教程。如果你在寻找指令的API,我们最近把他移动到$compile
这个文档解释当我们想要在AngularJS 应用中建立自己的指令时,该如何实现。
什么是指令?
从一个高的层次看,指令是DOM元素(属性、元素名称、注释、或CSS样式类)上的标记告诉AngularJS的HTML 编译器($compile)去附加特定的行为到DOM元素或者是变换DOM元素和它的子元素。
Angular 内置了一个指令集,比如ngBind,ngModel,和ngClass。非常像是你创建一个Controllers和Services,你可以创建自己的指令用于Angular。当Angular穷的那个你的应用,HTML编译器通过DOM匹配指令。
“编译”HTML模板是什么意思? 对于AngularJS, “编译” 表示附加事件到HTML上建立交互效果。我们使用“编译”这个术语的原因是指令的递归处理借鉴了编译程序语言编译源代码的过程。
匹配指令
我们写一个指令前,我们需要知道Angular的HTML编译器决定何时使用给定的指令。
在下面的例子中,我们说这个<input>元素匹配ngModel指令。
<input ng-model=“foo”>
下面的代码也匹配ngModel:
<input data-ng:model=“foo”>
标准化
Angular标准化一个元素的标签和属性名称去确定一个元素匹配哪个指令。我们通常引用指令通过区分大写小的驼峰标准名称(例如 ngModel)。不过,HTML是不区分大小写的,我们在DOM上引用指令通过小写方式,通常在元素上使用中划线分割属性名(例如 ng-model)。
标准化的过程如下:
- 从元素或者属性去除x-和data-前缀
- 转换带有分隔符
:
,-
,或_
的名称为驼峰格式:
举例来说,下面的方式是相同的都匹配ngBind指令。
代码语言:javascript复制<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>
代码语言:javascript复制angular.module('docsBindExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)';
}]);
最佳时间: 优先使用-分隔的格式。(例如ng-bind
对应ngBind
)。如果你想使用HTML验证工具,你可以替代使用data-为前缀的版本 (例如 data-ng-bind
对应ngBind
). 其他显示在上面的形式出于兼容的原因也是支持的,不过我们建议你避免使用。
指令的类型
$compile可以在元素名称,属性,样式类名称,甚至是注释上匹配指令。下面演示了不同方式的指令能够在模板中引用:
代码语言:javascript复制<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
最佳实践: 优先使用指令通过标签名、属性名,比注释和class名要好。所以一般就简单的提供一个元素匹配表示一个指令。
最佳实践:注释指令通常使用在DOM API 限制无法创建指令到多个元素的情况(例如内部的table元素)。AngularJS1.2 采用了ng-repeat-start 和 ng-repeat-end 更好的解决了这个问题。鼓励开发者尽可能的去使用这个在自定义的注视指令上。
文本和属性的绑定
在编译处理过程中,编译器使用$interpolate服务匹配文本和属性、查看是否包含嵌套的表达式。这些表达式注册为watches并且将局部更新作为普通编译周期的一部分,一个插值例子展示在下面:
代码语言:javascript复制<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
ngAttr
属性绑定
我们的浏览器有时候会更多的需要考虑有效的属性。
举例,就这个模板而言:
代码语言:javascript复制<svg>
<circle cx="{{cx}}"></circle>
</svg>
我们大概期望Angular能够绑定,但是当我们检查控制台看到的类似于Errlr:Invalid value for attribute cx =”{{cx}}”。因为SVG DOM API 的约束,你不能简单的写作cx=”{{cx}}”
用ng-attr-cx你可以绕过这个问题。
如果绑定的属性前缀是ngAttr(标准化之前的是ng-attr-),则在绑定过程中它将应用于相应的没有前缀的属性。这允许你绑定到属性,否则浏览器会着急的处理它(例如一个SVG元素的cricle[cx]属性)。当使用ngAttr,$interpolate的allOrNothing标记已经在使用,所以如果任何的表达式在插值字符串结果是undefined,则这个属性将会移除不会添加到元素。
举例子,我们可以修复上面的例子,写为:
代码语言:javascript复制<svg> <circle ng-attr-cx="{{cx}}"></circle> </svg>
如果你想要修改一个驼峰命名的属性(SVG元素具有有效的驼峰命名的属性),比如viewBox属性在svg元素,你可以使用下下划线去表示这个属性去绑定驼峰命名。
举例,去绑定viewBox,我们可以写成:
代码语言:javascript复制<svg ng-attr-view_box="{{viewBox}}"> </svg>
创建指令
首先让我们说一下关于注册指令的API。非常像是控制器,指令也是注册在模块上。去注册一个指令,你使用module.directive API,module.directive需要一个标准的指令名称在工厂函数中。这个工厂函数应该返回一个基于不同选项的对象告诉$compile 当指令有匹配的时候如何做。
这个工厂函数在编译器第一次匹配指令的时候执行。你可以在这里执行任何的初始化工作。这个函数使用$injector.invoke执行使得它可以接受注入,类似于控制器。
最佳实践: Prefer using the definition object over returning a function.更好的是使用一个定义对象返回一个函数。
我们将通过一些指令的通用的例子,深入的了解不同的选项和编译过程。
最佳实践:为了避免某些未来的标准,最好给您的指令使用前缀。举个例子来说,如果你创建一个<carousel>指令,你可能会在HTML7引入的相同的元素发生而问题。2到3个短的前缀工作的很好。同样的,不要给自己的指令使用ng前缀或者你认为未来版本的angular可能会引起冲突的名称。
在下面的例子中,我们将使用前缀my(例如 myCustomer)。
模板扩展指令
假设你有一块表示客户信息的模板。这个模板在你的代码中重复了多次。当你修改一个地方,你需要去修改其他地方的几个。这是一个好机会使用一个指令去简化你的模板。
Let’s create a directive that simply replaces its contents with a static template:
让我们创建一个静态的模板上的指令,简单的替换它的内容:
代码语言:javascript复制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}}'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<div my-customer></div>
</div>
Notice that we have bindings in this directive. After $compile
compiles and links <div
my-customer></div>
, it will try to match directives on the element’s children. This means you can compose directives of other directives. We’ll see how to do that in an example below.
注意这样我们就做了指令的绑定。$comple编译和链接<div my-customer></div>之后,它将尝试去匹配指令到元素的子元素。这意味着你可以将多个指令组合起来。下我们将看到如何去做。
In the example above we in-lined the value of the template
option, but this will become annoying as the size of your template grows.
在上面的例子中,我们将template选项的值写在了一行之中,但是这是我们为了使沉长的模板尺寸更好看。
最佳实践: Unless your template is very small, it’s typically better to break it apart into its own HTML file and load it with thetemplateUrl
option.除非你的模板很小,它通常最好拆分成几个HTML文件并且使用templateUrl属性来读取。
If you are familiar with ngInclude
, templateUrl
works just like it. Here’s the same example using templateUrl
instead:
如果你见过ngInclude,templateUrl的工作就和它是一样的。这里有一个相同的例子,使用templateUrl替代演示:
代码语言:javascript复制angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
templateUrl: 'my-customer.html'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<div my-customer></div>
</div>
templateUrl
can also be a function which returns the URL of an HTML template to be loaded and used for the directive. Angular will call the templateUrl
function with two parameters: the element that the directive was called on, and an attr
object associated with that element.
templateUrl 也可以是一个函数来返回HTML模板的url,用来读取模板并且用于指令。Angular将调用templateUrl函数基于两个参数,一个是指令是在哪个元素上被调用,和一个attr属性关联相关的元素。
注意: You do not currently have the ability to access scope variables from the templateUrl
function, since the template is requested before the scope is initialized.
你没有能力从templateUrl函数中访问scope中的变量,因为这个模板是在作用域初始化完毕前加载的。
代码语言:javascript复制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';
}
};
});
代码语言:javascript复制<div ng-controller="Controller">
<div my-customer type="name"></div>
<div my-customer type="address"></div>
</div>
代码语言:javascript复制Name: {{customer.name}}
代码语言:javascript复制Address: {{customer.address}}
注意: When you create a directive, it is restricted to attribute and elements only by default. In order to create directives that are triggered by class name, you need to use the restrict
option.当你创建一个指令,它将默认受限于属性和元素。为了创建指令将会根据class name触发,你必须使用restrict组合。
The restrict
option is typically set to:
这些约束的选项通常有:
'A'
– only matches attribute name- A – 值匹配属性名
'E'
– only matches element name- E – 只匹配元素名称
'C'
– only matches class name- C – 只匹配css类名
These restrictions can all be combined as needed: 这些限制可以全部合并在需要的时候:
'AEC'
– matches either attribute or element or class name- AEC – 匹配属性、元素名称、或类名。
Let’s change our directive to use restrict:
'E'
:
让我们修改我们的指令去使用restrict:’E’;
代码语言:javascript复制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'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<my-customer></my-customer>
</div>
For more on the restrict
property, see the API docs.
查看更多关于约束限制的属性,可以查看API 文档;
When should I use an attribute versus an element? Use an element when you are creating a component that is in control of the template. The common case for this is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.
什么时候我应该使用属性而不是元素? 当你在模板中创建一个控制器的组件的时候,你应该使用元素。通常情况是当你创建一个特定领域的语言给你的模板。当你装饰一个已经存在的元素赋予更多的功能的时候,你应该使用属性。
Using an element for the myCustomer
directive is clearly the right choice because you’re not decorating an element with some “customer” behavior; you’re defining the core behavior of the element as a customer component.
使用一个元素去对应myCustomer指令是一个显然正确的选择,因为你没有装饰一个元素一些“customer”的行为;你定义了customer组件的核心行为。
指令的作用域隔离
Our myCustomer
directive above is great, but it has a fatal flaw. We can only use it once within a given scope.
我们的myCustomer指令很棒,但是它有一个致命的缺陷,我们只能在指定的作用域中使用它一次。
In its current implementation, we’d need to create a different controller each time in order to re-use such a directive:
现在的实现方式,为了重复使用我们的指令,我们必须每次创建不同的控制器。
代码语言:javascript复制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'
};
});
代码语言:javascript复制<div ng-controller="NaomiController">
<my-customer></my-customer>
</div>
<hr>
<div ng-controller="IgorController">
<my-customer></my-customer>
</div>
This is clearly not a great solution.
这显然不是一个好的解决办法。
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive’s inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive’s scope
option:
我们想要有效的分隔作用域内的指令,并且映射外面的作用域到一个指令的内部作用域,我们可以做到,通过创建创建成为隔离作用域。如果这样使用,我们将创建隔离的作用域。为了这个,我们可以使用作用域的scope选项:
代码语言:javascript复制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'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
<hr>
<my-customer info="igor"></my-customer>
</div>
Looking at index.html
, the first <my-customer>
element binds the info
attribute to naomi
, which we have exposed on our controller’s scope. The second binds info
to igor
.
让我们看卡index.html,第一个元素是<my-customer>元素绑定了info属性到naomi,我们而且将他曝光在了我们的controller作用域。第二个绑定info到igor.
Let’s take a closer look at the scope option:
我们近一点看看scope选项
代码语言:javascript复制//...
scope: {
customerInfo: '=info'
},
//...
The scope option is an object that contains a property for each isolate scope binding. In this case it has just one property:
scope 选项是一个对象,包含 每一个隔离的作用域板顶。这样它本身就只是一个属性。
- Its name (
customerInfo
) corresponds to the directive’s isolate scope propertycustomerInfo
. - 属性名(customerInfo)相当于指令的隔离作用域中的属性customerInfo。
- Its value (
=info
) tells$compile
to bind to theinfo
attribute. - 它的值(=info)告诉$compile去绑定info属性。
Note: These =attr
attributes in the scope
option of directives are normalized just like directive names. To bind to the attribute in<div
bind-to-this="thing">
, you’d specify a binding of =bindToThis
.
注意: 这个=attr
属性在 scope
选项 是一个标准化类似于指令的名称 .去绑定属性到<div
bind-to-this="thing">
,你需要设置值为 =bindToThis
.
For cases where the attribute name is the same as the value you want to bind to inside the directive’s scope, you can use this shorthand syntax:
举例当你想要绑定到作用域的属性名与值相同的话,你可以简写为以下的语法:
代码语言:javascript复制...
scope: {
// same as '=customer'
customer: '='
},
...
Besides making it possible to bind different data to the scope inside a directive, using an isolated scope has another effect.除此之外,还使它可能去绑定不同的数据到指令的作用域。
We can show this by adding another property, vojta
, to our scope and trying to access it from within our directive’s template:
我们可以添加其他的属性:vojta,到我们的作用域,并且尝试在我们的指令中访问它。
代码语言:javascript复制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'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
</div>
代码语言:javascript复制Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}
Notice that {{vojta.name}}
and {{vojta.address}}
are empty, meaning they are undefined. Although we defined vojta
in the controller, it’s not available within the directive.
注意{{vojta.name}}和{{vojta.address}}是空的,说明他们是undefined。虽然我们定义了vojta在控制器中,但是它在指令中是无效的。
As the name suggests, the isolate scope of the directive isolates everything except models that you’ve explicitly added to thescope:
{}
hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
顾名思义,指令的隔离作用域隔离了除模块中明确添加到scope对象的任何东西。这在构建可复用组件时很有用,因为它防止组件在修改你的model状态时只是你明确允许的哪些。
注意: Normally, a scope prototypically inherits from its parent. An isolated scope does not. See the “Directive Definition Object – scope” section for more information about isolate scopes.
一般情况,一个作用域继承自它的父类,一个隔离的作用域则不继承。查看”DIrective Definition Object “了解更多关于隔离作用域的信息。
最佳实践: Use the scope
option to create isolate scopes when making components that you want to reuse throughout your app.当你创建一个你希望能够在你的应用内复用的组件,使用scope选项去创建一个隔离作用域。
创建一个指令操作DOM
In this example we will build a directive that displays the current time. Once a second, it updates the DOM to reflect the current time.
在例子中,我们将创建一个指令来显示当前的时间。每一秒,它更新DOM显示当前时间。
Directives that want to modify the DOM typically use the link
option. link
takes a function with the following signature,function link(scope, element, attrs)
{
...
}
where:
指令要修改DOM,通常使用link选项。link接受一个函数,是这个样子:function link(scope, element, attrs)
{
...
}
scope
is an Angular scope object. 是angular作用域对象element
is the jqLite-wrapped element that this directive matches.是一个指令匹配到的那个jqLite包装后的元素attrs
is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values. 是一个键值对对象,保存着属性的名称和值。
In our link function, we want to update the displayed time once a second, or whenever a user changes the time formatting string that our directive binds to. We will use the interval service to call a handler on a regular basis. This is easier than using timeout but also works better with end-to-end testing, where we want to ensure that all timeouts have completed before completing the test. We also want to remove the interval if the directive is deleted so we don’t introduce a memory leak.
在我们的link函数,我们想每一秒更新显示一次时间,或者我们的指令能够处理任何时候用户修改时间的格式。我们将使用interval服务规律的调用handler方法。这是一个比使用timeout简单而且能更好的用于端到端的测试,因为我们要确保在完全测试前完成所有的timeout调用。我们还希望如果指令删除的时候能够删除interval避免内存泄漏。
代码语言:javascript复制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
};
}]);
代码语言:javascript复制<div ng-controller="Controller">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
</div>
There are a couple of things to note here. Just like the module.controller
API, the function argument in module.directive
is dependency injected. Because of this, we can use $interval
and dateFilter
inside our directive’s link
function.
这里有一两件事需要注意。正像 module.controller API,module.directive的函数参数是依赖注入的。因为这样,我们能够在我们的指令内部link函数中使用$interval和 dateFilter。
We register an event element.on('destroy', ...). What fires this destroy event?
我们注册一个事件 element.on(‘destroy’,…)。如何触发这个destroy事件?
There are a few special events that AngularJS emits. When a DOM node that has been compiled with Angular’s compiler is destroyed, it emits a destroy event. Similarly, when an AngularJS scope is destroyed, it broadcasts a destroy event to listening scopes.
这里有几个AngularJS触发的特殊事件。当一个DOM节点被angular编译后,销毁了,它会触发destroy时间。同样的,当时一个AngularJS作用域销毁了,它将广播destroy事件到监听的作用域。
By listening to this event, you can remove event listeners that might cause memory leaks. Listeners registered to scopes and elements are automatically cleaned up when they are destroyed, but if you registered a listener on a service, or registered a listener on a DOM node that isn’t being deleted, you’ll have to clean it up yourself or you risk introducing a memory leak.
基于监听这个事件,你可以删除事件监听器可能会引起内存泄漏。监听器注册在作用域和元素,当它们销毁的时候会被自动的清理,但是你要注册一个监听器到service或者注册一个监听器到DOM节点,并且不要删除。你将收拾干净或者引入一个内存泄漏问题。
最佳实践: Directives should clean up after themselves. You can use element.on('$destroy',
...)
orscope.$on('$destroy',
...)
to run a clean-up function when the directive is removed. 指令应该清理他们自身,你可以使用element.on(‘$destroy’,…) 或者scope.$on(‘$destroy’,…)去清理函数当指令删除的时候。
创建一个指令包含其他的元素
We’ve seen that you can pass in models to a directive using the isolate scope, but sometimes it’s desirable to be able to pass in an entire template rather than a string or an object. Let’s say that we want to create a “dialog box” component. The dialog box should be able to wrap any arbitrary content.
我们可以看到你能够通过一个模型给一个指令使用隔离的作用域,但是有些时候,它理想的是能够通过整个模板而不是一个字符串或者对象。让我们去创建一个“对话窗口”组件,这个对话窗口应该能够包含任何的内容。
To do this, we need to use the transclude
option.
为此,我们要必须使用transclude选项。
代码语言:javascript复制angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'my-dialog.html'
};
});
代码语言:javascript复制<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
What does this transclude
option do, exactly? transclude
makes the contents of a directive with this option have access to the scopeoutside of the directive rather than inside.
trasclude选项到底做了什么?transclude标记一个指令中的内容基于这个选项可以访问这个指令以外的作用域。
To illustrate this, see the example below. Notice that we’ve added a link
function in script.js
that redefines name
as Jeff
. What do you think the {{name}}
binding will resolve to now?
为了演示,我们看下面的例子。注意我们添加了一个link函数到script.js重新定义了name为Jeff。你认为{{name}}现在被绑定到哪个值上了呢?
代码语言:javascript复制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';
}
};
});
代码语言:javascript复制<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
Ordinarily, we would expect that {{name}}
would be Jeff
. However, we see in this example that the {{name}}
binding is still Tobias
.
一般情况下我们预计{{name}}应该是Jeff。不过,我们在例子汇总看到的是{{name}}仍然是Tobias。
The transclude
option changes the way scopes are nested. It makes it so that the contents of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope.
这个transclude选项更改作用域嵌套。它标记转换后的指令里的内容无论如何会使用外部的作用域,而不是内部的作用域。在这样的情况下,它让内容访问的是外部的作用域。
Note that if the directive did not create its own scope, then scope
in scope.name =
'Jeff';
would reference the outside scope and we would see Jeff
in the output.
注意,如果这个指令没有创建它自己的作用域,然后将在作用域中设置scope.name=’Jeff’;它将引用外部的作用域,我们将在输出中看到Jeff。
This behavior makes sense for a directive that wraps some content, because otherwise you’d have to pass in each model you wanted to use separately. If you have to pass in each model that you want to use, then you can’t really have arbitrary contents, can you?
指令的这种行为是合理的,它包装一些内容,否则你将不得不在每一个模型中传递。如果你不得不在你想要的每个模型中传递,那你将不能真正随心所欲的写内容,你能吗?
最佳实践: only use transclude:
true
when you want to create a directive that wraps arbitrary content. 只有当你想创建一个指令想自由的控制内容时,你才使用transclude:true.
Next, we want to add buttons to this dialog box, and allow someone using the directive to bind their own behavior to it.
另外,我们希望添加一个按钮到这个弹出窗口,并且允许使用这个指令去绑定自己的行为。
代码语言:javascript复制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'
};
});
代码语言:javascript复制<div ng-controller="Controller">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
Check out the contents, {{name}}!
</my-dialog>
</div>
We want to run the function we pass by invoking it from the directive’s scope, but have it run in the context of the scope where it’s registered.
我们希望运行的函数通过指令的作用域来执行,而且要让他它在注册过的上下文中执行。
We saw earlier how to use =attr
in the scope
option, but in the above example, we’re using &attr
instead. The &
binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time. Any legal expression is allowed, including an expression which contains a function call. Because of this, &
bindings are ideal for binding callback functions to directive behaviors.
我们看到之前如何使用=attr在scope选项中,但是在上面的例子中,我们使用$attr替代。这种&绑定允许一个指令在特定的时间触发在原始的作用域中的表达式求值。任何合法的表达式都允许,包括函数调用的表达式。因此,&绑定是一个理想的用来给指令的行为绑定回调函数的方法。
When the user clicks the x
in the dialog, the directive’s close
function is called, thanks to ng-click.
This call to close
on the isolated scope actually evaluates the expression hideDialog(message)
in the context of the original scope, thus running Controller
‘shideDialog
function.
接下来我们点击窗口中的x,这个指令的close函数被调用。感谢ng-click。这次调用close在隔离的作用的域实际上是表达式hideDialog(meesage),我们运行Controller的hideDialog函数。
Often it’s desirable to pass data from the isolate scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden. This is specified in the directive by calling close({message:
'closing for now'})
. Then the local variable message
will be available within the on-close
expression.
通常从隔离的作用域通过表达式获取父级数据,它可以通过一个本地变量的name和value组成的map放到表达式包装的函数。拿例子来说,hideDialog函数取得一个message在dialog隐藏的时候显示。这个指定在指令中叫close({message:’closing for now’}),然后这个本地变量mesage将在on-close表达式中可用。
最佳实践: use &attr
in the scope
option when you want your directive to expose an API for binding to behaviors. 当你想要你的指令暴露一个绑定行为的API的时候,使用&attr在scope选项中。
创建一个指令添加事件监听器
Previously, we used the link
function to create a directive that manipulated its DOM elements. Building upon that example, let’s make a directive that reacts to events on its elements.
For instance, what if we wanted to create a directive that lets a user drag an element?
代码语言:javascript复制angular.module('dragModule', [])
.directive('myDraggable', ['$document', function($document) {
return 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);
}
};
}]);
代码语言:javascript复制<span my-draggable>Drag ME</span>
创建指令沟通
You can compose any directives by using them within templates.
Sometimes, you want a component that’s built from a combination of directives.
Imagine you want to have a container with tabs in which the contents of the container correspond to which tab is active.
代码语言:javascript复制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'
};
});
代码语言:javascript复制<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>
代码语言:javascript复制<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>
代码语言:javascript复制<div class="tab-pane" ng-show="selected" ng-transclude>
</div>
The myPane
directive has a require
option with value ^myTabs
. When a directive uses this option, $compile
will throw an error unless the specified controller is found. The ^
prefix means that this directive searches for the controller on its parents (without the ^
prefix, the directive would look for the controller on just its own element).
So where does this myTabs
controller come from? Directives can specify controllers using the unsurprisingly named controller
option. As you can see, the myTabs
directive uses this option. Just like ngController
, this option attaches a controller to the template of the directive.
If it is necessary to reference the controller or any functions bound to the controller’s scope in the template, you can use the optioncontrollerAs
to specify the name of the controller as an alias. The directive needs to define a scope for this configuration to be used. This is particularly useful in the case when the directive is used as a component.
Looking back at myPane
‘s definition, notice the last argument in its link
function: tabsCtrl
. When a directive requires a controller, it receives that controller as the fourth argument of its link
function. Taking advantage of this, myPane
can call the addPane
function ofmyTabs
.
If multiple controllers are required, the require
option of the directive can take an array argument. The corresponding parameter being sent to the link
function will also be an array.
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'
};
});
Savvy readers may be wondering what the difference is between link
and controller
. The basic difference is that controller
can expose an API, and link
functions can interact with controllers using require
.
最佳实践: use controller
when you want to expose an API to other directives. Otherwise use link
.
总结
Here we’ve seen the main use cases for directives. Each of these samples acts as a good starting point for creating your own directives.
You might also be interested in an in-depth explanation of the compilation process that’s available in the compiler guide.
The $compile
API page has a comprehensive list of directive options for reference.