关闭。此问题不符合 Stack Overflow 准则。它目前不接受答案。这个问题似乎与帮助中心定义的范围内的编程无关。 7年前关闭。改进这个问题
在编写 Angular 指令时,可以使用以下任何函数来操作声明指令的元素的 DOM 行为、内容和外观:
编译
控制器
预链接
后链接
关于应该使用哪个功能似乎有些混乱。这个问题涵盖:
指令基础
如何声明各种功能?
源模板和实例模板有什么区别?
指令函数按什么顺序执行?
这些函数调用之间还会发生什么?
功能性质,做与不做
编译
控制器
预链接
后链接
相关问题:
指令:链接 vs 编译 vs 控制器。
定义 angular.js 指令时,“控制器”、“链接”和“编译”函数之间的区别。
angularjs中的编译和链接函数有什么区别。
AngularJS 指令中预编译和后编译元素的区别?
Angular JS 指令 - 模板、编译或链接?
Angular js 指令中的发布链接与预链接。
指令函数按什么顺序执行?
对于单个指令
根据以下 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 调用 controller
、pre-link
、迭代子代,并在所有指令上调用 post-link
,如下所示:
https://i.stack.imgur.com/XRDs6.png
这些函数调用之间还会发生什么?
各种指令函数在另外两个称为 $compile
(指令的 compile
执行的地方)和一个称为 nodeLinkFn
的内部函数(其中指令的 controller
、preLink
和 postLink
是执行)。在调用指令函数之前和之后,角度函数中会发生各种事情。也许最值得注意的是子递归。以下简化图显示了编译和链接阶段的关键步骤:
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>
如何声明各种功能?
编译、控制器、预链接和后链接
如果要使用所有四个功能,该指令将遵循以下形式:
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
},
};
});
源模板和实例模板有什么区别?
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 引导时,每个指令的 compile
函数只被调用一次。
正式地,这是执行不涉及范围或数据绑定的(源)模板操作的地方。
这主要是为了优化目的;考虑以下标记:
<tr ng-repeat="raw in raws">
<my-raw></my-raw>
</tr>
<my-raw>
指令将呈现一组特定的 DOM 标记。所以我们可以:
允许 ng-repeat 复制源模板 (
修改源模板以包含所需的标记(在 compile 函数中),然后允许 ng-repeat 复制它。
如果 raws
集合中有 1000 个项目,则后一个选项可能比前一个选项快。
做:
处理标记,使其用作实例(克隆)的模板。
不要
附加事件处理程序。
检查子元素。
建立对属性的观察。
在示波器上设置手表。
控制器功能
每当实例化新的相关元素时,都会调用每个指令的 controller
函数。
正式地,controller
函数是其中之一:
定义可以在控制器之间共享的控制器逻辑(方法)。
启动范围变量。
同样,重要的是要记住,如果指令涉及隔离范围,则其中从父范围继承的任何属性尚不可用。
做:
定义控制器逻辑
启动范围变量
不要:
检查子元素(它们可能尚未渲染,绑定到范围等)。
后链接功能
调用 post-link
函数时,前面的所有步骤都已执行 - 绑定、嵌入等。
这通常是进一步操作呈现的 DOM 的地方。
做:
操作 DOM(渲染,因此实例化)元素。
附加事件处理程序。
检查子元素。
建立对属性的观察。
在示波器上设置手表。
预链接功能
每当实例化新的相关元素时,都会调用每个指令的 pre-link
函数。
正如前面在编译顺序部分中看到的,pre-link
函数称为父子函数,而 post-link
函数称为 child-then-parent
。
pre-link
函数很少使用,但在特殊场景下可以派上用场;例如,当子控制器向父控制器注册自己,但注册必须采用 parent-then-child
方式(ngModelController
以这种方式进行操作)。
不要:
检查子元素(它们可能尚未渲染,绑定到范围等)。
不定期副业成功案例分享