前言
使用指令的优势在于,我们无需太多关心指令的内部实现(当给 Angular 应用添加所需指令后,Angular 内部会自行编译和运行所有指令),只需把重点放在如何使用指令上即可。
对于指令,我们需要了解一下几个问题:
- 什么是指令?
- 如何创建指令?
- 指令是如何编译和运行的?
什么是指令?
指令就是 AngularJS 扩展具有自定义功能的 HTML 元素的途径,指令又分为内置指令和自定义指令。
内置指令
AngularJS 提供了一系列的内置指令。其中一些指令重载了原生 HTML 元素,比如:
<form>
和<a>
标签,当在 HTML 中使用标签时,并不一定可以明确看出是否在使用指令。
基础 ng 属性指令
布尔属性
根据 HTML 标准的定义,布尔属性代表了 true
或 false
。当这个属性出现时即为 true
,未出现为 false
。AngularJS 提供了一组布尔属性,通过表达式的值来判断是否在元素上插入或移除该属性。
-
ng-disabled
使用ng-disabled
可以对disabled
属性进行绑定。 -
ng-readonly
使用ng-readonly
可以对readonly
属性进行绑定。 -
ng-checked
使用ng-checked
可以对checked
属性进行绑定。 -
ng-selected
使用ng-selected
可以对option
标签中的selected
属性进行绑定。
<div ng-controller='MyApp'>
<p><button ng-disabled="isDisabled">ng-disabled = true</button></p>
<p><button ng-disabled="!isDisabled">ng-disabled = false</button></p>
<p><input type="text" ng-readonly="isReadonly" value="ng-readonly = true"></p>
<p><input type="text" ng-readonly="!isReadonly" value="ng-readonly = false"></p>
<p><input type="checkbox" ng-checked="isChecked">ng-checked = true</p>
<p><input type="checkbox" ng-checked="!isChecked">ng-checked = false</p>
<p>
<select>
<option>ng-selected = false</option>
<option ng-selected="isSelected">ng-selected = true</option>
</select>
</p>
</div>
angular.module('myApp', [])
.controller('MyApp', ['$scope', function ($scope) {
$scope.isDisabled = true;
$scope.isReadonly = true;
$scope.isChecked = true;
$scope.isSelected = true;
}])
注: 不是将属性值设为 true 或 false,而是该属性是否出现。
类布尔属性
ng-href
、ng-src
等属性虽然不是标准的 HTML 布尔属性,但是由于行为相似,所以 AngularJS 是和布尔属性同等对待的。
-
ng-href
当url
为动态绑定时,应使用ng-href
代替href
,AngularJS 会在链接生效后再执行点击行为。 -
ng-src
当src
地址为动态绑定时,应使用ng-src
代替src
,AngularJS 会告诉浏览器,在ng-src
对应的表达式生效之前不要加载图像。
在指令中使用子作用域
ng-app
和 ng-controller
是特殊的指令,因为他们会修改嵌套在它们内部的指令的作用域。
-
ng-app
任何具有ng-app
属性的 DOM 元素将会被标记为$rootScope
($rootScope
是作用域链的起始点,任何嵌套在ng-app
内的指令都会继承它)的起始点。
注:如果需要在一个页面中放置多个 AngularJS应用,需要手动引导应用。 -
ng-controller
在 DOM 元素上放置一个控制器,为嵌套在其中的指令创建一个子作用域,避免将所有操作和模型都定义在$rootScope
上。
ng-controller
接收一个必备参数expression
(一个 AngularJS 表达式)。
以下内置指令具有同样的特性: -
ng-include
使用ng-include
可以加载、编译并包含外部 HTML 片段到当前应用中。 -
ng-switch
ng-switch
和ng-switch-when
及on="propertyName"
一起使用,可以在propertyName
发生变化时渲染不同指令到视图中。
<input type="text" ng-model='person.name'>
<div ng-switch on='person.name'>
<p ng-switch-default>默认显示</p>
<p ng-switch-when='show'>满足条件显示</p>
</div>
-
ng-view
ng-view
用来设置将被路由管理和放置在 HTML 中的视图的位置。 -
ng-if
ng-if
可以根据表达式的值在 DOM 中生成或移除一个元素。 -
ng-repeat
ng-repeat
用来遍历一个集合或为集合中的每个元素生成一个模板实例。
-
$index
:遍历索引(0 ~ length - 1)。 -
$first
:当元素为遍历的第一个时值为true
。 -
$middle
:当元素处于第一个和最后一个之间时值为true
。 -
$last
:当元素为遍历的最后一个时值为true
。 -
$even
:当$index
的值为偶数是值为true
。 -
$odd
:当$index
的值为奇数是值为true
。
.even {
background: #abcdef;
}
.odd {
background: #eee;
}
.first {
background: #ff0;
}
.last {
background: #f00;
}
<p ng-repeat="val in list"
ng-class="{even: $even, odd: $odd, first: $first, last: $last}">{{$index}}</p>
-
ng-init
ng-init
用来设置指令被调用时的内部作用域的初始状态。 -
{{ }}
{{ }}
是 AngularJS 内置的模板语法,在$scope
和视图之间创建绑定,$scope
变化时,视图就会随之自动更新。
注:{{ }}
实际上是ng-bind
的简略形式,但在屏幕可视区内使用{{ }}
会导致页面加载时未渲染的元素发生闪烁,用ng-bind
可以避免这个问题。 -
ng-bind
同{{ }}
。 -
ng-cloak
除了使用ng-bind
来避免闪烁外,还可以在含有{{ }}
的元素上使用ng-cloak
指令。 -
ng-bind-template
同ng-bind
类似,ng-bind-template
用来在视图中绑定多个表达式。
<p ng-bind-template='{{name}} {{position}}'></p>
-
ng-model
ng-model
用来将表单控件同包含它们的作用域中的属性进行绑定。 -
ng-show / ng-hide
ng-show
和ng-hide
是通过 CSS 控制元素的显隐(ng-if
则是通过添加或移除dom
)。 -
ng-change
在表单输入发生变化时触发。需要与ng-model
一起使用。 -
ng-form
ng-form
用来在一个表单内部嵌套另一个表单。普通的 HTML<form>
标签不允许嵌套,但是ng-form
可以(所以,只有内部所有子表单都合法时,外部表单才合法)。
下列 CSS 类会根据表单的验证状态自动设置:
- 表单合法时设置
ng-valid
- 表单不合法时设置
ng-invalid
- 表单未进行修改时设置
ng-pristion
- 表单进行过修改时设置
ng-dirty
input {
margin-bottom: 10px;
}
input.ng-invalid {
border: 1px solid red;
}
input.ng-valid {
border: 1px solid green;
}
<form name="signup_form" ng-controller="FormCtrl" ng-submit="submitForm()" novalidate>
<div ng-repeat="field in fields" ng-form="signup_form_input">
<input type="text"
name="dynamic_input"
ng-required="field.isRequired"
ng-model="field.name"
placeholder="{{field.placeholder}}" />
<div ng-show="signup_form_input.dynamic_input.$dirty && signup_form_input.dynamic_input.$invalid">
<span class="error" ng-show="signup_form_input.dynamic_input.$error.required">必填项</span>
</div>
</div>
<button type="submit" ng-disabled="signup_form.$invalid">提交</button>
</form>
angular.module('myApp', [])
.controller('FormCtrl', function($scope) {
$scope.fields = [
{placeholder: '请输入用户名', isRequired: true},
{placeholder: '请输入密码', isRequired: true},
{placeholder: '请输入邮箱(选填)', isRequired: false}
];
$scope.submitForm = function() {
alert("it works!");
};
});
-
ng-click
ng-click
用来指定元素被点击是所触发的事件。 -
ng-select
ng-select
用来将数据同 HTML 的<select>
元素进行绑定。可以和ng-model
及ng-options
一同使用。
ng-options
的值可以使一个内涵表达式(comprehension expression),简单来说就是它可以接受一个数组或对象,并对它们进行循环,将内部内容提供给select
标签内部的选项。
(1). 数组作为数据源:
- 用数组中的值做标签
- 用数组中的值作为选中的标签
- 用数组中的值做标签组
- 用数组中的值作为选中的标签组
(2). 对象作为数据源:
- 用对象的键 - 值(key - value)做标签
- 用对象的键 - 值作为选中的标签
- 用对象的键 - 值做标签组
- 用对象的键 - 值作为选中的标签组
<div ng-controller="Directive">
<select ng-model="item" ng-options="item.name for item in list">
<option value="">who</option>
</select>
<p>Winner:{{item.name}}</p>
</div>
angular.module('directive', [])
.controller('Directive', Directive);
Directive.$inject = ['$scope'];
function Directive ($scope) {
$scope.list = [
{
id: 1,
name: 'player1'
}, {
id: 2,
name: 'player2'
}, {
id: 3,
name: 'player3'
}
];
}
-
ng-submit
ng-submit
用来将表达式同onsubmit
时间进行绑定。这个指令会阻止默认行为(发送请求并重新加载页面),但前提是表单不含有action
属性。 -
ng-class
ng-class
可以动态设置元素的类,给要动态添加的类绑定一个表达式,当表达式为true
时,这个类会被添加,反之会被移除。 -
ng-attr-(suffix)
当 AngularJS 编译 DOM 时会查找花括号{{ some expression }}
内的表达式。这些表达式会被自动注册到$watch
服务中并更新到$digest
循环中,成为他的一部分:
<p>{{title}}</p>
有时候浏览器会对属性进行严格限制,如 SVG
<svg>
<circle cx="{{ cx }}"></circle>
</svg>
此时会报错,指出有一个非法属性。这时可以用 ng-attr-(suffix)
解决。
<svg>
<circle ng-attr-cx="{{ cx }}"></circle>
</svg>
-
ng-style
ng-style
为 HTML 元素添加 style 属性,且属性值必须是对象。
<p ng-style="{{style}}"></p>
$scope.style = {
width: '100px',
height: '100px',
background: '#000'
}
自定义指令
定义一个指令
angular.module('directive', [])
.directive('myDirective', myDirective);
function myDirective () {
// 一个指令定义对象
return {
// 通过设置项定义指令
}
}
directive()
方法接收两个参数:
-
name
(字符串)
指令的名字,用来在视图中引用该指令。 -
factory_function
(函数)
该函数返回一对象,在其中定义这个指令的全部行为。$compile
服务利用这个方法返回的对象,在 DOM 调用指令时来构造指令的行为。
指令的配置项
angular.module('directive', [])
.directive('myDirective', myDirective);
function myDirective () {
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
(String)
restrict
告诉 AngularJS 该指令以何种形式在 DOM 中被声明(默认A
)。
E - 以元素形式声明
<my-directive></my-directive>
C - 以类名形式声明
<div class="my-directive"></div>
M - 以注释形式声明
< !-- directive:my-directive -- >
A - 以属性形式声明
<div my-directive></div>
priority
(Number)
priority
可以用来设置指令的优先级,默认 0,通常情况下会忽略该属性,使用默认值(但也有场景需要设置高优先级,如ng-repeat
的该参数为 1000,所以它总是在其他指令之前被调用;若优先级一直,则先声明先调用)。terminal
(Boolean)
terminal
可以用来阻止当前元素上比自己优先级低的指令。
例:ngIf
的优先级略高于ngView
, 如果ngIf
表达式值为true
, 则ngView
会正常执行,反之,由于ngView
优先级较低就不会被执行。-
template
(String or Function)
template
为可选参数,可以为:- 一段 HTML 文本;
- 一个可以接受两个参数(
tElement
,tAttrs
)的函数,并返回一个代表模板的字符串。tElement
,tAttrs
中的t
代表template
。
注:模板字符串中必须只存在一个根 DOM 元素
template: '<div>template</div>',
折行时需在末尾加上反斜线,这样才能正确解析多行字符串。
template: '<div>\
template\
<p>wrap</p>\
</div>',
-
templateUrl
(String or Function)
templateUrl
为可选参数,可以为:- 一个代表外部 HTML 文件路径的字符串;
- 一个可以接受两个参数的函数,参数为
TElement
和tAttrs
,并返回一个外部 HTML 文件路径的字符串。
-
replace
(Boolean)
replace
为可选参数,如果设置这个参数,值必须为true
(默认为false
)。
若值为false
则意味着模板会被当做子元素插入到调用此指令的元素内部:
若值为 true
则意味着模板会替换调用此指令的元素:
-
scope
(Boolean or Object)
scope
为可选参数,可以设置为true
或一个对象(默认为false
)。
若值为true
,会从父作用域继承并创建一个新的作用域对象。
每个指令被调用时可能会:
(1). 默认值false
,继承父作用域不创建新作用域(互相影响);
<div ng-app='myApp'>
<div ng-controller='MyCtrl'>
父:{{value}}
<directive-dom></directive-dom>
</div>
</div>
<script>
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.value = 1;
}])
.directive('directiveDom', function () {
return {
restrict: 'ECMA',
template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
scope: false
}
});
</script>
(2). scope: true
,创建一个新的继承父作用域的新的独立作用域(互不影响);
<div ng-app='myApp'>
<div ng-controller='MyCtrl'>
父:{{value}}
<directive-dom></directive-dom>
</div>
</div>
<script>
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.value = 1;
}])
.directive('directiveDom', function () {
return {
restrict: 'ECMA',
template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
scope: true
}
});
</script>
(3). scope: {} / {...}
:
a.{}
:创建一个隔离父作用域的新作用域
<div ng-app='myApp'>
<div ng-controller='MyCtrl'>
父:{{value}}
<directive-dom></directive-dom>
</div>
</div>
<script>
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.value = 1;
}])
.directive('directiveDom', function () {
return {
restrict: 'ECMA',
template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
scope: {}
}
});
</script>
b. {...}
:可以与父作用域的属性方法进行绑定
绑定的三种形式:
@
(or @attr
):绑定字符串
=
(or =attr
):双向绑定
&
(or &attr
):绑定方法
<div ng-app='myApp'>
<div ng-controller="MyCtrl">
选手 {{obj.id}} 的成绩为: {{obj.value}}
<directive-dom obj="obj" str="绑定字符串" fn="fn(num)"></directive-dom>
</div>
</div>
<script>
(function () {
'use strict';
angular
.module('myApp', [])
.controller('MyCtrl', MyCtrl)
.directive('directiveDom', directiveDom);
MyCtrl.$inject = ['$scope'];
function MyCtrl($scope) {
$scope.obj = {
value: 85,
id: 1
};
$scope.fn = function (num) {
console.log('我被点击了' + num + '次');
};
}
function directiveDom() {
return {
restrivt: 'ECMA',
template: '<div><button ng-click="nextHandle()">下一位</button><p>{{str}}</p></div>',
scope: {
str: '@',
obj: '=',
fn: '&'
},
link: function ($scope) {
var n = 0;
$scope.nextHandle = function () {
n = n + 1;
$scope.obj.id = $scope.obj.id < 10 ? $scope.obj.id + 1 : 1;
$scope.obj.value = $scope.obj.id < 10 ? Math.floor(Math.random() * 100 + 1) : 85;
$scope.fn({num: n});
}
}
}
}
})();
</script>
-
transclude
(Boolean or Object)
transclude
设置是否将指令内部的元素嵌入到模板中,可选参数,如果设置这个参数,默认为false
。 -
controller
(String or function)
controller
为字符串时,会以字符串的值为名字,查找注册在应用中的控制器构造函数,也可以通过匿名构造函数的方式定义一个内联控制器(可以向其中注入任意AngularJS
服务)。
(1).$scope
:与指令元素相关联的作用域;
(2).$element
:当前指令对应的元素;
(3).$attrs
:由当前元素的属性组成的对象;
(4).$transclude
:嵌入链接函数会与对应的嵌入作用域进行预绑定(transclude
链接函数是实际被执行用来克隆元素和操作DOM
的函数)。
angular.module('myApp', [])
.directive('directiveDom', function () {
return {
restrivt: 'ECMA',
controller: function ($scope, $element, $attrs, $transclude) {
}
}
});
注:controller
主要是用来提供可以在指令间复用的行为,link
只能在当前内部指令中定义行为,无法在指令间复用
-
controllerAs
(String)
controllerAs
用来设置控制器别名 -
require
(String or Array)
require
可以将controller
注入到其值所指定的指令中,并作为当前指令link
的第四个参数,设置为字符串时,代表另外一个指令的名字。
require
参数值的修饰前缀:
(1).?
:在当前指令进行搜索(如果没有会传入null
);
(2).^
:在上游指令链中进行搜索;
(3).?^
:可以选择地加载需要的指令并在父指令链中进行搜索;
(4).无:在自身所提供的控制器中进行搜索(如果没有会抛出一个错误)。 -
compile
(Object or Function)
通常情况下,设置compile
函数是希望在指令和实时数据被放到DOM
中之前进行DOM
操作(编译函数负责对模板DOM
进行转换)。
注:compile
和link
是互斥的,如果同时设置,则link
会被忽略。 -
link
(Function)
如果指令中有require
选项,则第四个参数代表控制器或所依赖的指令的控制器,若require
选项为一个指令数组,则第四个参数代表由每个指令所对应的控制器所组成的数组(链接函数负责将作用域和DOM
进行链接)。
(1).scope
:作用域;
(2).iElement
:实例元素,即使用此指令的元素;
(3).iAttrs
:实例属性(可以在所有指令的链接函数间共享);
(4).controller
:指向require
选项所定义的控制器(可以在所有指令间共享)。
参考资料:
《Angular JS 权威教程》