指令是AngularJS应用最重要的组成部分之一,是对HTML属性或元素的扩展,通常情况下Web浏览器并不能识别这些属性或元素,但是AngularJS框架会把他们作为指令,然后执行相应的逻辑处理,最终将这些指令解析为Web浏览器能够识别的元素
指令的出现形式有以下4种:
<my-directive></my-directive>
<input type="text" my-directive/>
<input type="text" class="my-directive"/>
<!--my-directive-->
一、内置指令详解
1.ng-app:主要用于启动AngularJS框架,通常声明在<html>标签种,让AngularJS框架管理整个DOM元素
2.ng-model:用于表单元素和对应作用域中的属性建立数据绑定
3.ng-init:用于初始化AngularJS作用域,可以出现在任何HTML元素中
4.ng-controller:用于实例化指定的控制器对象
5.ng-disabled:用于禁用表单元素,当属性值为true时,表单被禁用。此外,还支持将AngularJS表达式返回结果作为判断条件
<input type="text" ng-disabled="true" ng-model="name"/>
<input type="text" ng-disabled="{{result}}" ng-model="name"/>
6.ng-checked:根据指令值true/false指定表单元素是否被选中,适用于checkbox和radio类型表单,可接收AngularJS表达式结果作为判断条件
7.ng-change、ng-click、ng-blur、ng-mousedown...:用于将表单内容变化事件和控制器中定义的事件处理方法进行绑定
8.ng-class:指定HTML元素中使用的CSS样式,可接收AngularJS表达式返回结果
9.ng-show/ng-hide:通过属性值true/false控制HTML元素的显示与隐藏
10.ng-bind:与表达式{{expression}}类似,但只在AngularJS加载完成后显示
11.ng-if:ng-if指令后指定一个表达式,表达式为false时,该指令所在的元素开始标签到结束标签之间的部分会从DOM中移除
<div ng-if="false">内容</div>
12.ng-switch/ng-switch-default/ng-switch-when:类似于javascript的switch...case
<div ng-switch on="1+1">
<p ng-switch-default>0</p>
<p ng-switch-when="1">1</p>
<p ng-switch-when="2">2</p>
<p ng-switch-when="3">3</p>
</div>
13.ng-repeat:用于遍历作用域中的集合数据
<div ng-repeat="person in persons">
姓名:{{person.name}}<br/>
年龄:{{person.age}}<br/>
地址:{{person.address}}<br/>
</div>
14.ng-href/ng-src:是对一些HTML标签的href和src属性的增强,可以接收AngularJS表达式作为属性值,可以在作用域中通过改变模型数据来改变ng-href/ng-src的属性值
15.ng-include:用于将一个单独的HTML文件内容包含到指令出现的位置
<div ng-include="'templates/navbar.html'">
二、AngularJS自定义指令
AngularJS模块对象提供了一个directive()方法来帮助我们实现自定义指令
1.directive()方法接收两个参数:第一个参数为指令名称,采用驼峰式命名法;第二个参数为指令定义方法,可以向指令方法中注入一些依赖,如$http、$rootScope等,该方法需要返回一个对象,其中应包含属性restrict、replace、template/templateUrl
--------------------------------------------------------------------------------------------------------------------
<script type="text/javascript">
var directModule=angular.module('directModule',[]);
directModule.directive("helloWorld",function(){
return {
restrict:"AE",
replace:true,
template:'<h4>你好,世界</h4>'
}
})
</script>
--------------------------------------------------------------------------------------------------------------------
2.属性详解
(1).restrict:用于约束自定义指令可以以什么形式出现,默认值为A
标志 | 指令使用形式 | 示例 |
E | 元素 | <hello-world></hello-world> |
A | 属性 | <div hello-world></div> |
C | 样式 | <div class="hello-world"></div> |
M | 注释 | <!--hello-world--> |
(2).replace:用于指定是否使用template属性定义的HTML模板内容替换指令所
在的HTML元素,例如,使用<div hello-world></div>
当replace为true时会解析成:<h4 hello-world="">你好,世界<h4>
当replace为false时会解析成:<div hello-world=""><h4>你好,世界</h4></div>
(3).template:用于指定AngularJS指令被替换成的HTML模板,可以在HTML内
容中使用其他指令和AngularJS表达式,AngularJS会先解析指令模板中的其他指令
和表达式,然后把最终结果输出到指令使用处
大多数情况下,我们会把指令模板内容写在一个单独的HTML文件中,或者使用
ng-template指令定义一个模板,然后使用templateUrl属性替换template,注意模板
helloWorl.html中只能以<div>标签开始和结束
--------------------------------------------------------------------------------------------------------------------
directModule.directive("helloWorld",function(){
return {
restrict:"AE",
replace:true,
templateUrl:'helloWorl.html'
}
});
--------------------------------------------------------------------------------------------------------------------
3.当指令采用驼峰式命名法后,使用时,可以使用"-"或者":"将多个单词隔开,为了符合HTML5命名规范,还可以再指令名称前加上"x-"或者"data-":x-hello-world、
data-hello-world
三、指令定义对象详解
directive()方法第二个参数为指令定义方法,该方法返回一个对象,即指令定义对象(DDO)
1.link方法
首先看一下在指令定义对象的HTML模板中使用AngularJS表达式的情况
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="sayHelloController">
<input type="text" ng-model="name"/>
<say-hello></say-hello>
</div>
<script type="text/javascript">
var directModule=angular.module("directModule",[]);
directModule.directive("sayHello",function(){
return {
restrict:"AEC",
replace:true,
template:"<h1>hello,{{name}}!</h1>"
};
});
directModule.controller('sayHelloController',function($scope){
$scope.name="Schuyler";
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
这样sayHello指令的HTML模板可以根据控制器作用域的name的变化实时变化
(1).如果对指令有要求,比如必须接收一个name属性,然后把属性值输出到页面中,如:<say-hello name="Schuyler"></say-hello>,这种需要访问指令属性的情况就可以使用指令对象的link方法
link()方法接收三个参数 :
scope:表示指令作用域,默认情况下和父级作用域相同
elem:表示应用当前指令的HTML元素(JQLite包装后的html元素)
attrs:表示一个对象,包含指令的所有属性及属性值
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div say-hello name="Schuyler"></div>
<script type="text/javascript">
var directModule=angular.module("directModule",[]);
directModule.directive("sayHello",function(){
return {
restrict:'AEC',
replace:true,
template:"<h1>hello,{{name}}</h1>",
link:function(scope,elem,attrs){
scope.name=attrs.name;
}
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
(2).在link()方法中,我们还可以响应HTML元素的事件和监视作用域模型数据变化
------------------------------------------------------index.html-------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div>
<hello-world></hello-world>
</div>
<script type="text/javascript">
var directModule=angular.module("directModule",[]);
directModule.directive("helloWorld",function(){
return {
restrict:'AEC',
replace:true,
templateUrl:'helloworld.html',
link:function(scope,elem,attrs){
scope.$watch("message",function(value){
console.log("Message Changed!");
});
scope.clearMessage=function(){
scope.message="";
};
}
};
});
</script>
</body>
</html>
-------------------------------------------------helloworld.html-----------------------------------------------
<div>
<input type="text" ng-model="message"/>
<h1>hello,world,{{message}}</h1>
<button ng-click="clearMessage()">清除信息</button>
</div>
--------------------------------------------------------------------------------------------------------------------
2.compile方法
AngularJS处理指令的过程:浏览器开始渲染HTML页面时,首先加载HTML元素、创建文档对象模型(DOM),加载完成后会触发onload事件,我们通过<script>标签将AngularJS库引入HTML页面中,AngularJS就会监听onload事件,然后从DOM元素中查找ng-app指令,如果找到就启动AngularJS框架,接着从ng-app指令所在的HTML标签开始使用$compile服务遍历DOM元素。我们使用directive()方法注册的指令都会保存在一个容器中,AngularJS会从这些DOM元素中识别哪些属于指令元素,AngularJS框架会根据指令定义对象决定如何处理这些指令。一旦所有的指令都被识别,就会执行指令定义对象的compile()方法。
(1).所有指令的compile()方法只会在AngularJS框架启动时调用一次
(2).compile()方法可接收两个参数:
tElem:指令所在的元素(JQLite包装过的对象)
tAttrs:指令元素的所有属性列表
(3).compile()方法和link()方法有冲突 ,如果指定了指令定义对象的compile()方法就不能再为指令定义对象增加link()方法,但是link()方法可以由compile()方法作为返回值返回
(4).compile()方法的主要作用是在link()方法执行之前对DOM元素进行修改
(5).一般情况下使用link()方法即可满足绝大部分需求,compile()方法使用较少
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div repeat="5">
<div>
<h3>hello world!</h3>
</div>
</div>
<script type="text/javascript">
var directModule=angular.module("directModule",[]);
directModule.directive("repeat",function(){
return {
restrict:'AEC',
replace:true,
compile:function(tElem,tAttrs){
console.log("compile...->"+tElem.html());
var temp=tElem.html();
for(var i=0;i<tAttrs.repeat-1;i++){
tElem.append(temp);
}
console.log("post compile...");
}
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
3.scope属性与指令作用域
默认情况下,指令直接使用父作用域,这种情况下某个指令对作用域模型数据的修改会对其他指令有影响。
在一些情况下我们需要在作用域中添加一些模型数据仅供指令内部使用,例如,需要有一个属性来控制指令元素的显示与隐藏,如果直接使用父作用域对象,那么这个属性在其他指令或控制器中也就能被访问与修改,这显然是不合理的,此时需要摆脱父作用域,可以通过以下两种方案:
(1).使用子作用域从父作用域原型继承
将指令定义对象的scope属性设置为true,表示为指令创建一个新的作用域对
象,该作用域对象会从父作用域原型继承。如果希望在指令作用域中拥有父作用域中
的所有模型数据,就可以使用这种方式
--------------------------------------------------------------------------------------------------------------------
var directApp=angular.module("directModule",[]);
directApp.directive("helloWorld",function(){
return {
scope:true,
restrict:'AE',
replace:true,
template:'<h3>hello,World</h3>'
};
});
--------------------------------------------------------------------------------------------------------------------
(2).使用孤立作用域,即创建一个新的作用域对象,和父作用域没有任何关系
将指令定义对象的scope属性设置为{},表示为指令创建孤立作用域
--------------------------------------------------------------------------------------------------------------------
var directApp=angular.module("directModule",[]);
directApp.directive("helloWorld",function(){
return {
scope:{},
restrict:'AE',
replace:true,
template:'<h3>hello,World</h3>'
};
});
--------------------------------------------------------------------------------------------------------------------
4.孤立作用域与父作用域模型数据绑定
孤立作用域无法继承父作用域的模型数据,这点在我们需要为指令创建大量供指令内部使用的模型数据时非常有用,但有些时候需要在孤立作用域中访问父作用域中的模型数据,AngularJS提供了比较灵活的方式供我们在孤立作用域与父作用域之间建立模型数据绑定,支持以下三种绑定方式:
(1).使用@符号建立基于属性的绑定
a.在指令使用时添加一个name属性,属性值使用表达式获取控制器中的name值
b.在指令定义对象的scope属性中添加一个name属性,值为'@'
c.在指令模板中通过表达式{{name}}获取父作用域中的name属性
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<input type="text" ng-model="name"/>
<say-hello name={{name}}></say-hello>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.controller("MainController",function($scope){
$scope.name="Schuyler";
});
directApp.directive("sayHello",function(){
return {
restrict:"AE",
scope:{ name:"@" },
template:"<h4>hello,{{name}}</h4>"
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
注意,sayHello指令的属性名无需和作用域的属性保持一致,如:
--------------------------------------------------------------------------------------------------------------------
<say-hello name-attr={{name}}></say-hello>
--------------------------------------------------------------------------------------------------------------------
此时指令定义对象中scope的name应该改为nameAttr,值为"@nameAttr",且模板中的表达式应改为{{nameAttr}}
--------------------------------------------------------------------------------------------------------------------
directApp.directive("sayHello",function(){
return {
restrict:"AE",
scope:{ nameAttr:"@nameAttr" },
template:"<h4>hello,{{nameAttr}}</h4>"
};
});
--------------------------------------------------------------------------------------------------------------------
使用@符号在指令的孤立作用域与父作用域之间建立基于属性的绑定是单向的,
而且无法对对象、数组等复杂数据模型进行绑定
(2).使用=符号建立双向绑定
"="符号建立双向绑定的使用与"@"符号建立单项绑定基本相同,差异如下:
a.指令使用时不需要用{{name}}表达式输出模型数据内容,而是直接指定父作用
域的属性名称
b.因为是双向绑定,当link()方法中修改指令作用域中的属性值时,父作用域中的
属性也会跟着被修改
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<input type="text" ng-model="name"/>
<say-hello name_attr="name"></say-hello>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.controller("MainController",function($scope){
$scope.name="Smith";
});
directApp.directive("sayHello",function(){
return {
restrict:"AE",
scope:{ nameAttr:"=nameAttr" },
link:function(scope,elem,attrs){
scope.nameAttr="Schuyler";
},
template:"<h4>hello,{{nameAttr}}</h4>"
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
(3).使用&符号调用父作用域中的方法
使用&符号和父作用域中的方法进行绑定,这样就能够达到调用父作用域方法的
目的,有用方法大致与"@"符号相同
a.使用指令时,添加一个属性,值为方法,如:on-message="onMessage()"
b.在指令定义对象的scope属性中使用&符号将父作用域中的方法和指令孤立作用
域中的onMessage属性建立绑定关系
c.在模板中调用方法
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<say-hello on-message="onMessage()"></say-hello>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.controller("MainController",function($scope){
$scope.onMessage=function(){
console.log("onMessage is called");
}
});
directApp.directive("sayHello",function(){
return {
restrict:"AE",
scope:{ onMessage:"&onMessage" },
template:"<button ng-click='onMessage()'>请按</button>"
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
5.Tranclusion
Tranclusion是AngularJS自定义指令中一个比较重要的概念,在计算机科学中,Tranclusion是指将整个或部分文档通过超文本引用包含到其他单个或多个文档中,简单的理解即是"嵌入"
(1).指令定义对象transclude属性介绍
a.将指令定义对象的transclude属性设置为true或者"element",true表示打开
Transclusion功能,只嵌入指令标签内容,如<p say-hello>{{name}}</p>只嵌入
{{name}};"element"则是把指令所作用的元素一同嵌入
b.在指令标签内添加内容,如<say-hello>{{name}}</say-hello>
c.在指令模板中使用ng-transclude指令:<span ng-transclude></span>,
其作用是把指令开始与结束标签中的内容插入到ng-transclude指令所在的位置
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<say-hello>
{{name}}
</say-hello>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.controller("MainController",function($scope){
$scope.name="Schuyler";
});
directApp.directive("sayHello",function(){
return {
restrict:"AE",
transclude:true,
template:"<h4>hello,<span ng-transclude></span></h4>"
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
(2).使用Transclusion实现DOM元素复制
AngularJS指令的Transclusion特性的另外一个作用是实现DOM元素的复制
指令定义对象的link()方法除了前文提及的三个参数外,实际上还有两个可选的
参数,最后一个参数为transclusion方法,名称为transcludeFn,该方法接收一个
函数,函数接收一个clone参数,clone参数为嵌入内容的备份
在link()中可以多次调用transcludeFn
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
</head>
<body>
<div ng-controller="MainController">
<say-hello>
<p>Hi,{{name}},Welcom to you!</p>
</say-hello>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.controller("MainController",function($scope){
$scope.name="Schuyler";
});
directApp.directive("sayHello",function(){
return {
restrict:"AE",
transclude:true,
link:function(scope,elem,attrs,ctrl,transcludeFn){
for(var i=0;i<5;i++){
transcludeFn(function(clone){
elem.append(clone);
});
}
},
template:"<span ng-transclude></span>"
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
6.controller方法与require属性
(1).指令定义对象的controller方法为指令控制器的构造方法,主要用于和其他指令进行通信,我们可以在指令构造方法中定义一些属性或方法供其他指令调用
(2).require属性指定要访问的指令名称,例如tabs和tab指令具有父子层级关系,设置
require:'^tabs'用于告诉AngularJS从当前元素及父级元素查找controller对象
(3).link()方法的第四个参数是一个controller对象,根据require属性可获取到父作用域
的控制器
(4).使用自定义指令同样能够达到多视图切换的效果,和AngularJS路由相比,编写指令的过程比编写路由复杂,但是使用起来比路由方便,如Tab选项卡指令
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
<link rel="stylesheet" type="text/css" href="bootstrap-3.3.7-dist/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="bootstrap-3.3.7-dist/css/bootstrap-theme.css">
</head>
<body>
<tabs active="0">
<tab lable="主页">
<p>这是我的主页</p>
</tab>
<tab lable="博客">
<p>我的个人博客:<a href="#">http://.....</a></p>
</tab>
</tabs>
<script type="text/javascript">
var directApp=angular.module("directModule",[]);
directApp.directive("tabs",function(){
return {
restrict:"AE",
scope:{},
transclude:true,
controller:function(){
this.tabs=[];
this.addTab=function addTab(tab){
this.tabs.push(tab);
};
this.selectTab=function selectTab(index){
for(var i=0;i<this.tabs.length;i++){
this.tabs[i].selected=false;
}
this.tabs[index].selected=true;
};
},
controllerAs:"tabs",
link:function($scope,$elem,$attrs,$ctrl){
$ctrl.selectTab($attrs.active||0);
},
template:
"<div>"+
"<ul class='nav nav-tabs'>"+
"<li ng-repeat='tab in tabs.tabs'>"+
"<a href='#' ng-click='tabs.selectTab($index);'
ng-bind='tab.lable'></a>"+
"</li>"+
"</ul>"+
"<div style='margin-top:30px' ng-transclude></div>"+
"</div>"
};
});
directApp.directive("tab",function(){
return {
restrict:"E",
scope:{
lable:"@"
},
require:"^tabs",
transclude:true,
template:
"<div ng-if='tab.selected'>"+
"<div ng-transclude></div>"+
"</div>"
,
link:function($scope,$element,$attrs,$ctrl){
$scope.tab={
lable:$scope.lable,
selected:false
};
$ctrl.addTab($scope.tab);
}
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------
四、自定义表单验证模式
以下自定义一个表单校验指令,实现对用户输入密码强度的控制
1.因为用到AngularJS的ngMessages模块,在自定义模块中加"ngMessages"依赖
2.将指令定义对象的require属性值设置为'ngModel',这样就可以在link()方法中访问
ngModel指令controller对象的成员和方法
3.ngModel指令controller对象的$parsers属性是一个存放方法的数组,可以通过push
方法将我们自定义的customValidator()方法添加到$parsers数组中,添加的方法会被AngularJS自动调用,添加的方法可以接收一个参数,该参数即为表单中用户输入的内容
4.ngModel指令controller对象的$setValidity()方法用于在$error对象添加一个和校验
相关的模型数据,第一个参数为模型名称, 第二个参数为一个boolean值,用于识别校验是否通过
--------------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="directModule">
<head>
<meta charset="utf-8">
<title>angularjs</title>
<script type="text/javascript" src="angular-1.5.5/angular.min.js"></script>
<script type="text/javascript" src="angular-1.5.5/angular-messages.min.js"></script>
</head>
<body>
<div style="margin-top:20px">
<form name="form1">
<label>请输入密码:</label>
<input type="text" name="strongSecret" ng-model="strongSecret"
strong-secret required/>
<div style="color:red;" ng-messages="form1.strongSecret.$error">
<div ng-message="numberValidator">
密码中至少包含一个数字!
</div>
<div ng-message="uppercaseValidator">
密码中至少包含一个大写字母!
</div>
<div ng-message="sixCharactersValidator">
密码长度至少为12!
</div>
</div>
</form>
</div>
<script type="text/javascript">
var directApp=angular.module("directModule",["ngMessages"]);
directApp.directive("strongSecret",function(){
return {
restrict:"A",
require:'ngModel',
link:function(scope,elem,attrs,ctrl){
var customValidator=function(ngModelValue){
if(/[A-Z]/.test(ngModelValue)){
ctrl.$setValidity("uppercaseValidator",true);
}else{
ctrl.$setValidity("uppercaseValidator",false);
}
if(/[0-9]/.test(ngModelValue)){
ctrl.$setValidity("numberValidator",true);
}else{
ctrl.$setValidity("numberValidator",false);
}
if(ngModelValue.length>12){
ctrl.$setValidity("sixCharactersValidator",true);
}else{
ctrl.$setValidity("sixCharactersValidator",false);
}
}
ctrl.$parsers.push(customValidator);
}
};
});
</script>
</body>
</html>
--------------------------------------------------------------------------------------------------------------------