AngularJS -- 指令(创建自定义指令)

点击查看AngularJS系列目录
转载请注明出处:http://www.cnblogs.com/leosx/


 

什么是指令

注:本指南是针对已经熟悉AngularJS基础知识的开发人员。如果你才刚刚开始,我建议查看系列教程。(这章是Angular的重点)

指令是一个Dom元素上的标签(和元素上的属性, CSS 类样式一样,属于这个Dom元素),它告诉AngualrJS的HTML 编译器($compile),去附加一个行为到这个Dom元素上去,这个行为可以改变这个Dom元素,或者这个Dom元素的子元素。

AngularJS 有一套自己内置的指令,如:ngBind,ngModel,ngClass等等...你也可以自定义自己的指令。当Angular应用程序起来之后,会先去加载Dom树,然后去匹配你的指令,然后执行。

HTML 编译器 -- 对于Angularjs来说,编译意味着递归去给HTML添加一些事件监听,让DOM元素和Angualr之间可以进行交互,相互作用。这里使用编译这个术语的原因是:添加事件监听这个过程,刚好反应了我们C#,Java等编程语言将源代码编译成应用的这个过程。

 

匹配指令

在我们编写我们的第一个自定义指令之前,我能需要知道AngularJS的HTML编译器是怎么确定在什么时候使用我们自定义的指令的。

下面这个例子中,我们可以看到<Input>元素匹配到了 ngModel 这个指令。

<input ng-model="foo">

下面这个例子也是匹配到了 ngModel 这个指令:

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

AngularJS使用元素标签类型(eg:input)和属性名称来确定哪个元素匹配到了哪个指令。标准的指令(eg: ngModel)是区分大小写的,使用了驼峰命名法则。然而,由于HTML是不区分大小写的,所以后来也不区分大小写了…(感觉等于没说…) 但是通常我们都是使用  [-]  这个破折号来代表一个指令(通常,并不是必须)。

一般的使用方法如下:

1.使用  x-  或者  data-  在元素或者属性前。

2.使用  : 或者  - 或者 _ 来代替了驼峰命名法则。

<div ng-app="docsBindExample">
<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>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
angular.module('docsBindExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)';
}]);
})();
</script>

效果图:

AngularJS -- 指令(创建自定义指令)

$compile编译器会根据元素名称,属性,CSS类名,甚至是注释去匹配指令

下面展示了一个指令可以再哪些地方被引用:

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

建议1:  最好的使用指令方式是通过标签名,属性名,而不是使用注释和CSS样式类名。这样做可以让编译器更加容易的去正确匹配指令。

建议2:  注释指令通常用来解决Dom API的限制,进行跨越多个元素的操作。比如在 <Table> 元素内,去跨越多个元素进行操作。Angular1.2中引入了ng-repeat-startng-repeat-end指令,它们可以更好的去解决这个问题。鼓励开发者使用它们,而不是使用注释指令去解决这样的问题。

 

文本和属性绑定

在编译过程当中,编译器会根据文本的属性值和使用了$interpolate服务的属性去查看这个HTML元素是否包含嵌入式的表达式。如果有,那么这些表达式会被注册到监视器中,并且会进入$digest阶段。下面是一个使用了$interpolate 表达式的示例:

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

 

ngAttr属性绑定

我们的浏览器有时候会挑剔什么是他们认为又要的属性值(也就是属性值不是你随便写的) 想想看下面的例子:

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

我们很希望Angular允许我们这样绑定,但是当我们打开浏览器控制台的时候,你就会看到如下错误:

Error: Invalid value for attribute cx="{{cx}}".

这是因为SVG DOM API的限制原因,你就不能写成: cx=”{{cs}}”了,而应该使用: ng-attr-cx 来解决这个问题。

如果一个属性的绑定的前缀是 ngAttr 而不是正规的 ng-attr-这种形式,那么在binding期间,它会被应用到相应的没有前缀的属性上去(eg: <circle ngAttr-cx=”a”></circle> 它是不会被应用到cx属性上去的)。因为当使用ngAttr前缀的时候,对于$interpolate阶段来说,任何标识都还不起作用。所以在这个阶段中所有的string结果都是 undefined的,既然是undefined的,那么对应的这个属性会被移除,而不再继续添加到元素上去。

我们可以如下去解决上面提到的问题:

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

 

注册指令

我们先来谈谈和注册指令相关的API,和controller一样,指令也是被注册到模块(modules)上的,创建指令的API是: module.directive(指令名称,功能函数)。module.directive(direName,func)  ,这个func方法需要返回一个 配置参数对象,通常是一个Json对象,来告诉$compile编译器,进行这个指令匹配时的行为及动作.

这个func功能函数只会在当编译器第一次匹配到指令的时候执行一次。你可以在这里执行任何的初始化工作。这个func函数是被 $injector.invoke方法调用的。

友情提示:通常,为了避免与未来一些指令冲突,我们自定义的时候,都最好有一个自己的前缀。。。

 

模板扩展指令

我们想想,如果你现在需要一个模板,去展示客户的信息,而且这个模板会重复出现很多次,当你要改变一个地方,你就得去改很多次。这个是一个很好的情景让你去使用指令(directive)去简化你的模板(你该使用模板扩展指令了)。

下面,我们就是用一个例子来说明:

<div ng-app="docsSimpleDirective">
<div ng-controller="Controller">
<div my-customer></div>
</div>
<div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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}}'
};
});
})();
</script>

效果图:

AngularJS -- 指令(创建自定义指令)

当$compile编译器编译,并且连接了<div my-customer></div>之后,它将会继续尝试匹配这个元素的子元素。这就意味着你可以篡改其它指令哦!一会儿我们来看下面的例子。

还有需要注意的,上面的例子当中,我们返回了参数 template,当模板大小改变的时候,这是很烦人的哦!你的字符串会很长,很长! 哈哈!好像邪恶了。 除非你的模板就像上面例子一样,确实特别小,否则,我们还是建议你把模板吓到一个单独的HTML文件当中去,然后再指令的func中,我们返回的参数中,使用templateUrl选项,来指定我们的模板路径。

<div>
<div ng-controller="Controller">
<div my-customer></div>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
templateUrl: 'my-customer.html'
};
});
})();
</script>

my-customer.html的内容:

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

效果图:

AngularJS -- 指令(创建自定义指令)

其实,templateUrl选项也可以是一个函数,返回要加载的HTML模板的URL路径,Angular会自动调用templateUrl函数,并且传入两个参数:ele(对应的元素), 和一个与这个元素相关的attr对象。

注意:在scope没有初始化之前,你是不能够在templateUrl函数中访问scope的,所以说,Controller的初始化一定是要在使用templateUrl函数之前。

<div ng-app="docsTemplateUrlDirective">
<div ng-controller="Controller">
<div my-customer type="name"></div>
<div my-customer type="address"></div>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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';
}
};
});
})();
</script>

customer-name.html

Name: {{customer.name}}

customer-address.html

Address: {{customer.address}}

效果图:

AngularJS -- 指令(创建自定义指令)

 

再次注意:默认情况下,你创建的指令仅限于用于属性或者元素,为了创建一个可以用于类(class)的触发器,你需要使用到restrict选项。

restrict选项的几个经典选项:

‘A’ -  仅仅匹配属性名

‘E’ -   仅仅去匹配元素名

‘C’ -   仅仅匹配class名

友情提示:这些选项可以根据需要进行组合的哦!!

‘AEC’- 它可以匹配属性(attr)或者元素(ele)或者类名(class)。

 

我们来写一个例子,让我们的这个指令只能去匹配元素名,也就是说这个指令只能用于元素上。

<div ng-app="docsRestrictDirective">
<div ng-controller="Controller">
<my-customer></my-customer>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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'
};
});
})();
</script>

my-customer.html内容如下:

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

效果图:

AngularJS -- 指令(创建自定义指令)

更多的关于restrict选项,你可以参考API文档(你知道的,有可能访问不了哦!肿么破,这里不说哈!)

 

更大的问题来了。什么时候我该用属性,什么时候我们该用元素呢?  不出意外,其实你应该能知道,什么时候该用什么。 当我们要创建一个控件的时候,那我们就得使用元素匹配方式。当你需要给你现在的元素增加一些新功能,进行修饰,扩展的时候,我们就得使用属性了。不多解释…

 

指令独立的scope

大家可以想一想,上面说的关于指令的使用的例子,就会发现它有个致命的缺陷,一个Scope(也就是一个Controller)里面你只能使用一次我们自定义的myCustomer指令。这样的话,我们如果想要重复的使用这个指令的时候,你就得声明与之对应个数的Controller,Oh~~ my god  !!!  你肯定瞬间不想使用Angular了,不错不要着急,会有解决方法的。

 

我们可以使用给每个directive(指令)一个独立的scope来进行分离,然后将外部Controller中的scope映射到directive(指令)内部的独立的scope上去。这样,我们就可以调用自己独立的scope啦! 是不是很好哦!要实现上面的独立,AngularJS给我们提供了一个directive的scope参数,看下面示例,你就明白了!

<div ng-app="docsIsolateScopeDirective">
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
<hr>
<my-customer info="igor"></my-customer>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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'
};
});
})();
</script>

my-customer-iso.html内容如下:

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

效果图:

AngularJS -- 指令(创建自定义指令)

我们来解释一下上面,在<my-customer>元素中,我们给它的info属性中,绑定了Controller中的Scope中的naomi属性上去了。 第二个<my-customer>元素的info属性我们是给绑定了igor属性。

我们来仔细看看scope选项:

// ...
scope:{
customerInfo: '=info'
},
// ...

 

scope选项中,包涵了所有关于这个指令独立的scope信息,在上面例子中,它只包涵了一个属性:

customerInfo 是这个指令独立的scope的属性名,它的值为  =info   ,这是在告诉$compile编译器,去把这个属性绑定到一个叫做info的属性上去。。。

注意:上面的绑定是一个很标准的绑定方式,但是如果你想绑定成这样:<div bind-to-this=”thing”>,你需要将customerInfo的值设置为  =bindToThis    (也就是驼峰命名法则)

我们来考虑还有一种情况,如果说你想就让绑定的属性名和你这个指令独立的scope的属性名一样的话,你可以使用下面这个简写语法:

...
scope: {
// same as customer: '=customer'
customer: '='
},
...

 

除了可以将不同的数据绑定到directive 独立的scope上之外,使用独立的scope还有另外一个效果.

不解释,先来段代码:

<div ng-app='docsIsolationExample'>
<div ng-controller="Controller">
<my-customer info="naomi"></my-customer>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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'
};
});
})();
</script>

my-customer-plus-vojta.html 内容如下:

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

效果图如下:

AngularJS -- 指令(创建自定义指令)

 

请注意: {{vojta.name}} 和 {{vojta.address}} 值是空的。也就是他们根本没有被定义。虽然我们在controller中的scope中定义了vojta属性,但是,它在我们自定义的directive(指令)中,是不可用的。。。

顾名思义,在这个directive的独立scope中,除了实体模型你需要明确的增加之外,你可以添加任何的东西到这个独立的scope中,那么这样的话,我们就可以在这里进行重构组件,这样的话,我们就可以防止控件改变了你的模型的状态,当然,除了你明确的指定将这个模型传入进来了,你都传进来了,就可以改变了。

注意:在一般情况下,一个scope是从上级的scope派生来的,但是这里是一个特例哦!  directive(指令)中的scope不是那样的,如果你想要了解更多的关于定义指令的对象- scope 你可以看看这里。

建议: 当我们想要让我们自定义的组件在你的整个Angular应用程序中重用的话,你就要使用独立的scope,但是,如果你只使用一次的话,就可以不用了。不过个人建议,最好还是使用吧! *^_^*

 

 

创建一个指令,去操作DOM

在这个例子当中。我们去创建一个指令,用于显示当前的时间。一秒更新一次,我们就需要更新DOM去显示当前时间。

通常,我们如果想要在指令中去修改DOM元素的话(Angular不建议你直接去操作DOM),会使用 link 选项 ,link 选项的签名如下:

function link(scope, element, attrs){ ...... }
   [ scope ]是一个Angular的scope对象
   [ element ]是这个指令所匹配到的那个元素。
   [ attrs ] 是一个规范的key-value键值对儿的hash对象

在link 方法中,我们想每秒钟更新一下显示时间, 可以使用Angular自带的 $interval 服务,相比之下,它要比  $timeout 服务更加好用,而且更加的易于端对端测试,它会保证在你的测试完成之前,你的$timeout 里面的所有执行都已完成,而且,它也可以保证当你把directive(指令)移除了之后,会自动的把 $interval 服务给卸载掉,从而不会造成内存的泄露。

是骡子是马,咱们还是直接拉出来溜溜!! 直接上代码!

<div ng-ap="docsTimeDirective">
<div ng-controller="Controller">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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
};
}]);
})();
</script>

看看效果图(自己去实践哈,这里不做jif的动态图了哈):

AngularJS -- 指令(创建自定义指令)

 

这里有几个需要注意的地方。 就像module.contoller API一样,module.directive函数也是依赖注入的,因此,我们可以注入 $interval 和 dateFilter(过滤器),然后再link 函数中进行使用了。

我们注册了事件 element.on(‘$destory’, … )    在 $destory  事件中,发生了什么事情呢?

在AngularJS里有几个特别的事件。当一个DOM节点在Angular的编译器中要被销毁,会触发 $destory  事件。同样的,当一个AngularJS的 scope 对象被销毁的时候,也会广播一个 $destory 事件,给那些监听scope的对象。

通过监听这个事件,你可以去删除哪些可能会导致内存溢出的监听器。已注册监听的scope和元素(element)会在它们被销毁的时候自动清理掉,我们是不用去手动销毁的。但是,如果你你注册了一个服务,或者一个监听DOM节点的监听器,而且它们还被删除了,那么,你就得自己手动去清理。否则可能会造成内存泄露。

建议:指令应该由他们自己去清理的。也就是说,你需要调用 element.on( ‘$destory’, … ) 或者 scope.$on( ‘$destory’, …)  去运行一个清理函数(func),来保证当这个指令被清除的时候,会清除掉和它相关的资源。

 

我们来创建一个封装了其它元素的指令:

现在你知道了怎么传递一个model到 directive上的独立scope上去。 但是,有的时候,我们需要传递整个模板,而不只是一个字符串或者一个对象而已,那怎么办呢?  假设我们现在需要创建一个对话框组件。对话框应该可以显示任意对象,该怎么实现呢?  这需要用到另外的一个选项:transclude    直接上代码:

<div ng-app="docsTransclusionDirective">
<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
angular.module('docsTransclusionDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'my-dialog.html'
};
});
})();
</script>

my-dialog.html页面内容:

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

我们来看看效果图:

AngularJS -- 指令(创建自定义指令)

那这个 transclude 选项做了些什么事情呢?   transclude 选项会让指令去访问来自指令之外的 scope 。而不是去访问自己内部的scope 。

还是使用例子来解释下吧! 下列中,请注意,我们添加了一个 link 函数,它重新设置了scope中的name属性的值为 “Jeff”,你猜猜在 {{name}} 绑定中,会显示什么呢?

<div ng-app="docsTransclusionExample">
<div ng-controller="Controller">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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';
}
};
});
})();
</script>

my-dialog.html 中的内容如下:

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

 

一般来说,我们期望 {{name}} 里面显示的是 “Jeff”,但是,but,事实上,它显示的依然是 “Tobias”。选项 transclude 改变了 scope的嵌套方式,它会让{{name}} 指令去访问指令外的scope,而不是去访问指令内部的scope 。

注意:如果你的指令没有创建自己的scope,那么你再在link 中, scope.name= ‘Jeff’的话,就会看到输出的结果是 “Jeff”了,因为这样相当于重新定义了外部scope的name属性值为 “Jeff”。不要绕晕了哦!

这个对于那些需要包涵一些不同内容(内容可变)的指令来说非常有意义哦! 没有这个特性的话,你就得分别创建多个不同的实例了。

建议: 从我们的使用来看就可以知道, transclude选项的使用情景应该是当你的指令内容中,需要包含经常变化或者是不定的地方。

接下来,我们来创建一个有按钮的对话框,它允许我们去绑定一些我们自定义的行为上去,直接上代码:

<div ng-app="docsIsoFnBindExample">
<div ng-controller="Controller">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
Check out the contents, {{name}}!
</my-dialog>
</div>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.hideDialog = function () {
$scope.dialogIsHidden = true;
$timeout(function () {
$scope.dialogIsHidden = false;
}, 2000);
};
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {
'close': '&onClose'
},
templateUrl: 'my-dialog-close.html'
};
});
})();
</script>

my-dialog-close.html 内容如下:

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

再来看看效果图:

AngularJS -- 指令(创建自定义指令)

当我们点击关闭按钮的时候,它会把下面那句话隐藏掉。两秒钟后,再显示出来,自己亲自去试!

正如我们所知道一样,我们可以在指令的scope中定义一些函数(func),然后在scope的上下文中去调用它,但是注意的是,这个函数并需先定义。

我们之前看到了怎么样在scope选项中使用  =attr 。但是上面的例子当中,我们使用了 &attr 实例。这里的这个 & 绑定(别看只是一个符号,它是一个绑定哦!) 允许指令在原始scope的特定时间,去触发调用一个表达式。这个表达式不限制,任意表达式都行,包括那些包涵了方法,函数调用的表达式。正因如此, & 绑定符,是一个很理想的 绑定 指令的回调函数 的方式。

当用户点击对话框中的X按钮的时候,指令的close 函数将被调用,由于ng-click的执行,事实上,它会调用它指令自己独立的scope,而那个scope选项(是选项哦,不要搞晕了),又去调用了上下文中父级scope的 hideDialog() 方法。因此实现了控制器中的hideDialog功能。

建议: 当你想要给你的自定义指令暴露一个绑定行为的API的时候,你就可以使用scope选项 的 &attr 。

 

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

以前,我们使用 link 选项函数去创建一个指令去操作DOM元素。下面,我们来创建一个能够反映使用它的元素的事件的指令。这个示例中,我们会创建一个可以让元素拖动的指令。

先上例子:

<div ng-app="dragModule">
<span my-draggable>Drag ME</span>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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);
}
};
}]);
})();
</script>

效果图:

AngularJS -- 指令(创建自定义指令)

 

创建一个用于通信的指令

还是先上例子吧!

<div ng-app="docsTabsExample">
<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>
</div>
<script src="https://code.angularjs.org/1.3.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
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'
};
});
})();
</script>

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>

效果图:

AngularJS -- 指令(创建自定义指令)

 

在 myPane 指令中,使用到了一个 require选择,其值为:  ^myTabs  。当一个指令使用了这个选项的, $compile 编译器会需要让你提供一个 contorller ,不然的话,它会抛出一个异常的。  使用 ^  前缀意味着这个指令会在它的父级元素(如果没有 ^ 前缀的话,它会在自己的元素里面查找Controller)里面去查找Controller。

那么,这个 myTabs controller又是从哪里来的呢? 指令可以使用Controller选项去指定controller从哪里来! 就像上面例子一下,  myTabs 指令使用了这个选项。 这个选项的值和AngularJS的控制器( ngController )写法一样,这个选项也可以附加一些模板给这个指令。

如果有必要引用一个controller或者任何的函数,然后将他们绑定到模板的controller的scope上去的话,你可以使用 controllerAs 选项来为controller指定一个别名。  你需要给指令定义一个用于使用的scope配置,在指令作为一个组件来使用的时候,这是特别有用的哦!

再来回顾一下myPane 的定义,注意 link函数的最后的一个参数: tabsCtrl。 当一个指令需要一个控制器的时候,在 link 函数中,会传到第四个参数中去。我们可以在第四次参数中得到。 利用这一点,myPane可以调用myTabs中的addPane功能了。

如果你需要多个控制器的话,可以给 require选项传入一个数组。如下:

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'
};
});

有些读者可能很想知道 link 函数和 controller之间的区别。 它们最基本的区别是,controller是一个暴露的API, 而link 函数是让元素和控制器进行交互的。

建议: 当你确定你需要向外,向其他指令暴露一个API的时候,你就得使用controller,其它的情况,还是使用 link比较好!!!

 

简单说明一下,这一章很重要,但是这里呢,我们只是写了一些指令的用例,但是每个用例都是你去创造更多指令的一个很不错的起点哦!!  发挥你的想要把!

 

如果你有兴趣了解一下编译过程的话,你可以点击这里

如果你想要查看一下 $compile API 编译器提供的详细的指令的话,你可以点击 这里

上一篇:带你走近AngularJS 之创建自定义指令


下一篇:angularjs 创建自定义的指令