AngularJS:AngularJS渲染模板后如何运行其他代码?


182

我在DOM中有一个Angular模板。当我的控制器从服务中获取新数据时,它将更新$ scope中的模型,然后重新呈现模板。到目前为止一切都很好。

问题是,在模板重新呈现并且在DOM中(在本例中为jQuery插件)之后,我还需要做一些额外的工作。

似乎应该有一个事件要听,例如AfterRender,但我找不到任何此类事件。也许一条指令是一个可行的方法,但是它似乎还为时过早。

这是一个概述我的问题的jsFiddleFiddle-AngularIssue

==更新==

基于有用的评论,我相应地切换到了处理DOM操作的指令,并在指令内部实现了$ watch模型。但是,我仍然遇到相同的基本问题。$ watch事件中的代码在模板被编译并插入DOM之前触发,因此,jQuery插件始终在评估一个空表。

有趣的是,如果我删除了异步调用,那么整个程序就可以正常工作,因此这是朝正确方向迈出的一步。

这是我更新的小提琴,以反映这些更改:http : //jsfiddle.net/uNREn/12/


这似乎类似于我的问题。stackoverflow.com/questions/11444494/…。也许那里的东西可以帮上忙。
dnc253'9

Answers:


39

这篇文章很旧,但是我将您的代码更改为:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

参见jsfiddle

希望对您有帮助


谢谢,这非常有帮助。这对我来说是最好的解决方案,因为它不需要控制器进行dom操作,并且从真正的异步服务响应中更新数据时仍然可以使用(并且我不必在控制器中使用$ evalAsync)。
gorebash13年

9
是否已弃用?当我尝试此操作时,它实际上呈现DOM 之前调用。它依赖于影子dom还是其他东西?
谢恩2014年

我是Angular的新手,我看不到如何针对我的具体情况实现此答案。我一直在寻找各种答案和猜测,但这是行不通的。我不确定“监视”功能应该监视什么。我们的角度应用程序具有各种不同的指令,每个页面的指令会有所不同,那么我如何知道要“观看”的内容呢?当然,有一种简单的方法可以在页面呈现完毕后触发一些代码?
moosefetcher

63

首先,正确处理渲染的地方是指令。我的建议是通过像这样的指令来包装DOM操作jQuery插件。

我遇到了同样的问题,并提出了这个代码段。它使用$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);
                });
            });
        },
    };
});

希望这可以帮助您避免一些挣扎。


这也许表明很明显,但是如果这对您不起作用,请检查链接函数/控制器中的异步操作。我认为$ evalAsync无法将这些考虑在内。
LOAS

值得注意的是,您还可以将link函数与组合templateUrl
Wtower '16

35

遵循Misko的建议,如果您要进行异步操作,则可以使用$ timeout()代替(无效)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

使用$ evalAsync()(有效)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

小提琴。我还添加了“删除数据行”链接,该链接将修改$ scope.assignments,模拟对数据/模型的更改-以表明更改数据有效。

“ 概念概述”页面的“ 运行时”部分说明,当您需要在当前堆栈框架之外但在浏览器呈现之前发生某些事情时,应使用evalAsync。(在这里猜测...“当前堆栈框架”可能包括Angular DOM更新。)如果您需要在浏览器渲染后进行某些操作,请使用$ timeout。

但是,您已经发现,我认为这里不需要进行异步操作。


4
$scope.$evalAsync($scope.assignmentsLoaded(data));海事组织没有任何意义。的参数$evalAsync()应该是一个函数。在您的示例中,您是在应注册函数以供以后执行时调用该函数。
hgoebl 2014年

@ hgoebl,$ evalAsync的参数可以是函数或表达式。在这种情况下,我使用了一个表达式。但是您是对的,该表达式会立即执行。我修改了答案,将其包装在函数中以供以后执行。感谢您抓住这一点。
Mark Rajcok 2014年

26

我发现最简单(便宜和愉快)的解决方案是,在显示的最后一个元素的末尾添加一个ng-show =“ someFunctionThatAlwaysReturnsZeroOrNothing()”的空跨度。当检查是否应显示span元素时,将运行此功能。执行此功能中的任何其他代码。

我意识到这不是最优雅的做事方式,但是它对我有用。

我有一个类似的情况,尽管略有相反,当动画开始时我需要移除加载指示器,但在移动设备上,angular的初始化速度要快于要显示的动画,并且使用ng-cloak不足,因为加载指示器在显示任何真实数据之前将其移除。在这种情况下,我只是将我的return 0函数添加到第一个呈现的元素,然后在该函数中翻转了隐藏加载指示符的var。(当然,我在此功能触发的加载指示器中添加了ng-hide隐藏。


3
当然这是一个hack,但这正是我所需要的。强制我的代码在渲染后完全运行(或非常接近完全运行)。在理想的世界中,我不会这么做,但是我们在这里……
安德鲁(Andrew)

我需要在$anchorScrollDOM渲染后调用内置服务,而这似乎无济于事。顽强但有效。
Pat Migliaccio

ng-init是更好的选择
Flying Gambit

1
ng-init可能并不总是有效。例如,我有一个ng-repeat,它将多个图像添加到dom。如果要在ng-repeat中添加每个图像后调用函数,则必须使用ng-show。
Michael Bremerkamp '17


4

终于我找到了解决方案,我使用了REST服务来更新我的收藏集。为了转换数据表,jquery是以下代码:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });

3

我不得不经常这样做。我有一条指令,并且需要在模型内容完全加载到DOM之后做一些jquery内容。所以我把我的逻辑放在指令的link:函数中,并将代码包装在setTimeout(function(){.....},1);中。setTimout将在加载DOM之后触发,并且1毫秒是DOM加载之后代码执行之前的最短时间。这似乎对我有用,但是我希望angular一旦模板完成加载就引发一个事件,以便该模板使用的指令可以执行jquery任务并访问DOM元素。希望这可以帮助。



2

$ scope。$ evalAsync()或$ timeout(fn,0)都不对我可靠。

我必须将两者结合起来。我制定了一条指令,并且还设置了比默认值更高的优先级以作好衡量。这是它的指令(请注意,我使用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);
    }
}

要调用该指令,您可以这样做:

<div postrender-action="functionToRun()"></div>

如果要在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>

1

在某些情况下,您更新服务并重定向到新视图(页面),然后在更新服务之前加载指令,然后可以使用$ rootScope。$ broadcast(如果$ watch或$ timeout失败)

视图

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

控制者

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

指示

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);
        }
   };
});

1

我提供了一个非常简单的解决方案。我不确定这是否是正确的方法,但是在实际意义上是可行的。让我们直接看一下我们要渲染的内容。例如,在包含some ng-repeat的指令中,我会注意段落或整个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
            });
        }
}]);

注意,代码实际上在渲染更改后运行,而不是完整渲染。但是我想在大多数情况下,只要发生渲染更改,您就可以处理这种情况。p如果只想在渲染完成后只运行一次代码,则可以考虑将此长度(或任何其他度量)与模型进行比较。我对此表示感谢。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.