ChatGPT解决这个技术问题 Extra ChatGPT

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

关闭。此问题不符合 Stack Overflow 准则。它目前不接受答案。这个问题似乎与帮助中心定义的范围内的编程无关。 7年前关闭。改进这个问题

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

编译

控制器

预链接

后链接

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

指令基础

如何声明各种功能?

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

指令函数按什么顺序执行?

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

功能性质,做与不做

编译

控制器

预链接

后链接

相关问题:

指令:链接 vs 编译 vs 控制器。

定义 angular.js 指令时,“控制器”、“链接”和“编译”函数之间的区别。

angularjs中的编译和链接函数有什么区别。

AngularJS 指令中预编译和后编译元素的区别?

Angular JS 指令 - 模板、编译或链接?

Angular js 指令中的发布链接与预链接。

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

C
Community

指令函数按什么顺序执行?

对于单个指令

根据以下 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 之前调用它们的链接函数。

原始 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)

我们可以在这里区分两个阶段——编译阶段和链接阶段。

编译阶段

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

https://i.stack.imgur.com/PZPOm.png

可能很重要的一点是,在这个阶段,compile 函数获取的模板是源模板(而不是实例模板)。

链接阶段

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

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

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

https://i.stack.imgur.com/XRDs6.png


@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的链接之后被调用。
I
Izhaki

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

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

https://i.stack.imgur.com/2uqPZ.png

为了演示这些步骤,让我们使用以下 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 函数的 API 类似:

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。
C
Community

如何声明各种功能?

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

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

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 术语中,我们说 compile 函数返回一个模板函数。

编译、控制器和后链接

如果不需要 pre-link,则 compile 函数可以简单地返回 post-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                 
            };
        }
    };  
});

有时,希望在定义 (post) 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                 
        },          
    };  
});

C
Community

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

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 中并绑定到相关范围。


e
eppsilon

编译函数

当 Angular 引导时,每个指令的 compile 函数只被调用一次。

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

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

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

<my-raw> 指令将呈现一组特定的 DOM 标记。所以我们可以:

允许 ng-repeat 复制源模板 (),然后修改每个实例模板的标记(在 compile 函数之外)。

修改源模板以包含所需的标记(在 compile 函数中),然后允许 ng-repeat 复制它。

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

做:

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

不要

附加事件处理程序。

检查子元素。

建立对属性的观察。

在示波器上设置手表。


e
eppsilon

控制器功能

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

正式地,controller 函数是其中之一:

定义可以在控制器之间共享的控制器逻辑(方法)。

启动范围变量。

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

做:

定义控制器逻辑

启动范围变量

不要:

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


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

后链接功能

调用 post-link 函数时,前面的所有步骤都已执行 - 绑定、嵌入等。

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

做:

操作 DOM(渲染,因此实例化)元素。

附加事件处理程序。

检查子元素。

建立对属性的观察。

在示波器上设置手表。


如果有人使用链接功能(没有前置链接或后置链接),很高兴知道它等同于后置链接。
e
eppsilon

预链接功能

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

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

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

不要:

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