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

时间:2012-09-06 16:19:22

标签: javascript jquery dom angularjs

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

问题是我需要在重新呈现模板之后做一些额外的工作并且在DOM中(在这种情况下是一个jQuery插件)。

似乎应该有一个事件要听,比如AfterRender,但我找不到任何这样的事情。也许一个指令是一种方法,但它似乎也太早了。

这是一个概述我的问题的jsFiddle:Fiddle-AngularIssue

==更新==

根据有用的评论,我相应地切换到一个指令来处理DOM操作,并在指令中实现了一个模型$ watch。但是,我仍然有同样的基础问题; $ watch事件中的代码在模板编译并插入DOM之前触发,因此,jquery插件总是在评估一个空表。

有趣的是,如果我删除异步调用,整个过程就可以了,所以这是朝着正确方向迈出的一步。

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

12 个答案:

答案 0 :(得分: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);
                });
            });
        },
    };
});

希望这可以帮助你防止一些挣扎。

答案 1 :(得分:39)

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

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

请参阅jsfiddle

我希望它可以帮到你

答案 2 :(得分:34)

根据Misko的建议,如果你想要异步操作,那么而不是$ timeout()(这不起作用)

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

使用$ evalAsync()(确实有效)

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

Fiddle。我还添加了一个“删除数据行”链接,它将修改$ scope.assignments,模拟对数据/模型的更改 - 以显示更改数据的工作。

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

但是,正如您已经发现的那样,我认为这里不需要异步操作。

答案 3 :(得分:26)

我发现最简单(便宜又开朗)的解决方案就是在渲染的最后一个元素的末尾添加一个带有ng-show =“someFunctionThatAlwaysReturnsZeroOrNothing()”的空跨度。检查是否应显示span元素时,将运行此函数。执行此函数中的任何其他代码。

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

我有类似的情况,虽然在动画开始时需要移除加载指示器的地方稍微颠倒,但在移动设备上角度初始化比要显示的动画要快得多,并且使用ng-cloak不足以作为在显示任何实际数据之前,已经删除了加载指示器。在这种情况下,我只是将我的返回0函数添加到第一个渲染元素,并在该函数中翻转隐藏加载指示符的var。 (当然我在这个功能触发的加载指示器中添加了一个ng-hide。

答案 4 :(得分:7)

答案 5 :(得分:4)

最后我找到了解决方案,我正在使用REST服务来更新我的收藏。为了转换datatable jquery,请遵循以下代码:

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

答案 6 :(得分:3)

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

答案 7 :(得分:2)

您还可以创建一个在链接函数中运行代码的指令。

请参阅that stackoverflow reply

答案 8 :(得分: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>

答案 9 :(得分:1)

我带来了一个非常简单的解决方案。我不确定这是否是正确的方法,但它在实际意义上有效。让我们直接观察我们想要呈现的内容。例如,在包含一些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长度(或任何其他度量)与模型进行比较。 感谢您对此提出的任何想法/意见。

答案 10 :(得分:0)

您可以使用angular-ui utils的“jQuery Passthrough”模块​​。我成功地将jQuery touch carousel插件绑定到一些图像,我从Web服务检索异步并使用ng-repeat渲染它们。

答案 11 :(得分:0)

在某些情况下,您更新服务并重定向到新视图(页面),然后在更新服务之前加载指令,那么您可以使用$ 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);
        }
   };
});