angular 指令详解一compile与link

原文地址:https://987.tw/2014/09/03/ang...

AngularJS directives是令人惊艳的。它允许你创造高度语意且可重复利用的元件。在某种意义上你可以认为它是极致的web components先驱者。

有许多很棒的文章,甚至是书籍,在教导你如何撰写自己的directives。相较之下,只有少许的资讯谈到有关compile及link函式的差异,更不用说有关pre-link及post-link函式差别。

大多数的导引只有简单地提到compile函式主要由AngularJS在内部使用,并且建议你只要用link函式,应该能够涵盖大多数的使用案例。

这是十分不幸的,因为了解这些函式其中的差异能够提升你的能力,更加的了解AngularJS内部运作,并且订制出更好的directives。

所以跟着我,文章最后你将会正确地了解这些函式是什么以及什么时候该使用它们。

本文假设你已经了解什么是AngularJS directive。如果不了解,我强烈建议你先阅读AngularJS开发者指南的directive章节。

AngularJS如何处理directives?

在我们开始之前,让我打断一下,先了解AngularJS是如何处理directives。

(1)当浏览器渲染(render)页面时,基本地读取HTML标签,建立一个DOM,当DOM准备好时,广播(broadcast)出一个事件(event)。

(2)当你使用<script></script>标签引入你的AngularJS程式码到页面时,AngularJS会监听(listen)该事件,一旦该事件发出,AngularJS便会开始走遍(traversing)DOM,寻找所有元素(element)中的属性(attribute )是否具有ng-app。

(3)一旦找到具有该属性的元素,AngularJS便以该元素作为起始点,进行DOM 处理。如果在html元素的属性内设定ng-app,那么AngularJS将会从html元素开始处理 DOM。

(4)从起始点开始,AngularJS递回地调查所有子元素,从你的AngularJS应用程式中所定义的directives中去找寻相对应的样式。

AngularJS如何处理元素,取决于实际定义directive的物件(译注:directive definition object)。你可以预先定义compile函式或link函式,两者可同时存在。或者选择性的定义pre-link及post-link这两个函式来取代link函式,

所以,这些函式有什么差异?为什么及何时该使用这些函式?
坚持下去...

程式码

为了解释这些差异,我会用程式码来做示范,希望能够更容易的理解。

考虑下列HTML标签:

<level-one>  
    <level-two>
        <level-three>
            Hello {{name}}         
        </level-three>
    </level-two>
</level-one>

以及下列JavaScript:

var app = angular.module('plunker',[]);

function createDirective(name){  
  return function(){
    return {
      restrict: 'E',compile: function(tElem,tAttrs){
        console.log(name + ': compile');
        return {
          pre: function(scope,iElem,iAttrs){
            console.log(name + ': pre link');
          },post: function(scope,iAttrs){
            console.log(name + ': post link');
          }
        }
      }
    }
  }
}

app.directive('levelOne',createDirective('levelOne'));  
app.directive('levelTwo',createDirective('levelTwo'));  
app.directive('levelThree',createDirective('levelThree'));

目标很简单:让AngularJS处理巢状的三个directives,而每个directive都有自己的compile、pre-link及post-link函式,各函式输出讯息至console,我们可以借此作为识别。

这让我们可以一睹AngularJS是如何在背后处理这些directives。

输出结果:
这是console输出结果的截图:

如果你要自己试试看,开启这个plnkr链接,并在打开浏览器的Console。

开始分析

第一件要注意的是,函式呼叫的顺序:

// COMPILE階段
// levelOne:    compile函式已呼叫
// levelTwo:    compile函式已呼叫
// levelThree:  compile函式已呼叫

// PRE-LINK階段
// levelOne:    pre link函式已呼叫
// levelTwo:    pre link函式已呼叫
// levelThree:  pre link函式已呼叫

// POST-LINK階段 (注意到反向順序)
// levelThree:  post link函式已呼叫
// levelTwo:    post link函式已呼叫
// levelOne:    post link函式已呼叫

这个清除地展示AngularJS一开始compile所有directives,compile阶段尚未连结scope,link阶段也分成pre-link及post-link阶段。

注意到呼叫compile及pre-link的顺序是一致的,但是呼叫post-link的顺序则是相反的。

所以在这里我们可以已经清处的辨别这几个不同的阶段,但是compile与pre-link又有什么不同呢?它们也有同样的顺序,为什么要将它们分开?

文件物件模型(DOM)

稍微深入一些,进一步修改JavaScript,呼叫时一并输出元素的DOM:

var app = angular.module('plunker',tAttrs){
        console.log(name + ': compile => ' + tElem.html());
        return {
          pre: function(scope,iAttrs){
            console.log(name + ': pre link => ' + iElem.html());
          },iAttrs){
            console.log(name + ': post link => ' + iElem.html());
          }
        }
      }
    }
  }
}

app.directive('levelOne',createDirective('levelThree'));

注意到console.log额外的输出讯息。没有任何更动,仍然是最原始的标签。

这应该能让我们更详细的了解函式的来龙去脉。

让我们再次执行程式码。
输出结果:


观察

输出DOM结果透漏某些有趣的东西:compile与pre-link阶段的DOM不一样。

所以,发生什么事?

Compile
我们已经学习到当AngularJS侦测DOM准备好时,会进行DOM处理。

所以,当AngularJS开始走遍DOM,它遇见<level-one>元素,并从它的directive定义(directive definition)中得知需要执行某些行为。

因为在levelOne的directive定义中,定义了compile函式,所以会呼叫此函式并带入元素DOM作为函式的参数。

如果你靠近一点你会看到,在这个时机点,元素的DOM仍然是最初刚开始的DOM,系由浏览器根据原始HTML标签所创造出来的DOM。

在AngularJS里,经常用样板元素(template element)来提到原始的DOM,因此基于这个原因我个人用tElem来作为compile函式内的参数名称,用来表示样板元素。

当levelOne的compile执行之后,AngularJS更深入且递回地走入DOM,对<level-two>及<level-three>重复相同的编译步骤。

Post-link
在我们深入pre-link函式前,让我们先看一下post-link函式。

如果你产生的directive只有link函式,AngularJS会将它当作是post-link函式。因为这个原因我们要在先讨论它。

一旦AngularJS走到DOM的最后(底)并执行完所有compile函式,它会往回(上)走并且执行所有关联的post-link函式。

现在DOM是用反方向在走遍,因此呼叫post-link函式是相反的顺序。所以前几分钟看到相反顺序觉得很奇怪,现在开始觉得合理了。

这相反顺序保证所有的子元素post-link会先被执行,接着才是父元素的post-link。

所以,当<level-one>的post-link函式被呼叫,我们可以保证<level-two>及<level-three>的post-link已经被呼叫过。

这就是为什么它被认为是用来加入你的directive逻辑最安全以及预设的地方。

那元素的DOM呢?为什么在这里它们是不同的?

当AngularJS呼叫了directive的compile函式之后,它会产生一个样板元素(template element)的实例元素(instance element)(通常称之为消灭实体),并且提供一个scope给这个实体。这个scope可以是全新的scope、继承的子scope或孤立的scope,取决于相对应directive定义物件内scope属性设定。

所以,到连结阶段的时候,实例元素及scope已经可以开始使用,并且AngularJS会将它作为函式参数传递到post-link函式。

Pre-link
当撰写post-link函式时,你可以保证所有子元素的post-link函式已经执行过。

在大多数的案例中,这个非常合理,因此它也是最常用来撰写directive程式码的地方。

然而,AngularJS提供了一个附加的钩子,称之为pre-link函式,程式码会先被执行,抢先在所有子元素的post-link被执行之前。

再次强调:

pre-link函式保证所有子元素的post-link被执行前,先执行pre-link函式,并且是在实体元素中执行。

所以当相反顺序的呼叫post-link十分合理,那原始顺序的呼叫pre-link也是十分合理。

回顾

如果我们回顾之前原始输出,我们可以清晰的辨认出发生什么事:

// 这里的元素仍然是最原始的样板标签

// COMPILE 阶段
// levelOne:    原始DOM中呼叫compile函式
// levelTwo:    原始DOM中呼叫compile函式
// levelThree:  原始DOM中呼叫compile函式

// 从这里开始,元素已经实例化且綁定了SCOPE
// (例:NG-REPEAT 已有多重实例)

// PRE-LINK 階段
// levelOne:    元素实例中呼叫pre link函式
// levelTwo:    元素实例中呼叫pre link函式
// levelThree:  元素实例中呼叫pre link函式

// POST-LINK 阶段 (注意到順序相反)
// levelThree:  元素实例中呼叫post link函式
// levelTwo:    元素实例中呼叫post link函式
// levelOne:    元素实例中呼叫post link函式

摘要

回顾中我们可以描述不同的函式及使用案例如下:

Compile函式
在AngularJS产生实例及scope之前,使用compile函式来更动原始DOM(样板元素)。

它可以有多个元素实例,但只会有一个样板元素。ng-repeat就是这个案例的一个完美范例。它让compile成为最佳的地方来进行更动DOM,之后才会套用所有实例,因为只会执行一次,所以当你要消灭很多实例时,可以获得很多效率上的提升。

样板的元素及属性都会作为参数传递到compile函式,但不会有scope传入,因为还没准备好:

/**
* Compile函式
* 
* @param tElem - 样板元素
* @param tAttrs - 样板元素的属性
*/
function(tElem,tAttrs){

    // ...

};

Pre-link函式
当AngularJS已经compile子元素,在任何子元素的post-link执行之前,使用pre-link函式来实作逻辑。

Scope、实例元素及实例属性都会作为参数传递到pre-link函式:

/**
* Pre-link函式
* 
* @param scope - 關連於此實例的scope
* @param iElem - 實例元素
* @param iAttrs - 實例元素的屬性
*/
function(scope,iAttrs){

    // ...

};

Post-link函式
使用post-link来执行逻辑,该逻辑知道所有子元素已经编译,并且所有子元素的pre-link及post-link都已经被执行。

基于这个理由,post-link认为是最安全及预设的地方来撰写你的程式码。

Scope、实例元素及实例属性都会作为参数传递到post-link函式:

/**
* Post-link函式
*
  • @param scope - 關連於此實例的scope

  • @param iElem - 實例元素

  • @param iAttrs - 實例元素的屬性
    */

function(scope,iAttrs){

// ...

};

结论

到目前为止,但愿你有清楚的理解关于compile、pre-link及post-link之间的差异。

如果没有且你很认真的在做AngularJS开发,我强烈建议你再读一次文章,直到你有稳固的抓住其运作原理。

了解这个重要的概念将会让你更容易理解原生的AngularJS directive是如何运作,并且如何最佳化你订制的directives。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


ANGULAR.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中文文档更容易理解,前提是你的英语基础还可以。英文文档对于知识点讲述简明扼要,通俗易懂,而有些中文文档读起来特别费力,基础差、底子薄的有可能一会就会被绕晕了,最起码英文文档中的代码与中文文档中的代码是一致的,但知识点讲述实在是差距太大。Angular.jshasapowerfuldire
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上。目前,它支持6种图表类型(折线图,条形图,雷达图,饼图,柱状图和极地区域区)。而且,这是一个独立的包,不依赖第三方 JavaScript 库,小于 5KB。   其中用到的软件:   Chart.js框架,版本1.0.2,一
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,结局方案为更换jquery、angularjs、IE的版本。 1.首先尝试更换jquery版本为1.7.2 jquery-1.9.1.js-->jquery-1.7.2.js--> jquery2.1.4.js 无效 2.尝试更换IE版本IE8 IE11-
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:http://ngmodules.org/modules/angularjs-dropdown-multiselecthttp://dotansimha.github.io/angularjs-dropdown-multiselect/#/AngularJSDropdownMultiselectThisdire
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目需求,需要在首页搜索框中添加语音输入功能,考虑到科大讯飞语音业务的强大能力,遂决定使用科大讯飞语音输入第三方服务。软件首页截图如下所示: 涉及的源代码如下所示: //语音识别$rootScope.startRecognize = function() {var speech;
Angular数据更新不及时问题探讨前言 在修复控制角标正确变化过程中,发觉前端代码组织层次出现了严重问题。传递和共享数据时自己使用的是rootScope,为此造成了全局变量空间的污染。根据《AngularJs深度剖析与最佳实践》,如果两个控制器的协作存在大量的数据共享和交互可以利用Factory等服务的“单例”特性为它们注入一个共享对象来传递数据。而自己在使用rootScope
HTML:让表单、文本框只读,不可编辑的方法有时候,我们希望表单中的文本框是只读的,让用户不能修改其中的信息,如使中国">的内容,"中国"两个字不可以修改。实现的方式归纳一下,有如下几种。方法1:onfocus=this.blur()中国"onfocus=this.blur()>方法2:readonly中国"readonly>中国"readonly="tru
在AngularJS应用中实现微信认证授权遇到的坑前言 项目开发过程中,移动端新近增加了一个功能“微信授权登录”,由于自己不是负责移动端开发的,但最后他人负责的部分未达到预期效果。不能准确实现微信授权登录。最后还得靠自己做进一步的优化工作,谁让自己是负责人呢?原来负责人就是负责最后把所有的BUG解决掉。 首先,熟悉一下微信授权部分的源代码,如下所示:
AngularJS实现二维码信息的集成思路需求 实现生成的二维码包含订单详情信息。思路获取的内容数据如下: 现在可以获取到第一级数据,第二级数据data获取不到。利用第一级数据的获取方法获取不到第二级数据。for(i in data){alert(i); //获得属性 if(typeof(data[i]) == "o
Cookie'data'possiblynotsetoroverflowedbecauseitwastoolarge(5287>4096bytes)!故事起源 项目开发过程中遇到以上问题,刚开始以为只是个警告,没太在意。后来发现直接影响到了程序的执行效果。果断寻找解决方法。问题分析 根据Chrome浏览器信息定位,显示以下代码存在错误:
AngularJS控制器controller之间如何通信angular控制器通信的方式有三种:1,利用作用域继承的方式。即子控制器继承父控制器中的内容2,基于事件的方式。即$on,$emit,$boardcast这三种方式3,服务方式。写一个服务的单例然后通过注入来使用利用作用域的继承方式由于作用域的继承是基于js的原型继承方式,所以这里分为两种情况,当作用域上面的值
AngularJS路由问题解决遇到了一个棘手的问题:点击优惠详情时总是跳转到药店详情页面中去。再加一层地址解决了,但是后来发现问题还是来了:Couldnotresolve'yhDtlMaintain/yhdetail'fromstate'yhMaintain'药店详情http://192.168.1.118:8088/lmapp/index.html#/0优惠券详情
书海拾贝之特殊的ng-src和ng-href在说明这两个指令的特殊之前,需要先了解一下ng的启动及执行过程,如下:1)浏览器加载静态HTML文件并解析为DOM;2)浏览器加载angular.js文件;3)angular监听DOMContentLoaded事件,监听到时开始启动;4)angular寻找ng-app指令,确定作用范围;
angularjs实现页面跳转并进行参数传递Angular页面传参有多种办法,我在此列举4种最常见的:1.基于ui-router的页面跳转传参(1)在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个producers,点击其中一个目标,页面跳转到对应的producer页,同时将producerId
AngularJS实现表格数据的编辑,更新和删除效果实现首先,我们先建立一些数据,当然你可以从你任何地方读出你的数据var app = angular.module('plunker', ['ui.bootstrap']);app.controller('MainCtrl', function($scope) { $scope.name = 'World'; $sc
ANGULAR三宗罪之版本陷阱      坑!碰到个大坑,前面由于绑定日期时将angular版本换为angular-1.3.0-beta.1时,后来午睡后,登录系统,发现无论如何都登陆不进去了,经过调试,发现数据视图已经无法实现双向绑定了。自己还以为又碰到了“僵尸程序”了呢,对比药店端的程序发现并没有什么不同之处。后来自己经过一番思索才隐约感觉到是不是angular的版本造成的,将版本换为之前
JS实现分页操作前言 项目开发过程中,进行查询操作时有可能会检索出大量的满足条件的查询结果。在一页中显示全部查询结果会降低用户的体验感,故需要实现分页显示效果。受前面“JS实现时间选择插件”的启发,自己首先需要查看一下HTML5能否实现此效果。 整了半天,不管是用纯CSS3也好,还是用tmpagination.js还是bootstrap组件也好,到最后自己静下心来理
浏览器兼容性解决之道前言 浏览器兼容性一直是前端开发中不得不面对的一个问题。而最突出的就是IE。对绝大多数公司来说,兼容IE6的性价比已经很低,而IE7则几乎已经绝迹。所以,常见的兼容性下限是IE8。这也正是Angular1.2x的兼容性目标,Angular团队声明:Angular的持续集成服务器会在IE8下运行所有的测试。但这些测试不会运行在IE7及以下版本,它们也不会保证An
JS利用正则表达式校验手机号绪 由于项目需求,需要在前端实现手机号码的校验。当然了,对于基本的格式校验应该放在客户端进行,而不需要再将待校验的手机号发送至服务端,在服务端完成校验,然后将校验结果返回给客户端,客户端根据返回的结果再进行进一步的处理。如此反而复杂化了处理过程。 其实,处于安全考虑,应该在服务端进行二次校验。以下为在客户端的JS中校验手机号码格式
基于项目实例解析ng启动加载过程前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理。回过头来总结一下angular的启动过程。 下面以实际项目为例进行简要讲解。1.载入ng库2.等待,直到DOM树构造完毕。3.发现ng-app,自动进入启动引导阶段。4.根据ng-app名称找到相应的路由。5.加载默认地址。6.Js顺序执行,加载相应模版页sys_tpls/