共计 4485 个字符,预计需要花费 12 分钟才能阅读完成。
四、AngularJS的生命周期
在AngularJS应用启动前,它们以HTML文本形式保存在文本编辑器中。应用启动后会进行编译和链接,作用域会同HTML进行绑定,应用可以对用户在HTML中进行的操作进行实时响应。
1.编译阶段:在AngularJS会遍历整个HTML文档并根据JS中指令定义来处理页面上声明的指令。一旦对指令和其中的子模板进行遍历或编译,编译后的模板会返回一个叫模板函数的函数。
只有属于最高优先级指令的模板会被解析并添加到模板树中。这里有一个建议,就是将包含模板的指令和添加行为的指令分离开来。
2.链接阶段:一个指令的表现一旦编译完成,马上就可以通过编译函数对其进行访问,编译函数的签名包含有访问指令声明所在的元素(element)及该元素其他属性(attrs)的方法。模板函数将被传递给编译后的DOM树中每个指令定义规则中指定的链接函数。
由于每个指令都可以有自己的模板和编译函数,每个模板返回的也都是自己的模板函数。链条顶部的指令会将内部子指令的模板合并在一起并成为一个模板函数并返回,但在树的内部,只能通过模板函数访问其所处的分支。
下面具体讲解compile和Link
- compile[Object or Function]:compile 选项本身并不会被频繁使用,但是link函数则会被经常使用。本质上,当我们设置了link选项,实际上是创建了一个postLink() 链接函数,以便compile() 函数可以定义链接函数。通常情况下,如果设置了compile函数,说明我们希望在指令和实时数据被放到DOM中之前进行DOM操作,在这个函数中进行诸 如添加和删除节点等DOM操作是安全的。
compile: function (element, attrs, transcludeFn) { var tpEl = angular.element("<div><h2></h2></div>"); var h2 = tpEl.find('h2'); h2.attr('type', attrs.type); h2.attr('ng-model', attrs.ngModel); h2.val("hello compile"); element.replaceWith(tpEl); return function (scope, ele, attr) { //链接函数 } },
compile和link选项是互斥的。如果同时设置了这两个选项,那么会把compile所返回的函数当作链接函数,而link选项本身则会被忽略。
如果模板被克隆过,那么模板实例和链接实例可能不是同一个对象。因此在编译函数的内部我们只能转换那些可以被安全操作的克隆DOM节点。不要进行DOM事件监听的注册:这个操作应该在链接函数中完成。
编译函数负责对模板DOM进行转换。链接函数负责将作用域和DOM进行链接。 在作用域同DOM链接之前可以手动操作DOM。在实践中,编写自定义指令时这种操作是非常罕见的,但有几个内置指令提供了这样的功能。
- link:链接函数是可选的。用link函数创建可以操作的DOM指令。如果定义了编译函数,它会返回链接函数,因此当两个函数都定义时,编译函数会重载链接函数。如果我们的指令很简单,并且不需要额外的设置,可以从工厂函数(回调函数)返回一个函数来代替对象。如果这样做了,这个函数就是链接函数。下面两种在功能上是一样的。
/*第一种*/ angular.module('my-app', []) .directive('myDirective', function () { return { pre: function (tElement, tAttrs, transclude) { //在子元素被链接之前执行 //在这里进行DOM转换不安全 //之前调用link函数将无法定位要链接的元素 }, post: function (scope, iElement, iAttrs, controller) { //在子元素被链接之前执行 //如果在这里省略掉编译选项 //在这里执行DOM转换和链接函数一样安全吗? } } }); /*第二种*/ angular.module('my-app', []) .directive('myDirective', function () { return { link: function (scope, ele, attrs) { return { pre: function (tElement, tAttrs, transclude) { //在子元素被链接之前执行 //在这里进行DOM转换不安全 //之前调用link函数将无法定位要链接的元素 }, post: function (scope, iElement, iAttrs, controller) { //在子元素被链接之前执行 //如果在这里省略掉编译选项 //在这里执行DOM转换和链接函数一样安全吗? } } } } });
当定义了编译函数来取代链接函数时,我们提供给返回对象的第二个方法就是链接函数。也就是post:function(){}。链接函数的作用:它会在模板编译并同作用域进行链接后调用,它负责设置事件监听器,监视数据变化和实时的操作DOM。链接函数签名如下:
link: function (scope, element, attris) {}
如果指令中定义了require选项,链接函数签名会有第四个参数,代表控制器或者所依赖的指令控制器。如果require选项提供了一个指令数组,第四个参数会是一个有每个指令所对应的控制器组成的数组。
require: '^?accordion', link: function (scope, element, attris, accordionController) {}
post函数中的参数:
♠.scope:指令用来在其内部注册监听器的作用域。
♣.iElement:代表实例元素,指使用此指令的元素。在postLink函数中我们应该只操作此元素的子元素,因为子元素已经被链接过了。
♥. iAttrs:代表实例属性,是一个由定义在元素上属性组成的标准化列表,可以在所有指令的链接函数间共享。会以JS对象的形式进行传递。
♦. controller:controller参数指向require选项定义的控制器。控制器在所有的指令间共享,因此指令可以将控制器当做公共API。
五、ng-model
ng-model是一个用法特殊的指令,它提供跟底层的API来处理控制器的数据。
ng-model控制器会随ngModel被一直注入到指令中,为了访问ngModelController必须使用require设置
app.directive('myDirective', function () {
return {
require: '?ngModel',
link: function (scope, ele, attrs, ngModel) {
if (!ngModel)return;
//......
}
}
})
注意:这个指令没有独立作用域。如果给这个指令设置独立作用域,将导致内部ngModel无法更新尾部ngModel的对应值:AngularJS会在本地作用域以外查询值。
- 为了设置作用域中的视图值,需要调用 ngModel.$setViewValue() 函数。$setViewValue()接受一个参数,是字符型,其表示我们想要赋值给ngModel实例的值。 这个方法会更新控制器上本地的$viewValue,然后将值传递给每一个$parser函数。
app.directive('myDirective', function () { return { require: '?ngModel', link: function (scope, ele, attrs, ngModel) { if (!ngModel)return; $(function () { ele.datepicker({ onSelect: function (date) { //设置视图和调用apply scope.$apply(function () { ngModel.$setViewValue(date); }) } }) }) } } })
$setViewValue() 方法适合于在自定义指令中监听自定义事件(比如使用具有回调函数的jQuery插件),我们会希望在回调时设置$viewValue并执行digest循环。
- $render方法可以定义视图具体的渲染方式。自定义渲染:在控制器中定义$render方法可以定义视图具体的渲染方式。这个方法会在$parser流水线完成后调用。这个方法需要谨慎使用,因为会破坏Angular的标准工作方式。
app.directive('myDirective', function () { return { require: '?ngModel', link: function (scope, ele, attrs, ngModel) { if (!ngModel)return; ngModel.$render = function () { element.html(ngModel.$viewValue() || 'None') } } } })
- 属性:ngModelController中下面的属性可以用来检查修改视图。
◊. $viewValue
$viewValue属性保存着更新视图所需的实际字符串。
◊. $modelValue
$modelValue由数据模型持有。 $modelValue和$viewValue可能是不同的,取决于$parser流水线是否对其进行了操作。
◊. $parsers
$parsers 的值是一个由函数组成的数组,其中的函数会以流水线的形式被逐一调用。ngModel 从DOM中读取的值会被传入$parsers中的函数,并依次被其中的解析器处理。
◊. $formatters
$formatters的值是一个由函数组成的数组,其中的函数会以流水线的形式在数据模型的值发生变化时被逐一调用。它和$parser流水线互不影响,用来对值进行格式化和转换,以便在绑定了这个值的控件中显示。
◊. $viewChangeListeners
$viewChangeListeners 的值是一个由函数组成的数组,其中的函数会以流水线的形式在视图中的值发生变化时被逐一调用。通过$viewChangeListeners,可以在无需 使用$watch的情况下实现类似的行为。由于返回值会被忽略,因此这些函数不需要返回值。
◊. $error
$error对象中保存着没有通过验证的验证器名称以及对应的错误信息。
◊. $pristine
$pristine的值是布尔型的,可以告诉我们用户是否对控件进行了修改。
◊. $dirty
$dirty的值和$pristine相反,可以告诉我们用户是否和控件进行过交互。
◊. $valid
$valid值可以告诉我们当前的控件中是否有错误。当有错误时值为false, 没有错误时值为true。
◊. $invalid
$invalid值可以告诉我们当前控件中是否存在至少一个错误,它的值和$valid相反。
六、自定义验证(见下)
AngularJS表单验证