原文地址: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。

angular 指令详解一compile与link的更多相关文章

  1. ios – 将视频分享到Facebook

    我正在编写一个简单的测试应用程序,用于将视频从iOS上传到Facebook.由于FacebookSDK的所有文档都在Objective-C中,因此我发现很难在线找到有关如何使用Swift执行此操作的示例/教程.到目前为止我有这个在我的UI上放置一个共享按钮,但它看起来已禁用,从我读到的这是因为没有内容设置,但我看不出这是怎么可能的.我的getVideoURL()函数返回一个NSURL,它肯定包含视

  2. ios – 错误域= NSURLErrorDomain代码= -1003“找不到具有指定主机名的服务器

    当我尝试在设备上运行应用程序时出现此错误.当我在模拟器上运行它并且post请求正常工作时,我没有收到此错误.这是我的代码片段:任何帮助表示赞赏.谢谢解决方法此错误通常会提示DNS相关问题.检查设备上的DNS设置并确认您可以使用Safari或其他浏览器浏览互联网.如果有一个url,你可以在同一台服务器上获取,尝试直接在设备上的Safari中访问它.

  3. ios – Xcode Bot:如何在post触发器脚本上获得.ipa路径?

    我正在使用机器人来存档iOS应用程序,我需要获取.ipa产品路径才能将其发布到我们的分发系统中.机器人设置:并使用脚本打印所有env变量,其中不包含ipa文件的路径.此外,一些变量指向不存在的目录,即:XCS_OUTPUT_DIR这里的env变量输出:除此之外,我还能够确认.ipa文件是在另一个文件夹中创建的(/IntegrationAssets//

  4. iOS 10 Safari问题在DOM中不再包含元素

    使用此链接,您可以重现该错误.https://jsfiddle.net/pw7e2j3q/如果您点击元素并从dom中删除它,然后单击链接测试.你应该看到旧的元素弹出选择.是否有一些黑客来解决这个问题?解决方法我能够重现这个问题.问题是,每当您尝试删除其更改事件上的选择框时,iOS10都无法正确解除对选择框的绑定.要解决此问题,您需要将代码更改事件代码放在具有一些超时

  5. ios – 使用CocoaPods post install hook将自定义路径添加到HEADER_SEARCH_PATHS

    解决方法在Podfile中定义一个方法:然后在post_install中调用该方法:

  6. iOS7 Safari中的全屏模式

    我正在使用SenchaTouch开发移动网站.在iOS7Safari中,我无法创建顶级地址栏和下面的工具栏消失了.Sencha过去常常处理iOS6,但iOS7最近的一些变化导致了这个问题.http://java.dzone.com/articles/safari-ios-7-and-html5我阅读了上面的链接&对于HTML5游戏而言,这似乎也是一个问题.一些其他应用程序.适用于iOS6的旧win

  7. ios – 如何使用新的Apple Swift语言发布JSON

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  8. ios – Cordova 3.7在每个本机通话中复制iframe

    由于我已升级到Cordova3.7,每个本地调用都将一个新的iframe附加到DOM,如下所示.为了排除我现有的代码影响cordova的可能性,我尝试使用cordovaCLI创建一个新的代码,添加控制台插件,并在设备控制台中的setInterval循环中调用console.log().因此,我在DOM中收到了大量的iframe.我在iPad3,iOS7上尝试过使用xCode6构建应用程序.有没有人遇到这个问题?

  9. ios – POST请求使用application / x-www-form-urlencoded

    后端开发人员在POST请求中给出了这些说明:>路线:{url}/{app_name/{controller}/{action}控制器和动作应该是小帽子.>API测试链接:http:****************>请求应该使用POST方法.>参数应通过请求内容体(FormUrlEncodedContent)传递.>参数应该是json格式.>参数是关键的.在协议中没有经验5,我搜索并结束了我的代码.

  10. 从iOS应用程序发送帖子到PHP脚本不工作…简单的解决方案就像

    我之前已经做了好几次了但是由于某些原因我无法通过这个帖子…我尝试了设置为_POST且没有的变量的PHP脚本……当它们未设置为发布时它工作精细.这是我的iOS代码:这里是PHP的一大块,POST变量不在正确的位置?我想这对于更有经验的开发人员来说是一个相当简单的答案,感谢您的帮助!解决方法$_POST是一个数组,而不是一个函数.您需要使用方括号来访问数组索引:

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部