编写Angular指令时,可以使用以下任何函数来操纵DOM的行为,声明该指令的元素的内容和外观:
- 编译
- 控制者
- 预链接
- 链接后
关于应该使用哪个功能似乎有些混乱。这个问题包括:
编写Angular指令时,可以使用以下任何函数来操纵DOM的行为,声明该指令的元素的内容和外观:
关于应该使用哪个功能似乎有些混乱。这个问题包括:
Answers:
基于以下普拉克,请考虑以下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
所有指令,如下所示:
各种指令函数是从另外两个称为$compile
(compile
执行指令的位置)的角函数和称为(执行nodeLinkFn
指令的位置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
功能)的链接过程就完成了。
这意味着到时间.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返回一个同时包含前链接和后链接功能的对象;在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
};
}
};
});
有时,一个人希望在定义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 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操作的事实意味着编译过程中的输入标记有时与输出有所不同。特别是,某些输入标记ng-repeat
在呈现给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中并绑定到相关范围。
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
功能是其中之一:
同样,重要的是要记住,如果该指令涉及一个隔离的作用域,则它中从父作用域继承的任何属性都将不可用。