在angularjs还未更新出component时,我们一般会使用directive开发自定义指令或者组件,也正因为directive功能的强大,导致指令与组件概念含糊不清,所以才有后面用于做组件的component,当然对于component我们另起一篇文章再说。
directive是直接用于操作dom的函数,它甚至能直接改变dom的结构,我们从一个最简单的directive开始:
HTML:
<body ng-app="myApp" ng-controller="MyCtrl as ctrl">
<test></test>
</body>
JS:
angular.module('myApp', []) .controller('MyCtrl ', function () { })
.directive('test',function(){
return{
restrict:'E',
replace:true,
template:'<div>大噶好,偶系渣渣辉。</div>'
}
});
效果:
大噶好,偶系渣渣辉。
到这里我们已经实现了一个简单的指令(组件),现在我们可以在页面中按需复用它。假设template是一个特别复杂的dom结构,通过指令我们就可以省下重复的代码编写,节省了大量开发时间。
<body ng-app="myApp" ng-controller="MyCtrl as ctrl">
<test></test>
<test></test>
<test></test>
</body>
大噶好,偶系渣渣辉。
大噶好,偶系渣渣辉。
大噶好,偶系渣渣辉。
实际上angularjs自定义指令其实拥有很多灵活的属性,用于完成更复杂的功能,一个完整的directive模板结构应该是下面这样,属性看着有点多,比较丰富,接下来我们针对所有属性一一细说。
eg
angular.module('myApp', []).directive('directiveName', function () {
return {
restrict: String,
priority: Number,
terminal: Boolean,
template: ' String or Template Function',
templateUrl: String,
replace: 'Boolean or String',
scope: 'Boolean or Object',
transclude: Boolean,
controller: 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() {}
}
};
});
1.restrict /rɪˈstrɪkt/ 限制、约束
restrict表示指令在DOM中能以哪种形式被声明,是一个可选值,可选值范围有E(元素)A(属性)C(类名)M(注释)四个值,如果不使用此属性则默认值为E,以下四种表现相同:
<!-- E -->
<test></test>
<!-- A -->
<div test></div>
<!-- C -->
<div class="test"></div>
<!-- M -->
<!-- 指令:测试 -->
restrict的值可单个使用或者多个组合使用,比如restrict:'E’即表示只允许使用元素来声明组件,而restrict:'EACM’则表示你可以使用四种方式的任一一种来声明组件。
2.priority /praɪˈɒrəti/ 优先权
priority值为数字,表示指令的优先级,若一个DOM上存在多个指令时,优先级高的指令先执行,注意此属性只在指令作为DOM属性时起作用,我们来看个例子:
<div test test2></div> //两个指令 test和test2
angular.module('myApp', [])
.controller('MyCtrl', function () {})
.directive('test', function () {
return {
restrict: 'EACM',
priority: 10,
controller:function(){
console.log('我的优先级是10')
}
}
})
.directive('test2', function () {
return {
restrict: 'EACM',
priority: 20,
controller:function(){
console.log('我的优先级是20')
}
}
})
可以看到优先级更好的指令优先执行,若两个指令优先级相同时,声明在前的指令会先执行,ngRepeat的优先级为1000,它是所有内置指令中优先级最高的指令。大多数情况下我们会忽略此属性,默认即为0;
3.terminal /ˈtɜːmɪnl/ 终端
terminal值为布尔值,用于决定优先级低于自己的指令是否还执行,例如上方例子中,我们为test2指令添加terminal:true,可以看到test指令不会执行:
4.template /ˈtempleɪt/ 模板
template的值是一段HTML文本或一个函数,文章开始的HTML文本的例子上文已有展示,这里简单说下值为函数的情况,我们来看个例子:
<div test3 title="定品寻良骥"></div>
angular.module('myApp', [])
.controller('MyCtrl', function () {})
.directive('test3', function () {
return {
restrict: 'EACM',
template: function (_element, _attr) {
console.log(_element,_attr);
return '<div style="color: #f00;font-size: 36px; padding: 20px">陈群:'+_attr.title+'</div>'
}
}
})
template函数接受两个参数,_element(元素)和_attr(属性),然后我们分别console两个属性,可以清晰看到_element表示正在使用此指令的DOM元素,而_attr包含了使用此指令DOM元素上的所有属性。(详见下图)
所以在上述例子中,我们在DOM上添加了一个name属性,而在函数中我们通过tAttrs.name访问了此属性的值,所以最终DOM解析渲染为如下:
等到熟悉directive后,你会发现templateUrl相对template对于模板的处理更优雅,所以一般不会使用template。
5.templateUrl 模板路径
相对template直接将模板代码写在指令中,templateUrl推荐将模板代码另起文件保存,而这里保存对文件路径的引用;当然templateUrl同样支持函数,用法与template相同就我们来看一个简单的例子:
angular.module('myApp', [])
.controller('MyCtrl', function () {})
.directive('test4', function () {
return {
restrict: 'EACM',
templateUrl: 'template/test4-template.html'
}
})
特别注意,在使用template与templateUrl的模板文件时,如果你使用了replace:true属性(下面有介绍),且模板代码DOM结构有多层,请记住使用一个父级元素包裹你所有DOM结构,否则会报错,因为angularjs模板只支持拥有一个根元素。
正确写法:
<div class="root-div">
<span>定品寻良骥</span>
<span>中正探人杰</span>
</div>
错误示范:
<span>定品寻良骥</span>
<span>中正探人杰</span>
然后,在使用templateUrl时,需要在本地启动服务器来运行你的angular项目,否则在加载模板时会报错。可以是php的环境或者更简单的HbuiderX预览,可以自动创建本地服务。
6.replace /rɪˈpleɪs/ 替换
replace值为布尔值,用于决定指令模板是否替换声明指令的DOM元素,默认为false,我们来看两个简单的例子:
//--------------------------------------------------示例1
<test5></test5>//指令作为元素
//当replace: false 渲染的DOM结构如下
<test5>
<div>定品寻良骥</div>
</test5>
//当replace: true 渲染的DOM结构如下(指令被替换,只保留有模板中的dom结构)
<div>定品寻良骥</div>
//--------------------------------------------------示例2
<div test5></div>//指令作为属性
//当replace: false 渲染的DOM结构如下
<div test5>
<span>定品寻良骥</span>
</div>
//当replace: true 渲染的DOM结构如下(整个元素都被替换成模板元素,同时还保留了属性test5。)
<span test5>定品寻良骥</span>
7.scope [skəʊp:] 作用域
scope属性用于决定指令作用域与父级作用域的关系,可选值有布尔值或者一个对象,默认为false
①当 scope:flase 时,表示指令不创建额外的作用域,默认继承使用父级作用域,所以指令中能正常使用和修改父级中所有变量和方法。子主动修改影响父级。
②当 scope:true 时表示指令创建自己的作用域,但仍然会继承父作用域,如果自己的$scope.属性没有初始化值,那么这个属性就继承父级的,如果初始化或者改变过后就无法再次继承父级的scope,这种情况简称为继承且隔离,
直接继承可直接使用父级作用域,但实际开发中,我们自定义的指令往往会在各种上下文中使用,只有保证指令拥有隔离作用域,不会关心和不影响上下文,这样才能极大提升指令复用性。
然后特定情况下需要继承,有隔离自然有解决方案,这就得使用绑定策略了。angularjs中directive的绑定策略分为三种,@、=、和&
1、@通常用于传递字符串,注意,使用@传递过去的一定得是字符串,而且@属于单向绑定,即父修改能影响指令,但指令修改不会反向影响父
2、= 用于传递各类数据,字符串,对象,数组等等,而且是双向绑定,即不管修改父还是子,这份数据都会被修改(和scope: false结果一样?)
3、& 用于传递父作用域中声明的方法,也就是通过&我们可以在指令中直接使用父的方法
8.controller [kənˈtrəʊlə®] 控制器
我们都知道angular中控制器是很重要的一部分,我们常常在控制器操作数据通过scope作为桥梁以达到更新视图变化的目的,很明显指令拥有自己的scope,当然拥有自己的controller控制器也不是什么奇怪的事情。
controller的值可以是一个函数,或者一个字符串,如果是字符串指令会在应用中查找与字符串同名的构造函数作为自己的控制器函数,我们来看一个例子:
<body ng-app="myApp" ng-controller="MyCtrl">
<test></test>
</body>
<script>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
let vm = this;
$scope.name = '陈群';
}).directive('test', function () {
return {
restrict: 'EACM',
scope: {},
template: '<div><input type="text" ng-model="name"><div>',
replace: true,
controller: 'myCtrl'
}
})
</script>
在上述例子中,我们在父作用域声明了一个变量name,有趣的是我们并未对指令传递name属性,甚至还为指令添加了隔离作用域,但是因为指令的controller的值使用了与父作用域控制器相同的名字myCtrl,导致指令中也拥有了相同的controller,同样拥有了自己name属性,但这两个name属性互不干扰,毕竟有隔离作用域的存在。
如果控制器的值是一个函数,那就更简单了,还是上面的例子我们只是改改controller的值,如下:
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
let vm = this;
$scope.name = '陈群';
}).directive('test', function () {
return {
restrict: 'EACM',
scope: {},
template: '<div><input type="text" ng-model="name"><div>',
replace: true,
controller: function ($scope) {
$scope.name = '司马懿';
}
}
})
当然指令的controller的形参不止一个scope,一共有 element, transclude四个。。。。。
$scope:指令当前的作用域,所有在scope上绑定的属性方法,在指令中都可以随意使用,在上面的例子中我们已经有所展示。
$element:使用指令的当前元素
$attr:使用指令当前元素上的属性
$transclude:链接函数,用于克隆和操作DOM元素,没错,通过此方法我们甚至能在controller中操作dom,注意,如果要使用此方法得保证transclude属性值为true
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
}).directive('test', function () {
return {
restrict: 'EACM',
scope: {
myName: "&"
},
transclude: true,//若想使用$transclude方法请设置为true
controller: function ($scope, $element, $attrs, $transclude) {
$transclude(function (clone) {
var a = angular.element('<a>'); //angular生成DOM标签
a.attr('href',$attrs.attr);//取得div上的attr属性并设置给a
a.text(clone.text());// 通过CLONE属性可以获取指令嵌入内容,包括文本,元素名等等,已经过JQ封装,这里获取文本并添加给a
$element.append(a); // 将a添加到指令所在元素内
})
}
}
})