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

编译 控制器 pre-link post-link

对于应该使用哪个函数,似乎有些困惑。这个问题包括:

基本指令

如何声明各种函数? 源模板和实例模板之间的区别是什么? 指令函数的执行顺序是什么? 在这些函数调用之间还会发生什么?

功能性质,该做的和不该做的

编译 控制器 Pre-link Post-link

相关问题:

指令:链接vs编译vs控制器。 在定义angular.js指令时,'controller', 'link'和'compile'函数之间的区别。 angularjs中的compile函数和link函数有什么区别? AngularJS指令中预编译元素和后编译元素的区别? Angular JS指令-模板、编译还是链接? Angular js指令中的post link vs pre link。


当前回答

控制器的功能

每当一个新的相关元素被实例化时,就会调用每个指令的控制器函数。

正式来说,控制器函数是这样的:

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

同样,重要的是要记住,如果指令涉及一个孤立的作用域,那么其中继承自父作用域的任何属性都还不可用。

Do:

定义控制器逻辑 初始化范围变量

不:

检查子元素(它们可能还没有呈现,绑定到范围等)。

其他回答

如何声明各种函数?

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

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

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函数返回一个模板函数。

编译,控制器和Post-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
        }

    };  
});

控制器& Post-link

如果不需要编译函数,可以完全跳过它的声明,并在指令配置对象的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                 
        },          
    };  
});

没有控制器

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

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

控制器的功能

每当一个新的相关元素被实例化时,就会调用每个指令的控制器函数。

正式来说,控制器函数是这样的:

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

同样,重要的是要记住,如果指令涉及一个孤立的作用域,那么其中继承自父作用域的任何属性都还不可用。

Do:

定义控制器逻辑 初始化范围变量

不:

检查子元素(它们可能还没有呈现,绑定到范围等)。

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

对于单个指令

基于以下内容,考虑以下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。

对于嵌套指令

注意:以下内容不适用于在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。从图形上来说,我们可以这样表达:

值得一提的是,在此阶段,compile函数获得的模板是源模板(而不是实例模板)。

链接阶段

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

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

在这个阶段,Angular会对所有指令调用controller、pre-link、迭代子函数和post-link,如下所示:

Post-link函数

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

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

Do:

操作DOM(呈现,并因此实例化)元素。 附加事件处理程序。 检查子元素。 建立对属性的观察。 在瞄准镜上设置监视。

源模板和实例模板之间的区别是什么?

事实上,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中,并被绑定到相关的作用域。