指令的编译与链接

7/8/2015

上节中定义了指令的一系列操作,定义中的还有编译compile和link没有介绍。在介绍这部分之前,我们需要先了解一下Angular是如何工作的。只有了解了整个的生命周期,才能更好的理解这两个概念。

####生命周期管理

假如我们有一个树形的DOM结构如下图所示,当Angular App 获得DOM文档的控制权之后,开始遍历整个文档,对所有其中定义的指令进行一次遍历。自上而下进行。并根据指令的定义来处理操作。

基本上对于Angular处理过程可分为三部分:

  1. 初始化阶段
  2. 编译阶段
  3. 链接阶段

初始化阶段

初始化阶段只会在指令第一次被处理时运行,当该指令重复出现时,不会重复此操作

编译阶段

编译阶段主要是操作指令的模板,并且编译阶段没有进行DOM数据的绑定,所以操作DOM不会有太多性能的花销。由于自定义的很多指令其实是不进行模板操作的,所以在指令定义上不是经常使用他的compile参数。

> 当一个指令中定义了模板,而模板又包含了另一个指令,Angular则遍历整个的指令集合但是注意 对于一个元素来讲 只能有一个定义模板的指令生效(优先级最高)。所以设计元素指令的时候,一定注意不要混在使用模板和行为指令。

链接阶段

在这个阶段,Angular绑定事件监听到html的模板并且绑定一个作用域到指令。如果指令需要有能力控制其作用域,使用link函数可以达到此效果。

image

上图可以看出,实际处理过程中 编译结束后及进入链接阶段包含有prelink和postlink两部分。这两个部分将通过一些实例来讲解。

####编译和链接实例

实例一 调用顺序

首先来看以下一个自定义的例子

<!--javascript-->

    var directives=["myDirective1","myDirective2","myDirective3","myDirective4"];
    directives.forEach(function(dir){
        app.directive(dir,function(){
            //初始化阶段
            console.log(dir+"init")
            return{
                compile:function(element,attrs){
                    console.log(dir+"compile");
                    return{
                        pre:function(scope,element,attrs){
                            console.log(dir+"pre-link");
                        },
                        post:function(scope,element,attrs){
                            console.log(dir+"post-link");
                        }
                    };
                }
            };
        });
    });


    <!--html-->

    <div my-directive1>
        <div my-directive2 my-directive3>
            <div my-directive4></div>
        </div>
    </div>

这里我们定义了一个实例方法来查看运行的三个阶段的执行顺序,运行结果如下:

compile_and_link.html:36 myDirective1init
compile_and_link.html:39 myDirective1compile
compile_and_link.html:36 myDirective2init
compile_and_link.html:36 myDirective3init
compile_and_link.html:39 myDirective2compile
compile_and_link.html:39 myDirective3compile
compile_and_link.html:36 myDirective4init
compile_and_link.html:39 myDirective4compile

//编译阶段结束

//运行深度优先算法来调用link函数

compile_and_link.html:42 myDirective1pre-link
compile_and_link.html:42 myDirective2pre-link
compile_and_link.html:42 myDirective3pre-link
compile_and_link.html:42 myDirective4pre-link
compile_and_link.html:45 myDirective4post-link
compile_and_link.html:45 myDirective3post-link
compile_and_link.html:45 myDirective2post-link
compile_and_link.html:45 myDirective1post-link

实例二 调用时机

在了解了调用的顺序之后,我们来思考以下什么情况下应该用compile什么情况下用link. 首先我们已经了解到compile可以在不操作scope情况下修改DOM元素,而且并非直接操作DOM。所以对于那些想通过指令直接修改DOM的操作可以考虑使用compile参数。而对于需要绑定事件和需要操作scope的则应该使用link函数

//调用说明
function compile(tElement, tAttrs, transclude) { ... }

使用compile,下面的例子修改模板中的内容并增加DOM属性.增加DOM元素或者修改DOM元素都可以在compile阶段完成。

 app.directive("mySpanShow",function(){
        return{
            restrict:'EA',
            template:'<div><span><input type="text"></span><div>',
            compile:function(tele,tattr){
                var span=tele.find('span').first();
                span.attr('ng-show',tattr.model+".visible."+tattr.name);
                return {
                    pre:function(){},
                    post:function(){}
                }
            }
        }
  });

  <my-span-show model="state"  name="address"/>

  //结果显示
  <my-span-show model="state" name="address">
     <div>
        <span ng-show="state.visible.address" class="ng-hide">
           <input type="text">
        </span>
     </div>
  </my-span-show> 

使用link,下面的例子最终的输出已经开始变得面目全非

   app.controller('DirectiveController',function($scope){
     $scope.alert=function(){
         console.log("hi,i am pre-link");
     }
     $scope.alertPost=function(){
         console.log("hi,i am post-link");
     }
   }); 

   app.directive("myLinkDir",function(){
        return{
            restrict:'EA',
            controller:"DirectiveController",
            template:'<div><span><input type="text"></span><div>',
            compile:function(tele,tattr){
                return {
                    pre:function(scope,iElement,iAttrs){
                        scope.alert();
                        iElement.append("Pre is running");
                    },
                    post:function(scope,iElement,iAttrs){
                        scope.alertPost();
                        iElement.html("post is running");
                    }
                }
            }
        }
    });

最终原本需要输出一个模板函数,最终却变成了打印两句话,输出经历了从模板 变为Element.append("Pre is running"); 最终完全被替换掉过程。

实例三 实际使用

该实例引用自ng-book一书。用于进行用户名唯一性确认,实际使用可定义不同的策略,不一定非要每次更新都去确认输入是否唯一。也可通过调用在link上的绑定失去焦点的事件来发起查询。

angular.module('validationExample', [])
         .directive('ensureUnique',function($http) {
             return {

                 //ng-model的指令 通过require引入 link最后参数c 代表了控制器指向ngModel

                 require: 'ngModel',
                 link: function(scope, ele, attrs, c) {
                     scope.$watch(attrs.ngModel, function() {
                         $http({
                             method: 'POST',
                             url: '/api/check/' + attrs.ensureUnique,
                             data: {field: attrs.ensureUnique, valud:scope.ngModel
                         }).success(function(data,status,headers,cfg) {
                             c.$setValidity('unique', data.isUnique);
                         }).error(function(data,status,headers,cfg) {
                             c.$setValidity('unique', false);
                 });
             });
        } 
      }; 
   });

   //调用的时候仅仅需要在对应的输入部分加入以下代码就可以:

<input type="text"
           placeholder="Desired username"
             name="username"  
             ng-model="signup.username"
          ng-minlength="3"
          ng-maxlength="20"
          ensure-unique="username" required />

前端技术 Angular 页面已被访问1789次

发表评论