在Angular当中,指令是可以用来实现你想做的任何事情的利器,它可以给Dom绑定你所指定的行为(例如事件监听、添加点击事件),甚至是它都可以直接转换Dom元素(例如直接将<div>标签转换成一个列表)。
而在原生的Angular中自带了很多的指令,例如ngModel、ngClass、ngRepeat等等。但有时官方的指令满足不了我们的需求,这时就需要创建特定的指令,例如当前项目中的手机号限制指令、富文本字数限制指令、模糊搜索指令等等。而且,指令可以减少重复逻辑代码的编写,方便使用,提升开发效率。所以,学习下如何自定义指令是很有必要的。
那么我们如何自定义指令?
指令的一般模板
var myModule = angular.module([]);myModule.directive('myDirective', function() { return { restrict: 'EA', priority: 1000, template: '<div></div>', templateUrl: 'test.html', replace: true, transclude: true, scope: true, controller: function ($scope, $element, $attrs, $transclude) { }, require: 'ngRepeat', link: function (scope, iElement, iAttrs) { } };});
它里面有很多属性,每个属性的作用如下图:
实现一个指令
第一个指令,helloWorld
实现指令的代码如下:
demoApp.directive('helloWorld', function () {
return {
restrict: 'E',
template: '<h4>Hello world</h4>'
}
});
当实现之后,我们就可以在我们的html中使用helloWorld指令了,如下:
<div>
<hello-world></hello-world>
</div>
在上面的helloWorld指令中,我们用到下面一些内容:
指令命名
在指令的定义中使用的是驼峰式命名,在模板使用时,使用
的是通过短横线连接的。主要是能够支持HTML校验规则,我
们这里使用的是H5的HTML规则。
例如,上面的helloWorld指令,我们在定义指令的时候,使用的是hellWorld
这种驼峰式,而我们在模板中使用指令的时候是<hello-world></hello-world>
,使用的是用短横线连接。
restrict属性
它描述指令的声明风格,即它是否可以作元素名称、元素属性、样式类或者注释。
注意:当想让一个指令可以作为元素,也可以作为属性时,可以使用“AE”
例如,上面的helloWolrd指令,只是作为一个元素,因为它的restrict为E
。
模板
模板有两种形式:
- template:以字符串形式编写的一个内联模板
- templateUrl:加载模板的URL
上面的helloWorld指令,使用的是template
属性,直接填充<h4>Hello world</h4>
字符串作为指令的内容。
最终,我们的helloWorld使用的,达到的效果如下:
除了,上面helloWorld使用的restrict和template外,还有一些重要的属性,例如replace、transclude、scope、link,我们都来学习下。
replace
为true,则替换指令所在的元素;为false,则把当前指令追加到指令所在的元素内部,默认false。
若是,将helloWorld指令replace设置为true,代码如下:
demoApp.directive('helloWorld', function () { return { restrict: 'E', template: '<h4>Hello world</h4>', replace: true }});
指令转换结果:
transclude
为true,则把指令所在元素中原来的子节点移动到ng-transclude所在元素内;为false,则直接忽略内部的子节点,默认false。
若是将helloWorld指令transclude设置为true,它的代码如下:
demoApp.directive('helloWorld', function () { return { restrict: 'E', template: '<h4>Hello world<span ng-transclude style="color: red"></span></h4>', replace: true, transclude: true }});
指令转换结果:
scope属性
指令的作用域,默认false。当为false,为所在Dom元素上的作用域;当设置true时,创建一个新的作用域,所在元素的作用域是它的父作用域,它继承父作用域上所有的属性;当设置scope为一个对象时,创建一个独立的作用域,所在元素的作用域仍然是它的parent,但它不继承父对象上任何属性。
当scope设置false时
html代码:
<body ng-controller="demoController">
<div>指令外: {{name}}</div>
<hello-world>测试</hello-world>
</body>
js代码:
var demoApp = angular.module('demoApp', []);
demoApp.controller('demoController', function ($scope) {
$scope.name = "Jack";
});
demoApp.directive('helloWorld', function () {
return {
restrict: 'E',
template: '<h4>指令内部: {{name}}</h4>',
replace: true,
transclude: true,
scope: false,
link: function (scope) {
scope.name = "test";
}
}
});
效果如下:
解析:因为指令外和指令内部的是同一个作用域,所以指令内部的name改变,也造成指令外面变化。
当scope设置true时
html代码:
<body ng-controller="demoController">
<div>指令外: {{name}}</div>
<hello-world>测试</hello-world>
</body>
js中,指令的实现代码如下:
var demoApp = angular.module('demoApp', []);demoApp.controller('demoController', function ($scope) { $scope.name = "Jack";});demoApp.directive('helloWorld', function () { return { restrict: 'E', template: '<h4>指令内部, 改变之前:{{preName}}, 改变之后: {{name}}</h4>', replace: true, transclude: true, scope: true, link: function (scope) { scope.preName = scope.name; scope.name = "test"; } }});
结果:
解析:指令内部改变之前的名字是Jack,与外面一样;改变之后,指令外部没有变化,仍然是test。可以分析出,当设置为true时,指令内部继承了外部的属性,但它是一个新的作用域。还可以通过console.log打印进一步验证,外面的作用域是内部作用域的parent。
scope设置一个对象
使用时,在html中的代码如下:
<body ng-controller="demoController"> <div>指令外: {{name}}</div> <hello-world new-name="name">测试</hello-world></body>
js中,指令的实现代码如下:
var demoApp = angular.module('demoApp', []);demoApp.controller('demoController', function ($scope) { $scope.name = "Jack";});demoApp.directive('helloWorld', function () { return { restrict: 'E', template: '<h4>指令内部, name:{{name}}, scopeName: {{scopeName}}</h4>', replace: true, transclude: true, scope: {scopeName: '=newName'}, link: function (scope) { scope.name = scope.name; scope.scopeName = "test"; } }});
结果:
![image.png](https://upload-images.jianshu.io/upload_images/3120119-687abc62226d3ad4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
解析:name内容不存在,说明没有继承父scope的属性;因为使用’=newName’绑定了外部属性’name’,所以内部改变,指令外的值也变化了。
link函数
链接函数,在链接阶段执行,可以在内部操作Dom、添加事件监听、设置数据绑定等等。
例如,实现的一个限制手机号的指令,解决了当input为num类型时不能限制11位数字的问题。内部使用element.on监听Dom事件,使用element.after添加元素。
html使用代码:
<input type="text" ng-model="phone" placeholder="请输入手机号" phone-limit/>
js中,指令的实现代码如下:
.directive('phoneLimit', function () { return { restrict: "A", scope: { model: '=ngModel' }, link: function (scope, element, attrs, ctrl) { // console.log(element) //添加一个错误提示元素 function appendErrorTip() { var msg = "<span class='error-tip'></span>"; element.after(msg); // $(element).after(msg); } //输入限制 function inputLimit() { var value = String(element.val()); //截取到哪一个字符位置 var findIndex = -1; for(var i = 0; i < value.length; i++) { if(value[i] < '0' || value[i] > '9') { findIndex = i; break; } if(i >= 11) { findIndex = i; break; } } //查找到了, 则截取 if(findIndex != -1) { element.val(value.substr(0, findIndex)); scope.model = element.val(); } } //绑定输入事件 element.on('input', inputLimit); //添加错误提示元素 appendErrorTip(); element.on("blur", function () { var value = element.val(); //验证不通过,标红 if(value == undefined || value.length != 11) { element.next().html('手机号不正确'); } }) } }});
指令间通信
用到了两个属性,如下:
- controller:创建一个控制器,通过这个控制器,可以实现指令之间的通信
- require:要求必须存在另一个指令,当前指令才能正确运行
主要是通过控制器进行通信的,通过require属性语法,可以把require中的指令的控制器传递给当前指令,从而实现了通信。
其中,require的用法:
如:require: '?^^myTabs'
^^前缀:说明angular会从所有父元素上查找到myTabs指令,并且获取myTabs指令的控制器;当是^前缀时,说明angular会从当前元素和所有父元素上查找到myTabs指令,并且获取myTabs指令的控制器;当没有^
和^^
时,则说明angular只会从当前元素查找到myTabs指令,并且获取myTabs指令的控制器。
?前缀:可选的意思。具体是,当没有?前缀时,angular按照上面的规则没有查找到需要的控制器,那么就会抛出异常;如果有?前缀时,则就算没查找到,也不会抛出异常。