创建自定义的指令
这个文章将解释什么需要在自己的angularjs应用中创建自己的指令,以及如何实现它。
什么是指令
在高的层面上讲,指令是DOM元素中的标记(例如一个属性,一个节点名,注释或者CSS类),它告诉angularjs编译器去给这个元素附加一个指令的行为或者转换DOM元素和它的子元素。
Angularjs拥有一些内建的指令,像ngBind、ngModel和ngClass。非常类似于你创建自己的controller和service,你可以创建你自己的指令个angularjs使用,党angular初始化启动你的应用程序,html编译器将遍历你的DOM元素并且去匹配指令。
匹配指令
在你可以编写指令之前,你需要知道当你使用一个给定的指令的时候,angularjs的html编译器是怎样工作的.
在下面的例子里,我们说<input>元素匹配了ngModel指令:
代码语言:javascript复制<input ng-model="foo">
下面的例子也同样匹配ngModel指令:
代码语言:javascript复制<input data-ng:model="foo">
Angular规范是一个元素标签和属性名去决定哪个元素匹配哪个指令,我们通常使用大小写敏感的驼峰式规范化命名应用指令。可是因为HTML是大小写不敏感的,所以我们在DOM中使用小写的方式去引用指令,通常在DOM元素上使用短划线分隔的属性。
规范化的形式如下所示:
1:去除元素或者属性以x-和data-的开头。
2:使用:/_/-分隔驼峰式的命名。
例如,以下的格式都是等价的并且匹配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>
最佳实践:推荐使用短划线间隔的格式(比如ngBind指令使用ng-bind),假如你想使用一个HTML验证工具,你可以换为使用data-前缀的样式(比如ngBind指令使用data-ng-bind)。其他的样式,在一些遗留的因素上也是可接受的,但是我们建议你避免使用它们
所有Angular提供的指令匹配属性名、标签名、注释或者类名,下面演示了一个指令可以被引用的几种方式:
代码语言:javascript复制<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
最佳实践:对比注释和类型,我们更多的倾向于使用标签名和属性,这样可以更容易地确定哪个指令是元素需要去匹配的。
最佳实践:注释形的指令通常用于DOM API不可以跨越多个元素去创建指令的限制(比如在table元素内部),AngularJS 1.2加入了ng-repeat-start和ng-repeat-end作为这个问题的一个更好的解决方案,开发人员被鼓励使用这种方式来替换注释形的指令。
文本和属性绑定
在编译工作阶段,编译器使用$interpolate服务匹配文本和属性,它将发现他们是否包含嵌入的表达式。这些表达式将被注册为watchs,并且作为一个通常的生命周期的一部分被更新,下面是一个interpolation的示例:
代码语言:javascript复制<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
ngAttr属性绑定
浏览器对属性是否合法经常是挑刺的,例如,考虑一下这个例子:
代码语言:javascript复制<svg>
<circle cx="{{cx}}"></circle>
</svg>
我们期望Angular可以去绑定它,但是当我们查看控制台,我们将会看到一些这样的信息
代码语言:javascript复制Error: Invalid value for attribute cx="{{cx}}"
因为SVG DOM API是严谨话的,你不可以简单的写cx="{{cx}}"这样的属性。但是你可以使用ng-attr-cx来绕过这个问题,加入有一个拥有绑定的属性是拥有ngAttr前缀的,在绑定阶段,它将会被应用到相应的无前缀的属性上,这样就允许你去绑定你想要的属性,否则你只能看着它被浏览器处理掉,当使用ngAttr的时候,$interpolate的allOrNothing标识被使用,所以假如任何表达式返回的是undefined,这个属性将会被移除而不是添加到元素上。例如,我们可以像这样修复上文中的问题
代码语言:javascript复制<svg>
<circle ng-attr-cx="{{cx}}"></circle>
</svg>
假如想要去使用一个驼峰式属性名,像viewBox,可以使用下划线去指示属性去绑定到一个实际的驼峰属性上。例如下文中绑定一个viewBox,我们可以这样写:
代码语言:javascript复制<svg ng-attr-view_box="{{viewBox}}">
</svg>
创建指令
首先让我们谈谈注册指令的API,与controller相似,指令是注册在module上的,想要注册它,你可以使用module.directive API,module.directive需要一个规范化的指令名跟随着一个工厂方法,这个工厂方法需要返回一个包含不同选项来告诉$compile指令在被匹配的时候应该怎样去表现的对象。
这个工厂方法只会被在编译器匹配到指令的第一次的时候调用一次,你可以在这个时机执行任何的初始化工作,这个方法需要被$compile.invoke调用使得它可以像controller一样是可注入的。
最佳实践:推荐使用定义对象而不是返回一个方法。
我们将会使用一些指令的通常示例,然后进行深入的探讨不同的选项和编译过程。
最佳实践:为了避免与将来的标准冲突,最好为你自己的指令加一个前缀,比如,加入你想创建一个carousel指令,如果HTML7包含了一个这样的元素,这就会有问题了,两个或者三个字母的前缀就会使它工作的很好,同样的,不要给自己的指令加上ng或者可能会和未来版本的angular引起冲突的前缀。
未完待续…