angular怎么写angular 自定义过滤器之灵

angular 开发者指南 ----创建自定义指令 - 简书
angular 开发者指南 ----创建自定义指令
这个文档解释了何时你可能会想要在你的AngularJS应用中创建自定义指令,以及如何实现它们
指令是什么?
站在高的角度说, 指令是DOM元素上的一些标识符(例如属性,元素名,注释或者是css类),它们告诉AngularJS的html 编译器($compile)来赋予DOM元素一些特定的行为(例如通过事件监听器),甚至是改变DOM元素和它的子节点
Angular拥有许多内置的指令,像ngBind,ngModel,以及ngClass,就像你创建controller和service一样,你能够创建你自己的指令,当Angular初始化你的应用后,html编译器遍历DOM树以寻找指令并初始化它们
"编译"html模板是什么意思?对于angularJS来说,"编译"意味着把指令链接到html上使它变得可交互.我们之所以使用专业术语"编译"的原因是链接指令的这个循环过程是编译型语言中编译源代码过程的真实映射
在我们写指令之前,我们需要知道Angular的html编译器是怎样决定何时来使用那些给定的指令就像当元素匹配选择器时所使用的术语,我们说一个元素匹配一个指令,当这个指令是它声明的一部分时在接下来的例子中,我们说&input&元素匹配ngModel指令
&input ng-model="foo"&
这个&input&元素同样匹配ngModel
&input data-ng-model="foo"&
这个元素匹配person指令
&person&{{name}}&/person&
标准化命名
Angular根据元素的标签和属性名来决定哪个元素匹配哪个指令,我们首先想到的是通过小驼峰式的标准化命名(如ngModel)来区别指令.然而,由于html是忽略大小写的,我们在DOM中通过小写的形式来识别指令,使用有特色的dash-delimited属性来描述(如ng-model)
标准的写法如下所示:
在元素/属性前添加x-或者data-前缀的形式
用:,-,'_'分隔过的命名来替代驼峰写法
&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/&
最佳实践:最好是使用中划线的形式(如ng-bind表示ngBind),如果你想要使用html验证工具,你可以使用添加data-前缀的形式(如data-ng-bind表示ngBind).上面其他的那些声明形式出于历史遗留性原因不会报错,但是我们建议你避免使用它们
$compile能够通过元素名,属性,类名和注释来匹配到指令
angular的所有这些指令都是通过属性,标签名,注释和类名这四种形式来匹配的.下面是具体例子
&my-dir&&/my-dir&
&span my-dir="exp"&&/span&
&!-- directive: my-dir exp --&
&span class="my-dir:"&&/span&
最佳实践: 最好是通过标签名或者属性来声明指令,这样做通常会使元素匹配到相应指令更容易
最佳实践: 注释型指令通常被使用在这些地方: DOM API限制了创建跨越多个元素的指令的能力(例如在&table&元素内).angularJS 1.2版本引入了ng-repeat-statrt和ng-repeat-end指令来作为这个问题更好的解决方案,开发者被鼓励尽可能使用这两个指令来替代自定义注释型指令
首先让我们来看看注册指令的API, 和controller非常相似,指令被注册在modules上.为了注册一个指令,你需要使用module.directiveAPI,module.directive接收两个参数:第一个是标准化的指令命名,第二个是一个工厂函数.这个工厂函数应该返回一个带有不同配置选项的对象,来告诉$compile指令有着怎样的行为以及何时匹配
这个工厂函数仅仅在编译器第一次匹配到指令时被解析一次.你能够在这里做任何初始化工作.这个函数在解析时同样使用依赖注入,使得它像控制器一样变得可注入
我们接下来会看一些普通的例子,然后深度挖掘下不同的配置项以及编译流程
最佳实践: 为了避免和将来的标准发生冲突,最好是给你的自定义指令名加上前缀.如果你创建了一个&carousel&指令,如果html7引入了同样的元素就会发生问题,引入两个字母或三个字母的前缀(如btfcarousel)看起来不错;同样的,不要给你的自定义指令加上ng前缀,因为它可能和angular未来版本引入的指令发生冲突
在接下来的例子中,我们会使用my作为前缀
模板可扩展的指令
让我们假设你有一大块模板来表示用户的信息.这个模板在你的代码中重复了许多次,当你在一处更改它时,你不得不在其他的好几个地方也进行更改.这是一个创建指令来简化模板的好机会.让我们创建一个指令对这些内容用静态模板来做个简单的变换.script.js
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
.directive('myCustomer', function() {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
index.html
&div ng-controller="Controller"& &div my-customer&&/div&&/div&
Paste_Image.png
请注意我们在这个指令中所绑定的东西.在$compile编译,以及link函数把&div my-customer&&/div&链接到document树之后,它会试图匹配当前元素子节点上的指令.这意味着你能够组合不同的指令,我们会在接下来的例子中看看如何去做
最佳实践: 除非你的模板非常小,通常情况下把模板放到html文件中通过templateUrl选项来引入会是更好的选择
如果你很熟悉ngInclude,会发现templateUrl和它很相似.这里是使用templateUrl的例子:script.js
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
.directive('myCustomer', function() {
templateUrl: 'my-customer.html'
index.html
&div ng-controller="Controller"& &div my-customer&&/div&&/div&
my-customer.html
Name: {{customer.name}} Address: {{customer.address}}
Paste_Image.png
templateUrl也可以是一个函数,它返回html模板的url来被指令使用和加载.这个函数有两个参数:调用指令的元素本身element,以及这个元素上所关联的属性对象attr.
你现在还没有能力在templateUrl函数上获取到scope变量,这是因为在scope被初始化之前这个模板就已经被引用了.
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
.directive('myCustomer', function() {
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}}
Paste_Image.png
注意:当你创建指令时,默认情况下只能识别通过属性或元素声明的指令,为了能够通过譬如类名的方式来创建指令,你需要使用restrict选项.
restrict选项通常是下面这四种:
A -仅仅匹配属性名
E -仅仅匹配元素名
C -仅仅匹配类名
M -仅仅匹配注释
这些选择项是能够结合使用的:
AEC -会匹配属性,元素或者是类名
让我们使用restrict: 'E'来更改我们的指令.script.js
angular.module('docsRestrictDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
.directive('myCustomer', function() {
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}}
Paste_Image.png
我们何时应该使用属性 vs 元素?当你创建一个组件,它完全控制模板时,你应该使用元素element,一个常见的案例是:当你为你的模板创建一个特定领域的语言;当你为你已经存在的元素添加新功能时,你应该使用属性作为声明方式
在这里使用元素作为指令的声明方式myCustomer无疑是正确的选择.因为你不是在给一个元素添加某些自定义的行为;你是在定义元素作为一个自定义组件的核心行为.
指令的隔离作用域
我们上面的指令myCustomer看起来很不错,但是它有一个致命的瑕疵.我们仅仅只能在一个给定的作用域中使用它一次在当前的实现中,我们需要每次创建一个不同的控制器来复用这个指令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() {
restrict: 'E',
templateUrl: 'my-customer.html'
index.html
&div ng-controller="NaomiController"&
&my-customer&&/my-customer&
&div ng-controller="IgorController"&
&my-customer&&/my-customer&
my-cutomser.html
Name: {{customer.name}} Address: {{customer.address}}
Paste_Image.png
这显然不是一个好的解决方案我们想要有能力去把指令的作用域和外部作用域隔离开来,并且可以把外部作用域传递进来.我们能够通过创建"隔离作用域"来实现它.为了这个目的,我们使用指令的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() {
restrict: 'E',
customerInfo: '=info'
templateUrl: 'my-customer-iso.html'
index.html
&div ng-controller="Controller"&
&my-customer info="naomi"&&/my-customer&
&my-customer info="igor"&&/my-customer&
my-customer-iso.html
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
Paste_Image.png
让我们看index.html,第一个&my-customer&元素把info属性绑定到naomi上,我们在控制器的作用域中暴露出这个naomi对象;第二个元素把info绑定到igor让我们近距离看看作用域选项:
customerInfo: '=info'
scope选项是一个对象:它包含一组绑定的属性用于隔离作用域间的绑定.在这个例子中仅仅只有一个属性
它的名字(customerInfo)与指令的隔离作用域中属性customerInfo一致
它的值(=info)告诉$compile去绑定info属性
注意: 这个在指令scope选项中的=attr属性的命名规则和指令的命名方式一样,为了绑定属性&div bind-to-this="thing"&,你需要这样声明=bindToThis.
如果你想要绑定的属性名和指令内部的变量名一样的话,你可以采取下面缩写的形式:
// same as '=customer'
customer: '='
除了能把不同的数据绑定到指令内部的作用域中,使用隔离作用域还有其他的作用.看接下来这个例子:添加另一个属性vojta到我们的控制器作用域中,并且试图在指令内部的模板中获取它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() {
restrict: 'E',
customerInfo: '=info'
templateUrl: 'my-customer-plus-vojta.html'
index.html
&div ng-controller="Controller"&
&my-customer info="naomi"&&/my-customer&
my-customer-plus-vojta.thml
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
Name: {{vojta.name}} Address: {{vojta.address}}
Paste_Image.png
请注意{{vojta.name}}和{{vojta.address}}为空,意味着他们未定义,尽管我们在控制器中定义了vojta,在指令内部它是不可获取的就像名字所暗示的那样,指令的隔离作用域会隔离除了你显式地在scope: {}中添加的哈希对象之外的一切内容.这在你创建可复用组件时是非常有用的,因为它阻止了除了你显式引入以外的模型来更改你的组件状态
最佳实践: 使用scope选项来创建隔离作用域,当你想要在你的app中创建可复用组件时
创建一个操作DOM的指令
在这个例子中我们会创建一个展示当前时间的指令.每过一秒,它会更新DOM来展示当前时间
指令如果想要操作DOM,一般是使用link选项来注册DOM监听器同时更新DOM,它在模板被复制以后执行,并且指令的具体逻辑代码会放在这里.link接收一个函数如下所示function link(scope, element, attrs, controller, transcludeFn):
scope是Angular 的scope对象
element是指令匹配到的用jqLite包裹了以后的元素
attrs是一个标准化属性名的key-value对象.通过它可以访问到指令上的全部属性
controller表示指令自己的控制器或指令引入的控制器实例(如果有的话).确切值依赖于指令的require选项
在我们的link函数中,我们想要每过一秒就更新一次时间,或是无论何时用户改变指令所绑定的时间格式字符串.我们会使用$interval服务 ,这会比使用$timeout更容易,并且也能更好的进行端到端测试(因为在我们完成测试前我们需要确保所有的$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) {
updateTime();
element.on('$destroy', function() {
$interval.cancel(timeoutId);
// start the UI save the timeoutId for canceling
timeoutId = $interval(function() {
updateTime(); // update DOM
link: link
index.html
&div ng-controller="Controller"&
Date format: &input ng-model="format"& &hr/&
Current time is: &span my-current-time="format"&&/span&
Paste_Image.png
这里有不少事情需要注意下.就像module.controllerAPI,这里的函数参数同样是被依赖注入的,因为这个,我们能够在link函数中使用$interval和dataFilter
我们注册了一个event事件element.on('$destroy', ....),什么时候会触发这个$destroy事件呢?
在AngularJS的事件冒泡机制中有一些特别的时刻,当被Angular的编译器编译过的DOM节点被销毁时,它会冒泡一个$destroy事件;相似的,当一个AngularJS的作用域被销毁时,它会向那些监听的作用域广播一个$destroy事件
通过监听这个事件,你能够移除可能会导致内存泄漏的事件监听器.scope上注册的监听器和元素当它们被销毁时会被自动清除.但如果你是在service,或者是还未被删除的DOM节点上注册了一个监听器,你不得不自己手动销毁它们,否则会有内存泄漏的风险
最佳实践: 你应该在指令的自动销毁机制之外手动地进行销毁.你能够使用element.on('$destroy', ...)或者是scope.$on('$destroy', ...)来当指令被移除时执行清除函数
创建一个包裹了其他元素的指令
我们已经见到:你能过通过使用隔离作用域来给指令传递一些数据;但有时比起字符串或者对象,我们更想要能够传递整个模板;让我们创建一个对话框组件,这个组件应该能包裹任意内容为了这个目的,我们使用transclude选项.script.js
angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
.directive('myDialog', function() {
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&
Paste_Image.png
transclude选项实际上做了些啥?transclude使得拥有这个选项的指令的内容有能力去获取指令外部的作用域
为了说明这一点,让我们看接下来的例子.注意我们在script.js的link函数内重新定义了name为Jeff.你觉得下面{{name}}最终会显示啥?script.js
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
.directive('myDialog', function() {
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&
my-dialog.html
&div class="alert" ng-transclude&&/div&
Paste_Image.png
通常来说,我们可能会以为{{name}}的值会是Jeff,然而在这个例子中我们看到{{name}}的值仍然是Tobias
transclude选项改变了作用域嵌套的方式.实际上指令调用时标签中所嵌套的内容是和外部作用域绑定(而不是内部),这使得我们能够让内容获取到外部作用域
请注意:如果我们在指令中没有创建它自己的作用域(这里即scope: false),我们在指令内声明scope.name="Jeff",会更改外部作用域中name的值,即最后输出会是Jeff
这种行为对于那些包裹了某些内容的指令是很有意义的,否则的话你不得不一个个手动引入你想要使用的值;如果你真的这样做了,那是不是就意味着你其实无法真正的获取到任意内容了呢?
最佳实践: 仅仅在你想要创建一个可以包裹任意元素的指令时,使用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 =
$scope.dialogIsHidden =
$timeout(function() {
$scope.message = '';
$scope.dialogIsHidden =
.directive('myDialog', function() {
restrict: 'E',
transclude: true,
'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&
my-dialog-close.html
&div class="alert"&
&a href class="close" ng-click="close({message: 'closing for now'})"&×&/a&
&div ng-transclude&&/div&
我们想要运行通过指令scope选项引用进来的函数,但是想让它运行在它注册时的上下文环境中.
我们之前见过了如何在scope选项中使用=attr绑定,在上面的例子中我们使用了&attr绑定.&绑定允许指令执行表达式在它原先的上下文作用域中的值,在某个特定的时机;任何合法的表达式都是允许的,包括包含了函数调用的表达式.因为这个原因,&绑定非常适合指令行为需要绑定函数回调的情景.
当用户点击了对话框中的x,指令的close函数被调用,让我们感谢下ng-click.这个在隔离作用域中被称为call的函数实际上计算了表达式hideDialog(message)在它原先的作用域上下文中的值,即运行Controller的hideDialog函数
通常情况下,能够把隔离作用域中的数据通过表达式来传递给父级作用域这一特性是相当诱人的.我们能够通过在指令内部给引入的函数传入一组key:value来实现这一特性.例如,hideDialog函数获取到当对话框隐藏时需要展示的信息.这个信息是通过我们在指令内部调用close({message: 'closing for now'})来声明,然后本地变量message会被on-close表达式获取到.
最佳实践: 当你想要你的指令暴露出绑定行为的API时,使用scope选项的&attr绑定.
创建一个添加了事件监听器的指令
以前我们使用link函数来生成一个可操作DOM元素的指令,在这个例子的基础上,让我们创建一个指令来实时的反映元素上的事件不如让我们来写一个允许用户拖动元素的指令吧?script.js
angular.module('dragModule', [])
.directive('myDraggable', ['$document', function($document) {
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 -
startY = event.pageY -
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
function mousemove(event) {
y = event.pageY - startY;
x = event.pageX - startX;
element.css({
top: y + 'px',
function mouseup() {
$document.off('mousemove', mousemove);
$document.off('mouseup', mouseup);
index.html
&span my-draggable&Drag Me&/span&
创建一个可通信的指令
你能够在模板中使用任意指令来组合使用它们有时,你会需要一个由不同指令的结合构建而成的组件想象一下你需要一个容器,容器内激活的tab和对应的展示内容一致script.js
angular.module('docsTabsExample', [])
.directive('myTabs', function() {
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 =
pane.selected =
this.addPane = function(pane) {
if (panes.length === 0) {
$scope.select(pane);
panes.push(pane);
templateUrl: 'my-tabs.html'
.directive('myPane', function() {
require: '^^myTabs',
restrict: 'E',
transclude: true,
title: '@'
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
templateUrl: 'my-pane.html'
index.html
&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&
&div class="tab-content" ng-transclude&&/div&
my-pane.html
&div class="tab-pane" ng-show="selected"&
&h4&{{title}}&/h4&
&div ng-transclude&&/div&
Paste_Image.png
指令myPane有一个require选项,值时^^myTabs,当指令使用这个选项时,$ compile会抛出错误,除非找到了声明的控制器.^^前缀表示指令会在它的父元素上寻找指定的控制器.(一个^前缀表示会在元素自身或父元素上寻找指定控制器;没有前缀的话表明指令只会在元素自身寻找)
因此这个myTabs控制器是从哪儿来的呢?指令能够使用controller选项来声明控制器,这毫不意外.就像你看到的这样,myTabs指令使用了这个选项.就像ngController一样.这个选项给指令的模板绑定了一个控制器.
如果你需要在这个模板之外调用控制器或者是任何绑定到这个控制器上的函数,你能够使用选项controllerAs来给这个控制器定义一个别名.这时指令需要定义一个作用域使得这个配置生效.这在指令被作为一个组件来使用时是特别有效的
让我们回头看看myPane的定义,注意link函数的最后一个参数: tabsCtrl,当一个指令引入了一个控制器时,link函数接受这个控制器作为第四个参数.得益于它,myPane能够调用myTabs的addPane函数
如果需要引入多个控制器,指令的require选项能够接收数组参数.相应的,link函数同样接收数组作为参数.
angular.module('docsTabsExample', [])
.directive('myPane', function() {
require: ['^^myTabs', 'ngModel'],
restrict: 'E',
transclude: true,
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来引入控制器
最佳实践: 当你想要向其他指令暴露出API时使用controller,其他情况下使用link
这里我们已经见识了指令的常见使用场景.这里的每一个例子对于你创建自定义指令都是一个好的开始.你也许会对指令编译过程深层次的解释很感兴趣.这些东西会在下一篇编译指南中讲到.
首先是起因,使用angular进行项目开发大半年了,这两天要带新入职的妹子,被问到一些问题,发现有些东西自己都记得不是很清楚了,以前也没做过系统性的总结,于是打算重新看一遍官方文档,于是就有了这篇官网上开发指南的翻译
指令在angular中无疑非常重要,于是第一篇先从自定义指令开始吧,后续应该会再挑几篇进行翻译.
自己水平,文笔有限,翻译难免有错误疏漏之处,不吝指正,
文中所有实例代码有功夫的话后续会上传到github

我要回帖

更多关于 angularjs 自定义指令 的文章

 

随机推荐