更新:我对此问题表示赏心悦目。我不是在寻找黑客或变通办法。我正在寻找以角度组件访问dom的官方方式,并解释为什么我看到的行为($ postLink早期运行)似乎与官方文档相矛盾。
官方文档声明(here):
$ postLink() - 在此控制器的元素及其子元素已被链接之后调用。与post-link函数类似,此挂钩可用于设置DOM事件处理程序并执行直接DOM操作
原始问题:我在这里有一个问题的例子 - > http://plnkr.co/edit/rMm9FOwImFRziNG4o0sg?p=preview
我正在使用一个角度组件,我想修改后期链接功能中的dom,但是它不起作用,似乎该功能运行得太早,在模板实际准备好之前在dom之后角度处理。
在html页面中,我有这个:
<my-grid grid-id="'foo'"></my-grid>
该组件定义为:
appModule.component('myGrid',{
controller: gridController,
bindings: {
"gridId": "<",
},
templateUrl: 'gridTemplate'
});
在组件模板中,我有:
<table id='{{$ctrl.gridId}}'>
...
(绑定本身有效,毫无疑问。最终,在html中,表的id是'foo',如预期的那样。)
在控制器中,我有类似的东西:
function gridController($scope, $compile, $attrs) {
console.log ("grid id is: " + this.gridId); // 'foo'
this.$postLink = function() {
var elem = document.getElementById(this.gridId);
// do something with elem, but elem is null
}
}
我在调试时看到的是,当执行$ postLink函数时,该表位于dom中,但其id属性仍为{{$ctrl.gridId}}
而不是foo
,因此document.getElementById()
找到没有。这似乎与文档形成鲜明对比
我错过了什么?是否有不同的方法来访问组件中的dom?
更新2:今天我意识到指令的常规链接功能也出现同样的问题,它不仅限于组件。显然我误解了“直接操作DOM”的含义 - 链接函数在与dom分离的元素上运行,因此使用带有选择器的document
对象是没用的。
答案 0 :(得分:21)
有关$postLink()
的文档是正确的。它的控制器元素之后被称为它的子元素。这并不意味着您可以立即看到指令的结果。也许它正在调用$http
并在结果到达时插入结果。也许它正在注册一个观察者,而这个观察者依次设置结果,就像大多数Angular的内置指令一样。
您的案例中的基本问题是您希望在编译插值之后执行DOM操作,或者更好的是,在注册观察者有时间运行一次之后执行DOM操作。
不幸的是,没有官方方式来执行此操作。尽管如此,有一些方法可以实现这一点,但是你不会在文档中找到它们。
编译插值后运行函数的两种常用方法是:
使用$timeout
没有延迟(默认为0):$timeout(function() { /* Your code goes here */ });
.ready()
在您的情况下,一旦具有给定ID的元素存在,您最好使用观察者来运行函数:
var deregistrationFn = $scope.$watch(() => {
return document.getElementById(this.gridId);
}, (newValue) => {
if (newValue !== null) {
deregistrationFn();
// Your code goes here
}
});
最后,在我看来,我相信无论何时你需要等待编译插值,或者某些指令插入它们的值,你都不会遵循 Angular的方式建筑物。在您的情况下,为什么不创建一个新组件myGridTable
,它需要myGrid
作为父组件,并在那里添加适当的逻辑。这样,每个组件的责任都得到了更好的定义,并且更容易进行测试。
答案 1 :(得分:11)
误解在于角度$摘要循环如何与控制器生命周期事件(以及指令生命周期事件)一起工作。
每个控制器/指令生命周期事件都在$ apply内部调用。因此,DOM仅会更新以反映$digest cycle is completed之后的更改。
后链接函数在链接完所有内容后运行,但是at the end of this function更新DOM以反映任何绑定,因为它是$ digest循环内运行的最后一个函数。这意味着尝试查询DOM以获取受绑定影响的任何内容都不起作用(例如ng-repeat
,或者在您的情况下,绑定值)。但正如文档所述,DOM的链接已经运行,因此初始模板已经创建并且可以被操作。
由于您需要在应用绑定后查询DOM,因此需要在当前的$ digest周期完成后运行代码。这意味着使用延迟为0的$timeout
,即executed right after the current $digest cycle has ended。
function gridController($scope, $compile, $attrs, $timeout) {
console.log ("grid id is: " + this.gridId); // 'foo'
this.$postLink = function() {
$timeout(function() {
var elem = document.getElementById(this.gridId);
// do something with elem now that the DOM has had it's bindings applied
});
}
}
答案 2 :(得分:2)
在给出任何解决方案之前,它似乎与理解角度分量场景有关。我从你的例子中看到了在隔离范围内加载的模板DOM。重复隔离范围
所以,
的官方文档[1.5.6]
何时不使用组件:
如果您查看&#34;指令定义与组件定义之间的比较&#34;从文档中,您将看到
现在
组件永远不应该修改任何数据或DOM 拥有范围。通常,在Angular中,可以在任何地方修改数据 在应用程序中通过范围继承和监视。这是 实用,但也可能导致问题,当不清楚哪个 应用程序的一部分负责修改数据。那是 为什么组件指令使用 isolate 范围,所以整个类 范围操纵是不可能的。 参考: ng-doc 1.5.6
但是当你看到,有广告$ postLink()钩子可用,你可能想知道原因是什么?
但是,如果你仔细阅读这个钩子的描述,你会发现......
注意包含 templateUrl 指令的子元素不会被编译和链接,因为他们正在等待他们的模板异步加载,他们自己的编译和链接已暂停,直到发生。
请参阅下面的Steven Lambert答案......