编写Angular指令时,可以使用以下任何函数来操作声明指令的元素的DOM行为,内容和外观:
对于应该使用哪种功能似乎存在一些混淆。这个问题包括:
答案 0 :(得分:167)
根据以下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
的指令)。在调用其子指令link
之前,这些指令本身将具有名为的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
。从图形上看,我们可以这样表达:
值得注意的是,在这个阶段,编译函数获取的模板是源模板(而不是实例模板)。
DOM实例通常只是将源模板呈现给DOM的结果,但它们可以由ng-repeat
创建,也可以动态引入。
每当具有指令的元素的新实例呈现给DOM时,链接阶段就会开始。
在此阶段,Angular调用controller
,pre-link
,迭代子项,并在所有指令上调用post-link
,如下所示:
答案 1 :(得分:90)
各种指令函数从名为$compile
的两个其他角度函数(执行指令&#39; s compile
)和名为nodeLinkFn
的内部函数中执行(其中指令&#39; controller
,preLink
和postLink
已执行)。在调用指令函数之前和之后,角函数内发生了各种各样的事情。也许最值得注意的是儿童递归。以下简化图显示了编译和链接阶段的关键步骤:
要演示这些步骤,请使用以下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
返回的链接功能现在提供了范围。
首先,如果要求,链接功能会创建子范围(scope: true
)或隔离范围(scope: {...}
)。
然后执行控制器,提供实例元素的范围。
pre-link
API如下所示:
function preLink( scope, element, attributes, controller ) { ... }
在指令.controller
和.preLink
函数的调用之间几乎没有任何反应。 Angular仍然建议如何使用每个。
在.preLink
调用之后,链接函数将遍历每个子元素 - 调用正确的链接函数并附加当前作用域(作为子元素的父作用域)。
post-link
API类似于pre-link
函数:
function postLink( scope, element, attributes, controller ) { ... }
或许值得注意的是,一旦调用了指令的.postLink
函数,其所有子元素的链接过程就已完成,包括所有子函数.postLink
。< / p>
这意味着,在调用.postLink
时,孩子们会活着“生活”。准备好了。这包括:
此阶段的模板将如此:
<my-element>
<div class="ng-binding">
"{{label}}"
<div ng-transclude>
<div class="ng-scope">Inner content</div>
</div>
</div>
</my-element>
答案 2 :(得分: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
}
};
}
};
});
请注意,compile返回一个包含pre-link和post-link函数的对象;在Angular lingo中,我们说编译函数返回一个模板函数。
如果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
};
}
};
});
有时,人们希望在定义(post)compile
方法后添加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
}
};
});
如果不需要编译函数,可以完全跳过它的声明,并在指令的配置对象的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
},
};
});
答案 3 :(得分:30)
Angular允许DOM操作的事实意味着编译过程中的输入标记有时与输出不同。特别是,在渲染到DOM之前,可以将一些输入标记克隆几次(如ng-repeat
)。
角度术语有点不一致,但它仍然区分两种类型的标记:
以下标记证明了这一点:
<div ng-repeat="i in [0,1,2]">
<my-directive>{{i}}</my-directive>
</div>
源html定义
<my-directive>{{i}}</my-directive>
用作源模板。
但由于它包含在ng-repeat
指令中,因此将克隆此源模板(在我们的示例中为3次)。这些克隆是实例模板,每个都将出现在DOM中并绑定到相关范围。
答案 4 :(得分:23)
当Angular bootstraps时,每个指令的compile
函数只被调用一次。
正式地说,这是执行(源)模板操作的地方,不涉及范围或数据绑定。
首先,这是为了优化目的;考虑以下标记:
<tr ng-repeat="raw in raws">
<my-raw></my-raw>
</tr>
<my-raw>
指令将呈现一组特定的DOM标记。所以我们可以:
ng-repeat
复制源模板(<my-raw>
),然后修改每个实例模板的标记(在compile
函数之外)。compile
函数中),然后允许ng-repeat
复制它。如果raws
集合中有1000个项目,后一个选项可能比前一个更快。
答案 5 :(得分:19)
调用post-link
函数时,所有先前的步骤都已发生 - 绑定,转换等。
这通常是进一步操纵渲染DOM的地方。
答案 6 :(得分:19)
每当实例化新的相关元素时,都会调用每个指令的controller
函数。
正式地说,controller
功能是一个:
同样,重要的是要记住,如果指令涉及隔离范围,那么从父范围继承的任何属性都不可用。
答案 7 :(得分:15)
每当实例化新的相关元素时,都会调用每个指令的pre-link
函数。
如前面编译顺序部分所示,pre-link
函数称为parent-then-child,而post-link
函数称为child-then-parent
。
很少使用pre-link
函数,但在特殊情况下可能很有用;例如,当子控制器向父控制器注册自己时,注册必须采用parent-then-child
方式(ngModelController
以这种方式执行)。