Angular 指令 - 何时以及如何使用编译、控制器、预链接和后链接 [关闭]

Posted

技术标签:

【中文标题】Angular 指令 - 何时以及如何使用编译、控制器、预链接和后链接 [关闭]【英文标题】:Angular directives - when and how to use compile, controller, pre-link and post-link [closed] 【发布时间】:2014-08-28 04:50:33 【问题描述】:

在编写 Angular 指令时,可以使用以下任何函数来操作声明该指令的元素的 DOM 行为、内容和外观:

编译 控制器 预链接 链接后

对于应该使用哪个功能似乎有些混乱。这个问题涵盖:

指令基础

How to declare the various functions? What is the difference between a source template and an instance template? In which order the directive functions are executed? What else happens between these function calls?

功能性质,注意事项

Compile Controller Pre-link Post-link

相关问题:

Directive: link vs compile vs controller。 Difference between the 'controller', 'link' and 'compile' functions when defining an angular.js directive。 What is the difference between compile and link function in angularjs。 Difference between the pre-compile and post-compile element in AngularJS directives?。 Angular JS Directive - Template, compile or link?。 post link vs pre link in Angular js directives。

【问题讨论】:

什么什么? @Ian See:Operator overloading。本质上,这是为社区 wiki 设计的。太多相关问题的答案都是片面的,没有提供完整的情况。 这是很棒的内容,但我们要求这里的所有内容都保持在问答格式中。也许您想将其分解为多个离散的问题,然后从标签 wiki 链接到它们? 尽管这篇文章是题外话并且是博客形式的,但它在提供对 Angular 指令的深入解释方面最有用。请不要删除这篇文章,管理员! 老实说,我什至不关心原始文档。 *** 帖子或博客通常能让我在几秒钟内完成,而我需要 15 到 30 分钟才能理解原始文档。 【参考方案1】:

这些函数调用之间还会发生什么?

各种指令函数是从另外两个称为$compile(执行指令的compile)和一个称为nodeLinkFn(其中指令的controllerpreLink和@ 987654327@ 被执行)。在调用指令函数之前和之后,角度函数中会发生各种事情。也许最值得注意的是子递归。以下简化图显示了编译和链接阶段的关键步骤:

为了演示这些步骤,让我们使用以下 html 标记:

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

使用以下指令:

myApp.directive( 'myElement', function() 
    return 
        restrict:   'EA',
        transclude: true,
        template:   '<div>label<div ng-transclude></div></div>'
    
);

编译

compile API 如下所示:

compile: function compile( tElement, tAttributes )  ... 

参数通常以t为前缀,表示提供的元素和属性是源模板的,而不是实例的。

在调用compile 之前,已嵌入的内容(如果有)被移除,并且模板被应用于标记。因此,提供给compile 函数的元素将如下所示:

<my-element>
    <div>
        "label"
        <div ng-transclude></div>
    </div>
</my-element>

请注意,此时不会重新插入已嵌入的内容。

在调用指令的.compile 之后,Angular 将遍历所有子元素,包括那些可能刚刚被指令引入的子元素(例如模板元素)。

实例创建

在我们的例子中,将创建上述源模板的三个实例(由ng-repeat 创建)。因此,以下序列将执行 3 次,每个实例执行一次。

控制器

controller API 涉及:

controller: function( $scope, $element, $attrs, $transclude )  ... 

进入链接阶段,通过$compile返回的链接函数现在提供了一个作用域。

首先,如果需要,链接函数会创建子作用域 (scope: true) 或隔离作用域 (scope: ...)。

然后执行控制器,并提供实例元素的范围。

预链接

pre-link API 如下所示:

function preLink( scope, element, attributes, controller )  ... 

在调用指令的.controller.preLink 函数之间几乎没有发生任何事情。 Angular 仍然提供关于如何使用它们的建议。

.preLink 调用之后,链接函数将遍历每个子元素 - 调用正确的链接函数并将当前范围(用作子元素的父范围)附加到它。

后链接

post-link API 类似于pre-link 函数:

function postLink( scope, element, attributes, controller )  ... 

也许值得注意的是,一旦指令的.postLink函数被调用,其所有子元素的链接过程就完成了,包括所有子.postLink函数。

这意味着在调用.postLink 时,孩子们“活着”已经准备好。这包括:

数据绑定 已应用嵌入 附加范围

因此,此阶段的模板将如下所示:

<my-element>
    <div class="ng-binding">
        "label"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>

【讨论】:

你是如何创作这幅画的? @RoyiNamir Omnigraffle。【参考方案2】:

指令函数的执行顺序是什么?

对于单个指令

基于以下plunk,考虑以下 HTML 标记:

<body>
    <div log='some-div'></div>
</body>

使用以下指令声明:

myApp.directive('log', function() 
  
    return 
        controller: function( $scope, $element, $attrs, $transclude ) 
            console.log( $attrs.log + ' (controller)' );
        ,
        compile: function compile( tElement, tAttributes ) 
            console.log( tAttributes.log + ' (compile)'  );
            return 
                pre: function preLink( scope, element, attributes ) 
                    console.log( attributes.log + ' (pre-link)'  );
                ,
                post: function postLink( scope, element, attributes ) 
                    console.log( attributes.log + ' (post-link)'  );
                
            ;
         
     ;  
     
);

控制台输出将是:

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

我们可以看到先执行compile,然后是controller,然后是pre-link,最后是post-link

对于嵌套指令

注意:以下内容不适用于在其链接函数中呈现其子级的指令。很多 Angular 指令都是这样做的(比如 ngIf、ngRepeat 或任何带有transclude 的指令)。这些指令将在其子指令compile 被调用之前在本机将其link 函数称为。

原始的 HTML 标记通常由嵌套元素组成,每个元素都有自己的指令。就像下面的标记一样(见plunk):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

控制台输出将如下所示:

// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)

我们可以在这里区分两个阶段 - compile 阶段和 link 阶段。

编译阶段

当加载 DOM 时,Angular 开始编译阶段,它自上而下遍历标记,并在所有指令上调用 compile。从图形上看,我们可以这样表达:

也许需要提一下,在这个阶段,compile 函数获取的模板是源模板(而不是实例模板)。

链接阶段

DOM 实例通常只是将源模板渲染到 DOM 的结果,但它们可能由 ng-repeat 创建或动态引入。

每当带有指令的元素的新实例被渲染到 DOM 时,链接阶段就开始了。

在这个阶段,Angular 调用controllerpre-link,迭代子代,并在所有指令上调用post-link,如下所示:

【讨论】:

@lzhaki 流程图看起来不错。介意分享图表工具的名称吗? :) @merlin 我用过 OmniGraffle(但也可以用 illustrator 或 inkscape - 除了速度,就这个插图而言,没有什么 OmniGraffle 比其他图表工具做得更好)。 @Anant 的 plunker 消失了,所以这是一个新的:plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview 打开 JS 控制台查看日志语句 为什么当 ng-repeat 用于子指令时这不是真的???见plunk:plnkr.co/edit/HcH4r6GV5jAFC3yOZknc?p=preview @Luckylooke 您的 plunk 在 ng-repeat 下没有带有指令的子代(即,重复的是带有指令的模板。如果是这样,您会看到它们的编译仅在之后调用ng-repeat 的链接。【参考方案3】:

控制器功能

每当实例化新的相关元素时,都会调用每个指令的 controller 函数。

正式地,controller 函数是一个:

定义可以在控制器之间共享的控制器逻辑(方法)。 启动范围变量。

同样,重要的是要记住,如果指令涉及隔离范围,则其中从父范围继承的任何属性尚不可用。

做:

定义控制器逻辑 启动范围变量

不要:

检查子元素(它们可能尚未渲染、绑定到范围等)。

【讨论】:

很高兴您在指令中提到 Controller 是初始化作用域的好地方。我很难发现这一点。 控制器不“启动作用域”,它只访问已经独立于它启动的作用域。 @DmitriZaitsev 非常注重细节。我已经修改了文本。【参考方案4】:

预链接功能

每当实例化新的相关元素时,都会调用每个指令的 pre-link 函数。

正如前面在编译顺序部分中看到的,pre-link 函数称为父子,而post-link 函数称为child-then-parent

pre-link 函数很少使用,但在特殊场景下可以派上用场;例如,当子控制器向父控制器注册自己,但注册必须采用parent-then-child 方式(ngModelController 这样做)。

不要:

检查子元素(它们可能尚未渲染、绑定到范围等)。

【讨论】:

【参考方案5】:

编译函数

每个指令的 compile 函数仅在 Angular 引导时调用一次。

正式而言,这是执行不涉及范围或数据绑定的(源)模板操作的地方。

这主要是为了优化目的;考虑以下标记:

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

&lt;my-raw&gt; 指令将呈现一组特定的 DOM 标记。所以我们可以:

允许ng-repeat复制源模板(&lt;my-raw&gt;),然后修改每个实例模板的标记(在compile函数之外)。 修改源模板以包含所需的标记(在compile 函数中),然后允许ng-repeat 复制它。

如果raws 集合中有 1000 个项目,则后一种选项可能比前一种更快。

做:

处理标记,使其用作实例(克隆)的模板。

不要

附加事件处理程序。 检查子元素。 设置属性观察。 在示波器上设置手表。

【讨论】:

【参考方案6】:

后链接功能

post-link函数被调用时,前面的所有步骤都已经发生了——绑定、嵌入等等。

这通常是进一步操作呈现的 DOM 的地方。

做:

操作 DOM(渲染并因此实例化)元素。 附加事件处理程序。 检查子元素。 设置属性观察。 在示波器上设置手表。

【讨论】:

万一有人在使用链接功能(没有前置链接或后置链接),很高兴知道它相当于后置链接。【参考方案7】:

如何声明各种函数?

编译、控制器、预链接和后链接

如果要使用所有四个功能,该指令将遵循以下形式:

myApp.directive( 'myDirective', function () 
    return 
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) 
            // Controller code goes here.
        ,
        compile: function compile( tElement, tAttributes, transcludeFn ) 
            // Compile code goes here.
            return 
                pre: function preLink( scope, element, attributes, controller, transcludeFn ) 
                    // Pre-link code goes here
                ,
                post: function postLink( scope, element, attributes, controller, transcludeFn ) 
                    // Post-link code goes here
                
            ;
        
    ;  
);

请注意,compile 返回一个包含 pre-link 和 post-link 函数的对象;在 Angular 术语中,我们说编译函数返回一个 模板函数

编译、控制器和后链接

如果不需要pre-link,编译函数可以简单地返回链接后函数而不是定义对象,如下所示:

myApp.directive( 'myDirective', function () 
    return 
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) 
            // Controller code goes here.
        ,
        compile: function compile( tElement, tAttributes, transcludeFn ) 
            // Compile code goes here.
            return function postLink( scope, element, attributes, controller, transcludeFn ) 
                    // Post-link code goes here                 
            ;
        
    ;  
);

有时,希望在定义(发布)link 方法之后添加compile 方法。为此,可以使用:

myApp.directive( 'myDirective', function () 
    return 
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) 
            // Controller code goes here.
        ,
        compile: function compile( tElement, tAttributes, transcludeFn ) 
            // Compile code goes here.

            return this.link;
        ,
        link: function( scope, element, attributes, controller, transcludeFn ) 
            // Post-link code goes here
        

    ;  
);

控制器和后链接

如果不需要编译功能,可以完全跳过其声明,并在指令配置对象的link属性下提供链接后功能:

myApp.directive( 'myDirective', function () 
    return 
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) 
            // Controller code goes here.
        ,
        link: function postLink( scope, element, attributes, controller, transcludeFn ) 
                // Post-link code goes here                 
        ,          
    ;  
);

没有控制器

在上述任何示例中,如果不需要,可以简单地删除 controller 函数。例如,如果只需要post-link 函数,可以使用:

myApp.directive( 'myDirective', function () 
    return 
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) 
                // Post-link code goes here                 
        ,          
    ;  
);

【讨论】:

【参考方案8】:

源模板实例模板有什么区别?

Angular 允许 DOM 操作这一事实意味着编译过程中的输入标记有时与输出不同。特别是,某些输入标记可能会在被渲染到 DOM 之前被克隆几次(例如使用 ng-repeat)。

Angular 术语有点不一致,但它仍然区分了两种类型的标记:

源模板 - 如果需要,要克隆的标记。如果克隆,此标记将不会呈现到 DOM。 实例模板 - 要呈现给 DOM 的实际标记。如果涉及克隆,则每个实例都是一个克隆。

以下标记证明了这一点:

<div ng-repeat="i in [0,1,2]">
    <my-directive>i</my-directive>
</div>

源html定义

    <my-directive>i</my-directive>

作为源模板。

但由于它包含在ng-repeat 指令中,因此该源模板将被克隆(在我们的例子中是 3 次)。这些克隆是实例模板,每个都会出现在 DOM 中并绑定到相关范围。

【讨论】:

以上是关于Angular 指令 - 何时以及如何使用编译、控制器、预链接和后链接 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何重新编译使用“replace: true”的 Angular 1.x 指令

Ember 如何运行 Angular 指令以及如何导入 Angular

为啥以及何时使用 angular.copy? (深拷贝)

优化编译器如何决定何时展开循环以及展开循环的程度?

如何在 Angular 2 中编译 html?

Angular Slider Touch 不适用于指令