Javascript AngularJS:如何在 AngularJS 呈现模板后运行附加代码?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12304291/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
AngularJS: How to run additional code after AngularJS has rendered a template?
提问by gorebash
I have an Angular template in the DOM. When my controller gets new data from a service, it updates the model in the $scope, and re-renders the template. All good so far.
我在 DOM 中有一个 Angular 模板。当我的控制器从服务中获取新数据时,它会更新 $scope 中的模型,并重新呈现模板。到目前为止一切都很好。
The issue is that I need to also do some extra work after the template has been re-rendered and is in the DOM (in this case a jQuery plugin).
问题是,在重新渲染模板并在 DOM 中(在本例中为 jQuery 插件)后,我还需要做一些额外的工作。
It seems like there should be an event to listen to, such as AfterRender, but I can't find any such thing. Maybe a directive would be a way to go, but it seemed to fire too early as well.
似乎应该有一个事件可以监听,比如AfterRender,但是我找不到任何这样的东西。也许指令会是一种方式,但它似乎也为时过早。
Here is a jsFiddle outlining my problem: Fiddle-AngularIssue
这是一个 jsFiddle 概述了我的问题:Fiddle-AngularIssue
== UPDATE ==
== 更新 ==
Based on helpful comments, I've accordingly switched to a directive to handle DOM manipulation, and implemented a model $watch inside the directive. However, I still am having the same base issue; the code inside of the $watch event fires before the template has been compiled and inserted into the DOM, therefore, the jquery plugin is always evaluating an empty table.
根据有用的评论,我相应地切换到一个指令来处理 DOM 操作,并在该指令中实现了一个模型 $watch。但是,我仍然遇到相同的基本问题;$watch 事件中的代码在模板被编译并插入到 DOM 之前触发,因此,jquery 插件总是评估一个空表。
Interestingly, if I remove the async call the whole thing works fine, so that's a step in the right direction.
有趣的是,如果我删除异步调用,整个事情都可以正常工作,所以这是朝着正确方向迈出的一步。
Here is my updated Fiddle to reflect these changes: http://jsfiddle.net/uNREn/12/
这是我更新的 Fiddle 以反映这些变化:http: //jsfiddle.net/uNREn/12/
采纳答案by dipold
回答by danijar
First, the right place to mess with rendering are directives. My advice would be to wrap DOM manipulating jQuery plugins by directives like this one.
首先,处理渲染的正确位置是指令。我的建议是通过像这样的指令来包装 DOM 操作 jQuery 插件。
I had the same problem and came up with this snippet. It uses $watch
and $evalAsync
to ensure your code runs after directives like ng-repeat
have been resolved and templates like {{ value }}
got rendered.
我遇到了同样的问题并想出了这个片段。它使用$watch
并$evalAsync
确保您的代码在诸如ng-repeat
已解析的指令和诸如已{{ value }}
呈现的模板之类的指令之后运行。
app.directive('name', function() {
return {
link: function($scope, element, attrs) {
// Trigger when number of children changes,
// including by directives like ng-repeat
var watch = $scope.$watch(function() {
return element.children().length;
}, function() {
// Wait for templates to render
$scope.$evalAsync(function() {
// Finally, directives are evaluated
// and templates are renderer here
var children = element.children();
console.log(children);
});
});
},
};
});
Hope this can help you prevent some struggle.
希望这可以帮助您避免一些斗争。
回答by Mark Rajcok
Following Misko's advice, if you want async operation, then instead of $timeout() (which doesn't work)
按照 Misko 的建议,如果你想要异步操作,那么代替 $timeout() (这不起作用)
$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);
use $evalAsync() (which does work)
使用 $evalAsync() (确实有效)
$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );
Fiddle. I also added a "remove row of data" link that will modify $scope.assignments, simulating a change to the data/model -- to show that changing the data works.
小提琴。我还添加了一个“删除数据行”链接,该链接将修改 $scope.assignments,模拟对数据/模型的更改——以表明更改数据有效。
The Runtimesection of the Conceptual Overview page explains that evalAsync should be used when you need something to occur outside the current stack frame, but before the browser renders. (Guessing here... "current stack frame" probably includes Angular DOM updates.) Use $timeout if you need something to occur after the browser renders.
概念概述页面的运行时部分解释了当您需要在当前堆栈帧之外但在浏览器呈现之前发生某些事情时,应使用 evalAsync。(在这里猜测...“当前堆栈帧”可能包括 Angular DOM 更新。)如果您需要在浏览器呈现后发生某些事情,请使用 $timeout。
However, as you already found out, I don't think there is any need for async operation here.
但是,正如您已经发现的那样,我认为这里不需要异步操作。
回答by fuzion9
I have found the simplest (cheap and cheerful) solution is simply add an empty span with ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()" to the end of the last element rendered. This function will be run when to check if the span element should be displayed. Execute any other code in this function.
我发现最简单(便宜又好用)的解决方案就是在渲染的最后一个元素的末尾添加一个带有 ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()" 的空跨度。此函数将在检查是否应显示 span 元素时运行。执行此函数中的任何其他代码。
I realize this is not the most elegant way to do things, however, it works for me...
我意识到这不是最优雅的做事方式,但是,它对我有用......
I had a similar situation, though slightly reversed where I needed to remove a loading indicator when an animation began, on mobile devices angular was initializing much faster than the animation to be displayed, and using an ng-cloak was insufficient as the loading indicator was removed well before any real data was displayed. In this case I just added the my return 0 function to the first rendered element, and in that function flipped the var that hides the loading indicator. (of course I added an ng-hide to the loading indicator triggered by this function.
我遇到了类似的情况,虽然在动画开始时需要移除加载指示器的情况略有不同,但在移动设备上,angular 的初始化速度比要显示的动画快得多,并且使用 ng-cloak 是不够的,因为加载指示器是在显示任何真实数据之前删除。在这种情况下,我只是将 return 0 函数添加到第一个呈现的元素,并在该函数中翻转了隐藏加载指示器的 var。(当然,我在这个函数触发的加载指示器中添加了一个 ng-hide。
回答by Misko Hevery
I think you are looking for $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync
我认为您正在寻找 $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync
回答by Sergio Ceron
Finally i found the solution, i was using a REST service to update my collection. In order to convert datatable jquery is the follow code:
最后我找到了解决方案,我正在使用 REST 服务来更新我的收藏。为了转换数据表jquery是以下代码:
$scope.$watchCollection( 'conferences', function( old, nuew ) {
if( old === nuew ) return;
$( '#dataTablex' ).dataTable().fnDestroy();
$timeout(function () {
$( '#dataTablex' ).dataTable();
});
});
回答by mgrimmenator
i've had to do this quite often. i have a directive and need to do some jquery stuff after model stuff is fully loaded into the DOM. so i put my logic in the link: function of the directive and wrap the code in a setTimeout(function() { ..... }, 1); the setTimout will fire after the DOM is loaded and 1 milisecond is the shortest amount of time after DOM is loaded before code would execute. this seems to work for me but i do wish angular raised an event once a template was done loading so that directives used by that template could do jquery stuff and access DOM elements. hope this helps.
我不得不经常这样做。我有一个指令,需要在模型内容完全加载到 DOM 后做一些 jquery 的事情。所以我把我的逻辑放在链接中:指令的函数并将代码包装在 setTimeout(function() { ..... }, 1); setTimout 将在 DOM 加载后触发,1 毫秒是 DOM 加载后代码执行之前的最短时间。这似乎对我有用,但我确实希望 angular 在模板加载完成后引发一个事件,以便该模板使用的指令可以执行 jquery 内容并访问 DOM 元素。希望这可以帮助。
回答by Anthony O.
You can also create a directive that runs your code in the link function.
您还可以创建一个指令,在链接函数中运行您的代码。
请参阅stackoverflow 回复。
回答by Xchai
Neither $scope.$evalAsync() or $timeout(fn, 0) worked reliably for me.
$scope.$evalAsync() 或 $timeout(fn, 0) 对我来说都不可靠。
I had to combine the two. I made a directive and also put a priority higher than the default value for good measure. Here's a directive for it (Note I use ngInject to inject dependencies):
我不得不将两者结合起来。我做了一个指令,并把优先级高于默认值以获得良好的度量。这是它的指令(注意我使用 ngInject 来注入依赖项):
app.directive('postrenderAction', postrenderAction);
/* @ngInject */
function postrenderAction($timeout) {
// ### Directive Interface
// Defines base properties for the directive.
var directive = {
restrict: 'A',
priority: 101,
link: link
};
return directive;
// ### Link Function
// Provides functionality for the directive during the DOM building/data binding stage.
function link(scope, element, attrs) {
$timeout(function() {
scope.$evalAsync(attrs.postrenderAction);
}, 0);
}
}
To call the directive, you would do this:
要调用指令,您可以这样做:
<div postrender-action="functionToRun()"></div>
If you want to call it after an ng-repeat is done running, I added an empty span in my ng-repeat and ng-if="$last":
如果你想在运行完 ng-repeat 后调用它,我在我的 ng-repeat 和 ng-if="$last" 中添加了一个空跨度:
<li ng-repeat="item in list">
<!-- Do stuff with list -->
...
<!-- Fire function after the last element is rendered -->
<span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
回答by Ehsan88
I came with a pretty simple solution. I'm not sure whether it is the correct way to do it but it works in a practical sense. Let's directly watch what we want to be rendered. For example in a directive that includes some ng-repeat
s, I would watch out for the length of text (you may have other things!) of paragraphs or the whole html. The directive will be like this:
我带来了一个非常简单的解决方案。我不确定这是否是正确的方法,但它在实际意义上是有效的。我们直接看我们要渲染什么。例如,在包含一些ng-repeat
s的指令中,我会注意段落或整个 html 的文本长度(您可能还有其他内容!)。该指令将是这样的:
.directive('myDirective', [function () {
'use strict';
return {
link: function (scope, element, attrs) {
scope.$watch(function(){
var whole_p_length = 0;
var ps = element.find('p');
for (var i=0;i<ps.length;i++){
if (ps[i].innerHTML == undefined){
continue
}
whole_p_length+= ps[i].innerHTML.length;
}
//it could be this too: whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
console.log(whole_p_length);
return whole_p_length;
}, function (value) {
//Code you want to be run after rendering changes
});
}
}]);
NOTEthat the code actually runs after rendering changesrather complete rendering. But I guess in most cases you can handle the situations whenever rendering changes happen. Also you could think of comparing this p
s length (or any other measure) with your model if you want to run your code only onceafter rendering completed. I appreciate any thoughts/comments on this.
请注意,代码实际上在渲染更改而不是完整渲染后运行。但我想在大多数情况下,只要发生渲染更改,您就可以处理这些情况。p
如果您想在渲染完成后仅运行一次代码,您也可以考虑将此s 长度(或任何其他度量)与您的模型进行比较。我感谢对此的任何想法/评论。