对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能。
首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法:
angular.module(' myApp ' , []) .directive( ' myDirective ' , function() { return { restrict: String, priority: Number, terminal: Boolean, template: String or Template Function: function(tElement, tAttrs) {...}, templateUrl: String, replace: Boolean or String, scope: Boolean or Object, transclude: Boolean, controller: String or function(scope, element, attrs, transclude, otherInjectables) { ... }, controllerAs: String, require: String, link: function(scope, iElement, iAttrs) { ... }, compile: // 返回一个对象或连接函数,如下所示: function(tElement, tAttrs, transclude) { return { pre: function(scope, iElement, iAttrs, controller) { ... }, post: function(scope, iElement, iAttrs, controller) { ... } } return function postLink(...) { ... } } }; });
restrict是一个可选的参数。用于指定该指令在DOM中以何种形式被声明。默认值是A,即以属性的形式来进行声明。
可选值如下:
E(元素)
< my-directive ></ my-directive >
A(属性,默认值)
< div my-directive ="expression" ></ div >
C(类名)
< div class ="my-directive:expression;" ></ div >
M(注释)
< --directive:my-directive expression-- >
一般考虑到浏览器的兼容性,强烈建议使用默认的属性就可以即即以属性的形式来进行声明。最后一种方式建议再不要求逼格指数的时候千万不要用。
Code:
angular.module('app',[])
.directive('myDirective', function
() {
return
{
restrict: 'E',
template: '<a href="http://www.baidu.com">百度</a>'
};
})
HtmlCode:
<my-directive></my-directive>
效果:
大多数指令会忽略这个参数,使用默认值0,但也有些场景设置高优先级是非常重要甚至是必须的。例如,ngRepeat将这个参数设置为1000,这样就可以保证在同一元素上,它总是在其他指令之前被调用。
这个参数用来
停止运行当前元素上比本指令优先级低的指令
。但同当前指令优先级相同的指令还是会被执行。
例如:ngIf的优先级略高于ngView(它们操控的实际就是
terminal参数
),如果ngIf的表达式值为true,ngView就可以被正常执行,但如果ngIf表达式的值为false,由于ngView的优先级较低就不会被执行。
template参数是可选的,必须被设置为以下两种形式之一:
首先演示下第二种用法:
angular.module('app',[])
.directive('myDirective', function
() {
return
{
restrict: 'EAC',
template:
function
(elem, attr) {
return "<a href='" + attr.value + "'>" + attr.text + "</a>";
}
};
})
HtmlCode:(效果同上,不做演示了)
< my-directive value ="http://www.baidu.com" text ="百度" ></ my-directive > < div my-directive value ="http://www.baidu.com" text ="百度" ></ div >
templateUrl是可选的参数,可以是以下类型:
无论哪种方式,模板的URL都将通过ng内置的安全层,特别是$getTrustedResourceUrl,这样可以保护模板不会被不信任的源加载。 默认情况下,调用指令时会在后台通过Ajax来请求HTML模板文件。加载大量的模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部署应用之前对HTML模板进行缓存。
Code:
angular.module('app',[])
.directive('myDirective', function
() {
return
{
restrict: 'AEC',
templateUrl:
function
(elem, attr) {
return attr.value + ".html"; //
当然这里我们可以直接指定路径,同时在模板中可以包含表达式
}
};
})
replace是一个可选参数,如果设置了这个参数,值必须为true,因为默认值为false。默认值意味着模板会被当作子元素插入到调用此指令的元素内部,
例如上面的示例默认值情况下,生成的html代码如下:
< my-directive value ="http://www.baidu.com" text ="百度" >< a href ="http://www.baidu.com" >百度</ a ></ my-directive >
如果设置replace=true
< a href ="http://www.baidu.com" value ="http://www.baidu.com" text ="百度" >百度</ a >
据我观察,这种效果只有设置restrict="E"的情况下,才会表现出实际效果。
介绍完基本的指令参数后,就要涉及到更重要的作用域参数了...
scope参数是可选的,可以被设置为true或一个对象。默认值是false。
如果 一个元素上有多个指令使用了隔离作用域,其中只有一个可以生效。只有指令模板中的根元素可以获得一个新的作用域 。因此,对于这些对象来说scope默认被设置为true。内置指令ng-controller的作用,就是从父级作用域继承并创建一个新的子作用域。它会创建一个新的从父作用域继承而来的子作用域。这里的继承就不在赘述,和面向对象中的继承基本是一直的。
首先我们来分析一段代码:
< div ng-app ="app" ng-init ="name= '祖父'" > < div ng-init ="name='父亲'" > 第一代:{{ name }} < div ng-init ="name= '儿子'" ng-controller ="SomeController" > 第二代: {{ name }} < div ng-init ="name='孙子'" > 第三代: {{ name }} </ div > </ div > </ div > </ div >
我们发现第一代,我们初始化name为父亲,但是第二代和第三代其实是一个作用域,那么他们的name其实是一个对象,因此出现的效果如下:
第一代:父亲
第二代: 孙子
第三代: 孙子
我们在修改一下代码,把第三代隔离开来再看看效果:
< div ng-app ="app" ng-init ="name= '祖父'" > < div ng-init ="name='父亲'" > 第一代:{{ name }} < div ng-init ="name= '儿子'" ng-controller ="SomeController" > 第二代: {{ name }} < div ng-init ="name='孙子'" ng-controller ="SecondController" > 第三代: {{ name }} </ div > </ div > </ div > </ div >
JsCode:
angular.module('app', [])
.controller('SomeController',function
($scope) {
})
.controller('SecondController', function
($scope) {
})
效果如下:
第一代:父亲
第二代: 儿子
第三代: 孙子
在修改下代码来看看继承:
< div ng-app ="app" ng-init ="name= '祖父的吻'" > < div > 第一代:{{ name }} < div ng-controller ="SomeController" > 第二代: {{ name }} < div ng-controller ="SecondController" > 第三代: {{ name }} </ div > </ div > </ div > </ div >
效果如下:
第一代:祖父的吻
第二代: 祖父的吻
第三代: 祖父的吻
如果要创建一个能够从外部原型继承作用域的指令,将scope属性设置为true,简单来说就是可继承的隔离,即不能反向影响父作用域。
再来看个例子:
angular.module('myApp', [])
.controller('MainController', function
($scope) {
})
.directive('myDirective', function
() {
return
{
restrict: 'A',
scope:
false
,//切换为{},true测试
priority: 100,
template: '<div>内部:{{ myProperty }}<input ng-model="myProperty"/></div>'
};
});
Html代码:
<div ng-controller='MainController' ng-init="myProperty='Hello World!'">
外部: {{ myProperty}}
<input ng-model="myProperty" />
<div my-directive></div>
</div>
当我们改变scope的值我们会发现
false:
继承但不隔离
true:
继承并隔离
{}:
隔离且不继承
transclude是一个可选的参数。默认值是false。嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏。我们可以将整个模板,包括其中的指令通过嵌入全部传入一个指令中。 指令的内部可以访问外部指令的作用域,并且模板也可以访问外部的作用域对象 。为了将作用域传递进去,scope参数的值必须通过{}或true设置成隔离作用域。如果没有设置scope参数,那么指令内部的作用域将被设置为传入模板的作用域。
只有当你希望创建一个可以包含任意内容的指令时,才使用transclude: true。
我们来看两个例子-导航栏:
< div side-box title ="TagCloud" > < div class ="tagcloud" > < a href ="" >Graphics</ a > < a href ="" >ng</ a > < a href ="" >D3</ a > < a href ="" >Front-end</ a > < a href ="" >Startup</ a > </ div > </ div >
JsCode:
angular.module('myApp', [])
.directive('sideBox', function
() {
return
{
restrict: 'EA',
scope: {
title: '@'
},
transclude:
true
,
template: '<div class="sidebox"><div class="content"><h2 class="header">' +
'{{ title }}</h2><span class="content"
ng-transclude
></span></div></div>'
};
});
这段代码告诉ng编译器,将它从DOM元素中获取的内容放到它发现ng-transclude指令的地方。
再来你看个官网的例子:
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'
};
});
my-dialog-close.html
< div class ="alert" > < a href class ="close" ng-click ="close()" > × </ a > < div ng-transclude ></ div > </ div >
index.html
< div ng-controller ="Controller" > < my-dialog ng-hide ="dialogIsHidden" on-close ="hideDialog()" > Check out the contents, {{name}}! </ my-dialog > </ div >
如果指令使用了transclude参数,那么在控制器无法正常监听数据模型的变化了。建议在链接函数里使用$watch服务。
controller参数可以是一个字符串或一个函数。当设置为字符串时,会以字符串的值为名字,来查找注册在应用中的控制器的构造函数.
angular.module('myApp', [])
.directive('myDirective', function
() {
restrict: 'A',
controller: 'SomeController'
})
可以在指令内部通过匿名构造函数的方式来定义一个内联的控制器
angular.module('myApp',[])
.directive('myDirective', function
() {
restrict: 'A',
controller:
function
($scope, $element, $attrs, $transclude) {
//
控制器逻辑放在这里
}
});
我们可以将任意可以被注入的ng服务注入到控制器中,便可以在指令中使用它了。控制器中也有一些特殊的服务可以被注入到指令当中。这些服务有:
1. $scope
与指令元素相关联的当前作用域。
2. $element
当前指令对应的元素。
3. $attrs
由当前元素的属性组成的对象。
< div id ="aDiv" class ="box" ></ div > 具有如下的属性对象: { id: "aDiv", class: "box" }
4. $transclude
嵌入链接函数会与对应的嵌入作用域进行预绑定。transclude链接函数是实际被执行用来克隆元素和操作DOM的函数。
angular.module('myApp',[])
.directive('myLink', function
() {
return
{
restrict: 'EA',
transclude:
true
,
controller:
function
($scope, $element,$attrs,$transclude) {
$transclude(
function
(clone) {
var a = angular.element('<a>');
a.attr('href', $attrs.value);
a.text(clone.text());
$element.append(a);
});
}
};
});
html
< my-link value ="http://www.baidu.com" >百度</ my-link > < div my-link value ="http://www.google.com" >谷歌</ div >
仅在compile参数中使用transcludeFn是推荐的做法。 link函数可以将指令互相隔离开来,而controller则定义可复用的行为 。如果我们希望将当前指令的API暴露给其他指令使用,可以使用controller参数,否则可以使用link来构造当前指令元素的功能性(即内部功能)。如果我们使用了 scope.$watch()或者想要与DOM元素做实时的交互,使用链接会是更好的选 择 。使用了嵌入,控制器中的作用域所反映的作用域可能与我们所期望的不一样,这种情况下,$scope对象无法保证可以被正常更新。当想要同当前屏幕上的作用域交互时,可以使用传入到link函数中的scope参数。
controllerAs参数用来设置控制器的别名,这样就可以在视图中引用控制器甚至无需注入$scope。
< div ng-controller ="MainController as main" > < input type ="text" ng-model ="main.name" /> < span >{{ main.name }}</ span > </ div >
JsCode:
angular.module('myApp',[])
.controller('MainController', function
() {
this.name = "Halower";
});
控制器的别名使路由和指令具有创建匿名控制器的强大能力。这种能力可以将动态的对象创建成为控制器,并且这个对象是隔离的、易于测试。
require 为字符串代表另外一个指令的名字。 require会将控制器注入到其所指定的指令中,并作为当前指令的链接函数的 第四个参数 。字符串或数组元素的值是 会在当前指令的作用域中使用的指令名称 。在任何情况下,ng编译器在查找子控制器时都会参考当前指令的模板。
compile选项本身并不会被频繁使用,但是link函数则会被经常使用。本质上,当我们设置了link选项,实际上是创建了一个postLink() 链接函数,以便compile() 函数可以定义链接函数。通常情况下,如果设置了compile函数,说明我们希望在指令和实时数据被放到DOM中之前进行DOM操作,在这个函数中进行诸如添加和删除节点等DOM操作是安全的。
compile和link选项是互斥的。如果同时设置了这两个选项,那么会把compile所返回的函数当作链接函数,而link选项本身则会被忽略。
编译函数负责对模板DOM进行转换。链接函数负责将作用域和DOM进行链接 。 在作用域同DOM链接之前可以手动操作DOM。在实践中,编写自定义指令时这种操作是非常罕见的,但有几个内置指令提供了这样的功能。
compile: function(tEle, tAttrs, transcludeFn) {
//todo:
return function(scope, ele, attrs) {
// 链接函数
};
链接函数是可选的。 如果定义了编译函数,它会返回链接函数 ,因此当两个函数都定义时,编译函数会重载链接函数。如果我们的指令很简单,并且不需要额外的设置,可以从工厂函数(回调函数)返回一个函数来代替对象。如果这样做了,这个函数就是链接函数。
它提供更底层的API来处理控制器内的数据,这个API用来处理数据绑定、验证、 CSS更新等不实际操作DOM的事情,ngModel 控制器会随 ngModel 被一直注入到指令中,其中包含了一些方法。为了访问ngModelController必须使用require设置.
ngModelController常用的元素如下:
$setViewValue() 方法适合于在自定义指令中监听自定义事件(比如使用具有回调函数的jQuery插件),我们会希望在回调时设置$viewValue并执行digest循环。
angular.module('myApp')
.directive('myDirective', function() {
return {
require: '?ngModel',
link: function(scope, ele, attrs, ngModel) {
if (!ngModel) return;
$(function() {
ele.datepicker({
//回调函数
onSelect: function(date) {
// 设置视图和调用 apply
scope.$apply(function() {
ngModel.$setViewValue(date);
});
}
});
});
}
};
});
未完待续....
备注:我也是刚刚开始学习,如果你喜欢本文的话, 推荐共勉 ,谢谢!