AngularJS: How to run additional code after AngularJS has rendered a template?
我在DOM中有一个角度模板。当控制器从服务获取新数据时,它会更新$scope中的模型,并重新呈现模板。到目前为止一切都很好。
问题是,在重新呈现模板并在DOM中(在本例中是jquery插件)之后,我还需要做一些额外的工作。
似乎应该有个事件要听,比如AfterRender,但我找不到这样的东西。也许一个指令是一个可行的方法,但它似乎也太早了。
这里有一个J小提琴,概括了我的问题:小提琴角度
=更新=
基于有用的注释,我相应地切换到一个处理DOM操作的指令,并在该指令中实现了一个$watch模型。但是,我仍然有相同的基本问题;$watch事件中的代码在模板编译并插入到dom之前激发,因此jquery插件总是评估一个空表。
有趣的是,如果我移除异步调用,整个过程就可以正常工作,所以这是朝着正确的方向迈出的一步。
以下是我更新的小提琴以反映这些变化:http://jsfiddle.net/unren/12/
首先,正确处理渲染的地方是指令。我的建议是用像这样的指令包装dom操作jquery插件。
我也遇到了同样的问题,我想出了这个片段。它使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); }); }); }, }; }); |
希望这能帮助你避免一些斗争。
这篇文章很旧,但我将您的代码改为:
1 2 3 4 5 6 | scope.$watch("assignments", function (value) {//I change here var val = value || null; if (val) element.dataTable({"bDestroy": true}); }); } |
看JSfDeld.
希望对你有帮助
按照misko的建议,如果您想要异步操作,那么就不要使用$timeout()(这不起作用)
1 | $timeout(function () { $scope.assignmentsLoaded(data); }, 1000); |
使用$evalasync()(有效)
1 | $scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } ); |
小提琴。我还添加了一个"删除数据行"链接,它将修改$scope.assignments,模拟对数据/模型的更改,以显示更改数据是有效的。
概念性概述页面的运行时部分解释了当需要在当前堆栈框架之外但在浏览器呈现之前发生某些事情时,应使用evalasync。(猜猜看……"当前堆栈帧"可能包括角度DOM更新。)如果需要在浏览器呈现后发生某些事情,请使用$timeout。
但是,正如您已经发现的,我认为这里不需要异步操作。
我发现最简单(便宜又愉快)的解决方案就是在最后一个渲染元素的末尾添加一个带有ng show="somefunctionthat alwaysreturnszeronornothing()"的空跨度。此函数将在检查是否应显示跨度元素时运行。执行此函数中的任何其他代码。
我意识到这不是最优雅的做事方式,但是,它对我很有用…
我也遇到过类似的情况,虽然在动画开始时需要移除一个加载指示器的位置稍微颠倒了一点,但在移动设备上,Angular的初始化速度比要显示的动画快得多,并且使用NG斗篷是不够的,因为加载指示器在显示任何真实数据之前就被移除了。在这种情况下,我只是将返回0函数添加到第一个呈现元素中,然后在该函数中翻转了隐藏加载指示器的var。(当然,我在这个函数触发的加载指示器中添加了一个ng hide。
我想你在找$evalasync http://docs.angularjs.org/api/ng.$rootscope.scope$evalasync
最后我找到了解决方案,我使用了一个REST服务来更新我的收藏。为了转换数据表,jquery的代码如下:
1 2 3 4 5 6 7 | $scope.$watchCollection( 'conferences', function( old, nuew ) { if( old === nuew ) return; $( '#dataTablex' ).dataTable().fnDestroy(); $timeout(function () { $( '#dataTablex' ).dataTable(); }); }); |
我不得不经常这样做。我有一个指令,需要在模型资料完全加载到DOM之后做一些jquery资料。因此,我将逻辑放在指令的link:function中,并将代码包装在setTimeout(function()….)中。},1);在加载DOM之后将触发setimout,并且在加载DOM之后1毫秒是执行代码之前最短的时间。这似乎对我有用,但我确实希望Angular在加载完模板后引发一个事件,以便该模板使用的指令可以执行jquery操作并访问dom元素。希望这有帮助。
$scope.$evalasync()和$timeout(fn,0)对我来说都不可靠。
我不得不把两者结合起来。我做了一个指令,并将优先级设置为高于良好度量的默认值。这是它的一个指令(注意我使用nginject来注入依赖项):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); } } |
要调用该指令,可以执行以下操作:
1 |
如果您想在运行完ng repeat后调用它,我在ng repeat和ng if="$last"中添加了一个空跨度:
1 2 3 4 5 6 7 8 | <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> |
您还可以创建一个在link函数中运行代码的指令。
请参阅stackoverflow回复。
我提出了一个非常简单的解决方案。我不确定这是否是正确的方法,但它在实际意义上起作用。让我们直接看看我们想要呈现什么。例如,在包含一些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | .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 }); } }]); |
请注意,代码实际上在呈现更改后运行,而不是完全呈现。但我想在大多数情况下,只要渲染发生更改,您就可以处理这些情况。另外,如果您只想在渲染完成后运行一次代码,可以考虑将此
在某些情况下,如果更新服务并重定向到新视图(页),然后在更新服务之前加载指令,则可以使用$rootscope.$broadcast(如果$watch或$timeout失败)
视图
1 | <service-history log="log" data-ng-repeat="log in requiedData"></service-history> |
控制器
1 2 3 4 5 6 7 8 9 10 | app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) { $scope.$on('$viewContentLoaded', function () { SomeSerive.getHistory().then(function(data) { $scope.requiedData = data; $rootScope.$broadcast("history-updation"); }); }); }]); |
指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | app.directive("serviceHistory", function() { return { restrict: 'E', replace: true, scope: { log: '=' }, link: function($scope, element, attrs) { function updateHistory() { if(log) { //do something } } $rootScope.$on("history-updation", updateHistory); } }; }); |
您可以使用AngularUI实用程序的"jquery passthrough"模块。我成功地将jquery touch carousel插件绑定到一些图像上,这些图像是我从Web服务中检索到异步的,并用ng repeat呈现出来的。