Izh*_*aki 167

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

对于单个指令

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

<body>
    <div log='some-div'></div>
</body>
Run Code Online (Sandbox Code Playgroud)

使用以下指令声明:

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)'  );
                }
            };
         }
     };  

});
Run Code Online (Sandbox Code Playgroud)

控制台输出将是:

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)
Run Code Online (Sandbox Code Playgroud)

我们可以看到,compile在执行第一次,然后controller,再pre-link和最后的post-link.

对于嵌套指令

注意:以下内容不适用于在其链接函数中呈现其子项的指令.相当多的Angular指令都这样做(比如ngIf,ngRepeat或任何指令transclude).这些指令本身会在link调用其子指令之前调用它们的函数compile.

原始HTML标记通常由嵌套元素组成,每个元素都有自己的指令.如下面的标记(参见plunk):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>
Run Code Online (Sandbox Code Playgroud)

控制台输出如下所示:

// 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)
Run Code Online (Sandbox Code Playgroud)

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

编译阶段

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

一个图像说明了儿童的编译循环

或许重要的是要提到,在这个阶段,编译函数获取的模板是源模板(而不是实例模板).

链接阶段

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

每当具有指令的元素的新实例被呈现给DOM时,链接阶段就开始了.

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

演示链接阶段步骤的插图

  • @lzhaki流程图看起来不错.介意分享图表工具的名称?:) (5认同)
  • @ Anant的plunker消失所以这是一个新的:http://plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p = preview打开JS控制台以查看日志语句 (2认同)

Izh*_*aki 90

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

各种指令函数从内调用其他两个角函数的,执行$compile(其中指令的compile执行)和称为内部函数nodeLinkFn(其中,该指令的controller,preLinkpostLink被执行).在调用指令函数之前和之后,角函数内发生了各种各样的事情.也许最值得注意的是儿童递归.以下简化图显示了编译和链接阶段的关键步骤:

显示Angular编译和链接阶段的插图

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

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>
Run Code Online (Sandbox Code Playgroud)

使用以下指令:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});
Run Code Online (Sandbox Code Playgroud)

compileAPI看起来就像这样:

compile: function compile( tElement, tAttributes ) { ... }
Run Code Online (Sandbox Code Playgroud)

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

compile删除对已转换内容(如果有)的调用之前,将模板应用于标记.因此,提供给compile函数的元素将如下所示:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>
Run Code Online (Sandbox Code Playgroud)

请注意,此时不会重新插入已转换的内容.

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

实例创建

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

调节器

controllerAPI包括:

controller: function( $scope, $element, $attrs, $transclude ) { ... }
Run Code Online (Sandbox Code Playgroud)

进入链接阶段,$compile现在返回的链接函数提供了一个范围.

首先,如果请求,链接函数创建子范围(scope: true)或隔离范围(scope: {...}).

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

预链接

pre-linkAPI看起来就像这样:

function preLink( scope, element, attributes, controller ) { ... }
Run Code Online (Sandbox Code Playgroud)

实际上,在对指令的调用.controller.preLink函数之间没有任何反应.Angular仍然建议如何使用每个.

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

帖子链接

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

function postLink( scope, element, attributes, controller ) { ... }
Run Code Online (Sandbox Code Playgroud)

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

这意味着,在.postLink被召唤的时候,孩子们"活着"就准备好了.这包括:

  • 数据绑定
  • 应用包含
  • 附加范围

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

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Run Code Online (Sandbox Code Playgroud)

  • @RoyiNamir Omnigraffle. (6认同)
  • 你是怎么创造这幅画的? (3认同)

Izh*_*aki 43

如何申报各种功能?

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

如果要使用全部四个函数,该指令将遵循以下形式:

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
                }
            };
        }
    };  
});
Run Code Online (Sandbox Code Playgroud)

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

编译,控制器和后链接

如果pre-link没有必要,编译函数可以简单地返回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                 
            };
        }
    };  
});
Run Code Online (Sandbox Code Playgroud)

有时,人们希望在定义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 this.link;
        },
        link: function( scope, element, attributes, controller, transcludeFn ) {
            // Post-link code goes here
        }

    };  
});
Run Code Online (Sandbox Code Playgroud)

控制器和后链接

如果不需要编译函数,可以完全跳过它的声明,并link在指令的配置对象的属性下提供post-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                 
        },          
    };  
});
Run Code Online (Sandbox Code Playgroud)

没有控制器

在上面的任何示例中,controller如果不需要,可以简单地移除该功能.因此,例如,如果只post-link需要功能,可以使用:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});
Run Code Online (Sandbox Code Playgroud)


Izh*_*aki 31

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

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

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

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

以下标记演示了这一点:

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>
Run Code Online (Sandbox Code Playgroud)

源html定义

    <my-directive>{{i}}</my-directive>
Run Code Online (Sandbox Code Playgroud)

它充当源模板.

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


Izh*_*aki 23

编译功能

compile当Angular bootstraps时,每个指令的函数只被调用一次.

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

首先,这是为了优化目的; 考虑以下标记:

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>
Run Code Online (Sandbox Code Playgroud)

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

  • 允许ng-repeat复制源模板(<my-raw>),然后修改每个实例模板的标记(在compile函数外部).
  • 修改源模板以包含所需的标记(在compile函数中),然后允许ng-repeat复制它.

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

做:

  • 操纵标记,使其作为实例(克隆)的模板.

不要

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


Izh*_*aki 20

控制器功能

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

正式来说,controller功能在于:

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

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

做:

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

不要:

  • 检查子元素(它们可能尚未呈现,受范围限制等).


Izh*_*aki 19

后链接功能

post-link调用该函数时,所有先前的步骤都已发生 - 绑定,转换等.

这通常是进一步操纵渲染DOM的地方.

做:

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

  • 如果有人使用链接功能(没有预链接或后链接),最好知道它等同于后链接. (9认同)

Izh*_*aki 15

预链接功能

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

正如之前在编译顺序部分中看到的那样,pre-link函数被称为parent-then-child,而post-link函数被调用child-then-parent.

pre-link功能很少使用,但在特殊情况下可能很有用; 例如,当子控制器向父控制器注册自己时,注册必须是一种parent-then-child方式(以ngModelController这种方式做事).

不要:

  • 检查子元素(它们可能尚未呈现,受范围限制等).