Post-link函数

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

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

做的事:

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

如何声明各种函数?

编译,控制器,预链接&放大器;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 {
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

如果不需要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
}


};
});

控制器,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
},
};
});

没有控制器

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

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

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

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

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

对于单个指令

基于下面的砰砰作响,考虑下面的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的指令)。这些指令的link函数被称为之前,它们的子指令被称为compile

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

<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会对所有指令调用controllerpre-link、迭代子函数,并调用post-link,如下所示:

一个演示链接阶段步骤的插图

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

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

 Angular编译和链接阶段的演示图

为了演示这些步骤,让我们使用下面的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)。因此,下面的序列将执行三次,每个实例一次。

控制器

controller API包括:

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

进入链接阶段,通过$compile返回的链接函数现在具有作用域。

首先,如果需要,link函数创建一个子作用域(scope: true)或一个隔离作用域(scope: {...})。

然后执行控制器,并提供实例元素的作用域。

Pre-link

pre-link API看起来像这样:

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

实际上,在调用指令的.controller.preLink函数之间没有发生任何事情。Angular仍然提供了如何使用它们的建议。

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

Post-link

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

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

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

这意味着在.postLink被调用时,子对象已经“活”了。这包括:

  • 数据绑定
  • transclusion应用
  • 范围在

这个阶段的模板看起来是这样的:

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

编译函数

每个指令的compile函数只在Angular启动时被调用一次。

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

这样做主要是为了优化;考虑下面的标记:

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

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

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

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

做的事:

  • 操作标记,使其充当实例(克隆)的模板。

  • 附加事件处理程序。
  • 检查子元素。
  • 建立对属性的观察。
  • 在瞄准镜上设置监视。

控制器的功能

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

正式来说,controller函数是:

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

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

做的事:

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

不:

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

Pre-link函数

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

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

pre-link函数很少使用,但在特殊情况下可能有用;例如,当子控制器将自己注册到父控制器,但必须以parent-then-child的方式注册(ngModelController是这样做的)。

不:

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