我需要对作用域和模板执行一些操作。似乎我可以在链接函数或控制器函数中做到这一点(因为两者都可以访问范围)。
什么情况下我必须使用链接功能而不是控制器?
angular.module('myApp').directive('abc', function($timeout) {
return {
restrict: 'EA',
replace: true,
transclude: true,
scope: true,
link: function(scope, elem, attr) { /* link function */ },
controller: function($scope, $element) { /* controller function */ }
};
}
而且,我知道链接是非角的世界。我可以使用$watch, $digest和$apply。
当我们已经有了控制器,连杆函数的意义是什么?
为什么需要控制器
当你想要在DOM中嵌套指令并将API函数从父指令公开到嵌套指令时,链接和控制器之间的区别就开始发挥作用了。
从文档中可以看出:
最佳实践:当你想将API暴露给其他指令时使用controller。否则使用link。
假设你想要有两个指令my-form和my-text-input,你想要my-text-input指令只出现在my-form中,而不在其他地方。
在这种情况下,在定义指令my-text-input时,你会说它需要一个来自父DOM元素的控制器,使用require参数,就像这样:require: '^myForm'。现在父元素的控制器将作为第四个参数注入到链接函数中,在$scope, element, attributes之后。你可以调用该控制器上的函数并与父指令通信。
此外,如果没有找到这样的控制器,则会引发错误。
为什么要使用link呢
如果要定义控制器,则不需要使用link函数,因为$scope在控制器上是可用的。此外,在定义链接和控制器时,确实需要注意两者的调用顺序(控制器在前面执行)。
然而,为了保持Angular的方式,大多数DOM操作和使用$watchers的双向绑定通常在link函数中完成,而用于子函数和$scope操作的API则在控制器中完成。这不是一个严格的规则,但这样做将使代码更加模块化,并有助于分离关注点(控制器将维护指令状态,链接函数将维护DOM +外部绑定)。
控制器函数/对象表示一个抽象模型-视图-控制器(MVC)。虽然关于MVC没有什么新鲜的东西可写,但它仍然是angular最重要的优势:将关注点分割成更小的部分。仅此而已,所以如果你需要对来自视图的模型变化做出反应,控制器是做这项工作的合适人选。
关于链接功能的故事是不同的,它来自不同的角度,然后MVC。当我们想要跨越控制器/模型/视图(模板)的边界时,这是非常重要的。
让我们从传递给link函数的参数开始:
function link(scope, element, attrs) {
scope是Angular的作用域对象。
element是这个指令匹配的jqlite包装元素。
Attrs是一个具有规范化属性名称及其对应值的对象。
为了将链接放到上下文中,我们应该提到所有的指令都要经过这个初始化过程步骤:Compile, link。摘自Brad Green和Shyam Seshadri的书Angular JS:
编译阶段(link的姐妹,让我们在这里提到它,以获得一个清晰的图像):
在这个阶段,Angular遍历DOM以识别所有已注册的对象
模板中的指令。对于每个指令,它然后转换
DOM基于指令的规则(模板,替换,透射,和
等等),如果存在,则调用compile函数。结果是
编译模板函数,
链接阶段:
为了使视图动态,Angular会为每个视图运行一个link函数
指令。链接函数通常在DOM上创建侦听器
或者模型。这些侦听器保持视图和模型在同步
所有时间。
一个如何使用链接的好例子可以在这里找到:创建自定义指令。参见示例:创建一个操作DOM的指令,它将插入一个“日期-时间”到页面中,每秒刷新一次。
只是上面丰富源代码中的一个非常短的片段,展示了DOM的实际操作。$timeout服务有一个挂钩函数,并且它在析构函数调用中被清除,以避免内存泄漏
.directive('myCurrentTime', function($timeout, dateFilter) {
function link(scope, element, attrs) {
...
// the not MVC job must be done
function updateTime() {
element.text(dateFilter(new Date(), format)); // here we are manipulating the DOM
}
function scheduleUpdate() {
// save the timeoutId for canceling
timeoutId = $timeout(function() {
updateTime(); // update DOM
scheduleUpdate(); // schedule the next update
}, 1000);
}
element.on('$destroy', function() {
$timeout.cancel(timeoutId);
});
...
在我最初与链接和控制器功能斗争并阅读了大量关于它们的内容之后,我想现在我找到了答案。
首先让我们了解一下,
简单地说,angular指令是如何工作的:
We begin with a template (as a string or loaded to a string)
var templateString = '<div my-directive>{{5 + 10}}</div>';
Now, this templateString is wrapped as an angular element
var el = angular.element(templateString);
With el, now we compile it with $compile to get back the link function.
var l = $compile(el)
Here is what happens,
$compile walks through the whole template and collects all the directives that it recognizes.
All the directives that are discovered are compiled recursively and their link functions are collected.
Then, all the link functions are wrapped in a new link function and returned as l.
Finally, we provide scope function to this l (link) function which further executes the wrapped link functions with this scope and their corresponding elements.
l(scope)
This adds the template as a new node to the DOM and invokes controller which adds its watches to the scope which is shared with the template in DOM.
比较compile vs link vs controller:
Every directive is compiled only once and link function is retained for re-use. Therefore, if there's something applicable to all instances of a directive should be performed inside directive's compile function.
Now, after compilation we have link function which is executed while attaching the template to the DOM. So, therefore we perform everything that is specific to every instance of the directive. For eg: attaching events, mutating the template based on scope, etc.
Finally, the controller is meant to be available to be live and reactive while the directive works on the DOM (after getting attached). Therefore:
(1) After setting up the view[V] (i.e. template) with link. $scope is our [M] and $controller is our [C] in M V C
(2) Take advantage the 2-way binding with $scope by setting up watches.
(3) $scope watches are expected to be added in the controller since this is what is watching the template during run-time.
(4) Finally, controller is also used to be able to communicate among related directives. (Like myTabs example in https://docs.angularjs.org/guide/directive)
(5) It's true that we could've done all this in the link function as well but its about separation of concerns.
因此,最后我们得到了以下内容,它完美地符合所有部分: