修真院Web工程师零基础全能课
本节课内容
AngularJs进阶-指令
主讲人介绍
沁修,葡萄藤技术总监
项目经验丰富,擅长H5移动项目开发。
专注技术选型、底层开发、最佳代码实践规范总结与推广。
直播录屏版
传送门:https://v.qq.com/x/page/p0775...
文字版解析
概述
指令是什么?
其实我们从一开始接触到angularjs就在使用指令了
指令就像是教给html的一些小花招,它会附加在html的元素上,以自定义标记的方式,告诉angularjs的html编译器:
这某个元素加上了一些特定的行为或者是方法,还可以对html的元素进行各种操作。
angularjs中它本身是内置了一整套指令的,比如ngBind,ngClick,ngApp,ngView等等
但也可以自己创建自己的指令来方便我们的开发和使用。
我们先来看一下怎么去使用指令directive?
当angular启动我们的程序时,html编译器$compile就会遍历整个DOM,并且会去匹配DOM元素里的指令
那这些指令通常是怎么放置在html中的呢?
通常有4种方法:
<!-- 1. 属性的方式 -->
<spanmy-dir="exp"></span>
<!-- 2. 类的方式 -->
<spanclass="my-dir: exp;"></span>
<!-- 3. 元素的方式 -->
<my-dir></my-dir>
<!-- 4. 注释的方式 -->
<!-- directive: my-dir exp -->
不过建议大家以属性的方式为主,元素的方式为辅,其他的两种方式可以不去使用。
这样更容易看出来一个元素是跟那个指令相匹配的,更因为很多的指令都被限制为属性的方式。
这里就不多做演示了,我们默认都使用属性的方式。
指令的命名规范是?
指令有许多,如ngView, ngBind, ngModel等等。
但写在html中的时候有个问题就是html是不识别大小写区别的,因此驼峰的写法就会有问题。
有了指令匹配到元素上后,我们的编译器还需要通过另外的命名规范来匹配它们是否是一个指令:
因此命名规范会将驼峰式改为全小写,然后用破折号或者其他符号间隔的形式来实现,比如以下写法都是正确合法的:
- ng:bind
- ng-bind
- ng_bind
- x-ng-bind
- data-ng-bind
我们试着使用一下它:
<divng-controller="Fn1">
<p>{{ user }}</p>
</div>
<divng_controller="Fn2">
<p>{{ user }}</p>
</div>
<divng:controller="Fn3">
<p>{{ user }}</p>
</div>
<divdata-ng-controller="Fn4">
<p>{{ user }}</p>
</div>
<divx-ng-controller="Fn5">
<p>{{ user }}</p>
</div>
<scriptsrc="bower_components/angular/angular.js"></script>
<scripttype="text/javascript">
varmyApp=angular.module("myApp", []);
myApp.controller('Fn1',function($scope) {
$scope.user='Alex';
});
myApp.controller('Fn2',function($scope) {
$scope.user='Bill';
});
myApp.controller('Fn3',function($scope) {
$scope.user='Coco';
});
myApp.controller('Fn4',function($scope) {
$scope.user='Danel';
});
myApp.controller('Fn5',function($scope) {
$scope.user='Eleven';
});
接下来看看指令在html中的编译步骤:
1.首先,将html转换为DOM对象
2.然后它会遍历整个DOM,识别指令,将指令和与之对应DOM一起放到directive列表当中。
按顺序执行他们每一个的compile()函数(编译函数),这个时候他们会拥有一个修改DOM结构的机会,并且会产生一个link()函数。
因此最后会返回一个组合的linking函数,是页面中所有指令自身的compile函数返回的linking函数集合。
3.通过上一步返回的linking函数集合,把html模板和scope作用域连接起来,同时还能注册一些监听器。
最后让scope和DOM之间形成双向即时的绑定。
我们来看一个例子:
<inputtype="text"ng-model="userName"><buttonng-click="addUser()">add</button>
<ul>
<ling-repeat="user in users">{{user}}</li>
</ul>
通过这个例子,我们来理解编译过程为啥要分为compile和link两步。
就是因为有时候需要在model发生改变后,对应DOM结构也要跟着改变。
其中ng-model就是一个指令,ng-repeat又是另外一个。
但ng-repeat有一个问题就是它需要很快的根据model制造出多个新的li,li元素需要被克隆和插入到ul当中。
而且仅仅只是克隆是不够的,还需的要编译,以便它的{{user}}可以被正确的scope解析。
如果我们只是简单的拷贝插入一个li元素,然后编译,这样每一个li元素发生改变,都会遍历整个DOM树重新运行。
如果的repeat的item有成百上千个,就会有性能问题。
所以解决方案就是把编译分为两个步骤:
compile阶段识别所有的directive,然后按照优先级排序,该改变DOM就改变DOM。
到linking阶段再将scope和li绑定在一起。
自定义指令?
我们先写一个最简单的指令:
比如有这么一段带有个人信息的模板,会在代码中重复多次,那么我们就可以使用指令来简化这个过程。
当然指令和控制器一样,也是注册在模块上的,我们先创建一个指令,来替换原本的内容:
<divng-controller="DirCtrl1">
<divppt-time></div>
</div>
varmyApp=angular.module("myApp", []);
myApp.controller('DirCtrl',function($scope) {
$scope.user='Tom';
$scope.current=newDate();
$scope.task=2;
})
myApp.directive('pptTime',function() {
return{
template:'{{user}}领取任务{{task}}的时间点为:{{current}}'
}
})
这里可以看到,控制器DirCtrl里的变量,可以被指令使用并展示在视图上。
我们来看看发生了什么事:
1.首先将HTML转换为DOM对象。
2.对DOM对象进行编译,遍历DOM并且对指令进行匹配。
匹配上的指令会有个修改DOM结构的机会,并且产生一个link函数并返回link函数集合
3.通过返回的link函数集合,把模板和scope连接起来。
这样在scope和DOM之间会有一个双向即时的绑定,当scope发生变化的时候,DOM也会发生对应的改变。
这样我们就有基本的指令可以使用了,但这样有一点不好就是这不是一个好的代码实践:
我们希望将模板分割出来,让视图和代码分离,因此可以将template这个选项改为templateUrl来加载相应的html模板文件,会优雅许多。
自定义指令的模板
angular.module('app', []).directive('myDirective',function() {
return{
restrict:String,
priority:Number,
terminal:Boolean,
template:String,
templateUrl:String,
replace:BooleanorString,
transclude:Boolean,
scope:BooleanorObject,
controller:Stringorfunction(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) { }
}
returnfunctionpostLink() { }
}
}
})
我们来看一下这个自定义的指令的参数,首先restrict的值是一个string,作用是申明在模板中如何使用,它有四个值:
E,A,C,M
通常我们都推荐使用元素和属性的方式
E元素<ptt-time></ptt-time>A属性(默认)<div ptt-time></div>C样式类<div class=“ptt-time”></div>M注释<!— directive: ptt-time -->
priority: number表示的是指令的执行优先级,如果在单个DOM上有多个指令,则优先执行优先级高的。这个指令很少使用
template,是指令链接的DOM模板
templeteUrl,是指定一个字符串形式的内嵌模板,加载的模板是通过异步加载的
replace,是指令链接模板是否替换原有的元素
<hello>
<div>这里是指令内部的内容。</div>
</hello>
angular.module("MyModule", []).myModule.directive("hello",function() {
return{
restrict:"AE",
template:"<div>Hello World!</div>",
replace:true
}
});
transclude,和replace有一点相似,但它是让标识符里原有的内容带到新的位置上。
开启这个之后,就可以在指令的模板中使用ng-transclude来指明什么地方放内容。
<hello>
<div>这里是指令内部的内容。</div>
</hello>
angular.module("MyModule", []).myModule.directive("hello",function() {
return{
restrict:"AE",
transclude:true,
template:"<div>Hello everyone!<div ng-transclude>你看不见我</div></div>"
}
});
link,这是为目标DOM元素添加事件监听,建立数据绑定。
它的scope参数是表示与指令元素相关的作用域,element是当前指令所对应的元素,attrs是当前元素的属性所组成的对象
controller,是船舰一个控制器,通过标识符来公开通信API,并且给指令暴露出一组方法,供外部调用。
它的参数scope是和指令相关联的作用域:
element,是当前指令对应的元素
attrs,是当前元素的属性组成的对象
transclude,是嵌入链接函数
其实指令的controller和link函数可以互换,它们的区别在于:
控制器主要提供可以在指令之间复用的行为,但link是只能在当前内部指令中执行的行为且无法在指令之间复用。
关于controller和require的用法范例
require,它的值代表了另外个指令的名字,并且会作为link的第四个参数。
假设现在我们要编写三个指令,三个指令中的link链接函数中存在有很多重合的方法。
这时候我们就可以将这些重复的方法写在一个指令的controller中。
然后在这三个指令中:
require这个拥有controller字段的的指令,最后通过link链接函数的第四个参数就可以引用这些重合的方法了。
<superman>super</superman>
<superman speed>super and speed </superman>
varmyModule=angular.module("MyModule", []);
myModule.directive("superman",function() {
return{
scope: {},
restrict:'AE',
controller:function($scope) { // 定义了三个公共方法,供外部指令访问
$scope.abilities=[];
this.addStrength=function() {
$scope.abilities.push("strength");
};
this.addSpeed=function() {
scope.abilities.push("speed");
};
this.addLight=function() {
$scope.abilities.push("light");
};
},
link:function(scope, element, attrs) {
element.bind("mouseenter",function() {
console.log(scope.abilities);
});
}
}
});
myModule.directive("speed",function() {
return{
require:'^superman',
link:function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addSpeed();
}
}
});
最后讲一下scope,它的作用是创建一个新的作用域。
它的值有3种:
false,true,{}
当它等于false的时候,我们在哪个controller中使用这个指令,它就会继承这个ctrl的值:
也就是说它们之间不会隔离,哪一边的值发生变化,另外一边也会跟着变:
myApp.directive('hello',function() {
return{
restrict:'AE',
scope:false,
template:'<div><input type="text" ng-model="userName"/> {{ userName }} </div>',
replace:true
}
})
当它等于true的时候,指令就会继承控制器的值,改变控制器里的值,指令里面的值也会随之变化。
但是改变指令的值,控制器的却不会变,这就是产生了继承隔离。
当它等于{}时,就不会继承任何值,完全产生隔离。
然后,给大家布置一个作业,关于scope里的属性绑定策略,有:
@attr单向文本绑定
=attr双向绑定
&调用符作用域中的函数
自己去了解它们是怎么使用的。
以上就是上节课的内容解析啦