The examples in this article describe the usage of AngularJS instructions. Sharing it with you for your reference, the details are as follows:
Directives are the most important components of any AngularJS application. Although AngularJS comes with many directives, you will often find that you need to create some special directives yourself. This article will take you through custom directives and explain how to use them in real-world Angular projects. At the end of the article, we will use Angular instructions to create a simple note-taking application.
Overview
A directive is something that introduces new syntax. Directives are marks made on DOM elements and attached with some specific behaviors. For example, static HTML doesn't know how to create and display a date picker widget. To teach this new syntax to HTML we need a directive. This directive will create an element that acts as a date picker. We will see how to implement this directive later.
If you have written Angular applications before, you have already used directives, whether you realized it or not. You may have used directives like ng-model, ng-repeat, ng-show, etc. All of these directives bind specific functionality to DOM elements. For example, ng-repeat will repeat specific elements, while ng-show will display elements conditionally. If you want to create a draggable element you may need to create a directive. The basic idea behind the directive is simple. It makes HTML interactive by attaching event listeners to elements and transforming the DOM.
Looking at directives from a jQuery perspective
Think about how you would use jQuery to create a date picker. We first add a normal input field in the HTML and then in jQuery we call $(element).dataPicker() to convert it into a date picker. But, think about it. When a designer wants to examine this markup, can he/she immediately guess what this field is for? Is it just a normal input field or a date picker? You'll have to look at jQuery to confirm this. Angular's approach is to use directives to extend HTML. Therefore, a date picker directive might look like this:
<date-picker></date-picker>
or like this:
<input type='text' data-picker/>
This method of creating UI components is both intuitive and clear. You can see an element and know its purpose.
Create custom directives
An Angular directive may appear in four forms:
1. A new HTML element (<date-picker></date-picker>)
2. On an element Attribute ()
3. As a class ()
4. As annotation ()
Of course, we can completely decide how our directive appears in HTML. Now, let's take a look at how a typical Angular directive is written. It is similar to the controller registration method, but it will return a simple object (directive definition), which contains some properties of the configuration directive. The code below shows a simple Hello World directive:
var app = angular.module('myapp',[]); app.directive('helloWorld',function(){ return { restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' } });
In the code above, the app.diretive() function registers a new directive in our module. The first parameter of this function is the name of the directive. The second parameter is a function that returns the directive definition object. If your directive has dependencies on additional objects/services such as $rootScope, $http or $compile, they can be injected there as well. This directive can be used as an HTML element, as shown below:
<hello-world/>
or:
<hello:world/>
or as an attribute:
<div hello-world></div>
or:
<div hello:world/>
If you want to be compatible with HTML5, you can add x- or data- prefix in front of the attribute. Therefore, the following tags will match the helloWorld directive:
<div data‐hello‐world></div>
or
<di vx‐hello‐world></div>
Note
When matching directives, Angular will remove the x- or data- prefix from element/attribute names. Then convert the delimiter - or : to camelCase notation that matches the registered directive. This is why our helloWorld directive is actually written hello-world when used in HTML.
Although the simple command above just displays some static text, there are still some interesting points worth exploring. We have used three properties in this directive definition object. Let’s take a look at what these three attributes are used for:
restrict - This attribute specifies how a directive should be used in HTML (remember that directives can appear in four ways). In this example we set it to 'AE'. Therefore, this directive can be used as an HTML element or an attribute. To allow the directive to be used as a class we can set restrict to 'AEC'.
template - 这个实行指明了当指令被Angular编译和链接时生成的HTML标记。它不一定是一个简单的字符串。template可以很复杂,其中经常会涉及其它的指令,表达式({{}}),等等。在大多数情况下你可能会想要使用templateUrl而不是template。因此,理想情况下你应该首先将模板放置在一个单独的HTML文件中然后让templateUrl指向它。
replace - 这个属性指明了是否生成的模板会代替绑定指令的元素。在前面的例子中我们在HTML中使用指令为
link函数和作用域
有一个指令生成的模板是没有用的除非它在正确的作用域中北编译。默认情况下一个指令并不会得到一个新的子作用域。然而,它可以得到父作用域。这意味着如果一个指令位于在一个控制器中那么它将使用控制器的作用域。
为了利用作用域,我们可以使用一个叫做link的函数。它可以通过指令定义对象中的link属性来配置。我们现在对helloworld指令做一些修改一遍当用户在一个input字段中输入一个颜色名称时,Hello Wolld文字的背景颜色会自动发生改变。同样,当一个用户点击Hello World文字时,背景颜色会重置为白色。相应的HTML标记如下所示:
<body ng-controller='MainCtrl'> <input type='text' ng-model='color' placeholder='Enter a color' / > <hello-wolrd/> </body>
修改后的helloWorld指令代码如下所示:
app.directive('helloWorld',function(){ return { restrict: 'AE', replace: true, template: '<p style="background-color:{{color}}"></p>', link: function(scope,elem,attr){ elem.bind('click',function(){ elem.css('background-color','white'); scope.$apply(function(){ scope.color = "white"; }); }); elem.bind('mouseover',function(){ elem.css('cursor','pointer'); }); } } });
注意到link函数被用在了指令中。它接收三个参数:
scope - 它代表指令被使用的作用域。在上面的例子中它等同于符控制器的作用域。
elem - 它代表绑定指令的元素的jQlite(jQuery的一个自己)包裹元素。如果你在AngularJS被包含之前就包括了jQuery,那么它将变成jQuery包裹元素。由于该元素已经被jQuery/jQlite包裹,我们没有必要将它包含在$()中来进行DOM操作。
attars - 它代表绑定指令的元素上的属性。例如,如果你在HTML元素上有一些指令形式为:
link函数主要是用来对DOM元素绑定事件监听器,监视模型属性变化,并更新DOM。在前面的指令代码中,我们绑定了两个监听器,click和mouseover。click处理函数重置了
的背景颜色,而mouseover处理函数则将游标改变为pointer。模板中拥有表达式{{color}},它将随着父作用域中的模型color的变化而变化,从而改变了Hello World的背景色。
Compile函数
Compile函数主要用来在link函数运行之前进行一些DOM转化。它接收下面几个参数:
tElement - 指令绑定的元素
attrs - 元素上声明的属性
这里要注意compile不能够访问scope,而且必须返回一个link函数。但是,如果没有compile函数以依然可以配置link函数。compile函数可以被写成下面的样子:
app.directive('test',function(){ return { compile: function(tElem,attrs){ //在这里原则性的做一些DOM转换 return function(scope,elem,attrs){ //这里编写link函数 } } } });
大多数时候,你仅仅只需要编写link函数。这是因为大部分指令都只关心与注册事件监听器,监视器,更新DOM等等,它们在link函数中即可完成。像是ng-repeat这样的指令,需要多次克隆并重复DOM元素,就需要在link函数运行之前使用compile函数。你可能会问威慑呢么要将两个函数分别使用。为什么我们不能只编写一个函数?为了回答这个问题我们需要理解Angular是如何编译指令的!
指令是如何被编译的
当应用在启动时,Angular开始使用$compile服务解析DOM。这项服务会在标记中寻找指令然后将它们各自匹配到注册的适龄。一旦所有的指令都已经被识别完成,Angular就开始执行它们的compile函数。正如前面所提到的,compile函数返回一个link函数,该函数会被添加到稍后执行的link函数队列中。这叫做编译阶段(compile phase)。注意到即使同一个指令有几个实例存在,compile函数也只会运行一次。
在编译阶段之后就到了链接阶段(link phase),这时link函数就一个接一个的执行。在这个阶段中模板被生成,指令被运用到正确的作用域,DOM元素上开始有了事件监听器。不像是compile函数,lin函数会对每个指令的实例都执行一次。
改变指令的作用域
默认情况下指令应该访问父作用域。但是我们并不像对所有情况一概而论。如果我们对指令暴露了父控制器的scope,那么指令就可以自由的修改scope属性。在一些情况下你的指令可能想要添加一些只有内部可以使用的属性和函数。如果我们都在父作用域中完成,可能会污染了父作用域。因此,我们有两种选择:
一个子作用域 - 这个作用域会原型继承父作用域。
一个隔离的作用域 - 一个全新的、不继承、独立存在的作用域。
作用域可以由指令定义对象中的scope属性定义。下面的例子展示了这一点:
app.directive('helloWorld',function(){ return { scope: true, //使用一个继承父作用域的自作用域 restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' } });
上面的代码要求Angular为指令提供一个能够原型继承父作用域的子组用于。另一种情形,一个隔离作用域,代码如下所示:
app.directive('helloWorld',function(){ return { scope: {}, //使用一个全新的隔离作用域 restrict: 'AE', replace: true, template: '<h3>Hello World!</h3>' } });
上面的指令使用一个不继承父作用域的全新隔离作用域。当你想要创建一个可重用的组件时隔离作用域是一个很好的选择。通过隔离作用域我们确保指令是自包含的兵可以轻松地插入到任何HTML app中。这种做法防止了父作用域被污染,由于它不可访问父作用域。在我们修改后的helloWorld指令中如果你将scope设置为{},那么代码就不会再正常运行。它将创建一个隔离的作用域然后表达式{{color}}将无法引用隔离作用域中的属性因此值变为undefined。
隔离作用域并不意味着你一点都不能获取到父作用域中的属性。有一些技巧可以使你访问父作用域中的属性同时监听这些属性的变化。我们将在后续文章中提到这种高级技巧。