- compile(编译)
- controller(控制器)
- pre-link(前置链接)
- post-link(后置链接)
基于以下示例,考虑以下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
。 transclude
的任何指令)。这些指令将在其子指令compile
被调用之前自然地调用它们的link
函数。<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)
compile
。我们可以用图形来表示它:
ng-repeat
创建的,或者是即时引入的。controller
,pre-link
,迭代子级,并在所有指令上调用post-link
,如下所示:
ng-click
),应该在编译阶段还是链接阶段完成呢? - floribonmy-custom-dir
属性并添加 ng-click
属性,然后重新编译整个程序,但不确定在哪里或何时进行。 - floribon各种指令函数是从两个叫做 $compile
(执行指令的 compile
函数)和一个内部函数 nodeLinkFn
(执行指令的 controller
、preLink
和 postLink
函数)中执行的。在指令函数被调用之前和之后,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>
controller: function( $scope, $element, $attrs, $transclude ) { ... }
进入链接阶段后,通过$compile
返回的链接函数现在会提供一个作用域。
首先,如果需要,链接函数会创建一个子作用域(scope: true
)或隔离作用域(scope: {...}
)。
然后执行控制器,使用实例元素的作用域。
pre-link
API 如下:
function preLink( scope, element, attributes, controller ) { ... }
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
}
};
}
};
});
需要注意的是,编译函数返回一个包含前链接和后链接函数的对象;在 Angular 中我们说编译函数返回一个模板函数。
如果不需要pre-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术语有点不一致,但它仍然区分两种类型的标记:
以下标记演示了这一点:
<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>
< p > <my-raw>
指令将呈现一组特定的DOM标记。因此我们可以:
ng-repeat
复制源模板(<my-raw>
),然后修改每个实例模板的标记(在compile
函数外)。compile
函数中),然后允许 ng-repeat
复制它。如果 raws
集合中有1000个项目,则后一选项可能比前一选项更快。
每个指令的controller
函数在实例化一个新的相关元素时被调用。
官方文档中,controller
函数的作用有:
同样需要记住的是,如果指令涉及隔离作用域,在其中继承自父作用域的任何属性都还不可用。
后链接函数(post-link function)
被调用时,之前的所有步骤都已经完成-绑定、转置等。
通常这是进一步操作渲染后的DOM的地方。
每个指令的pre-link
函数在实例化一个新的相关元素时都会被调用。
如前文所述,在编译顺序部分中,pre-link
函数按父-子顺序调用,而post-link
函数按子-父顺序调用。
pre-link
函数很少使用,但在特殊情况下可能非常有用;例如,当子控制器向父控制器注册自身时,但注册必须以父-子
的形式进行(ngModelController
就是这样做的)。