AngularJS完成呈现HTML后运行jQuery代码


69

在控制器中,我使用$ http或$ resource服务获取一些JSON数据。然后,我将此数据写入$ scope,AngularJS更新页面的HTML结构。我的问题是我需要知道用Angularng-repeat指令填充的列表(我的意思是HTML DOM元素)的新大小(宽度和高度)是多少。因此,我必须在Angular完成DOM结构更新后立即运行javascript代码。正确的做法是什么?我过去四个小时都在搜索互联网,但是找不到解决我问题的方法。

这就是我接收JSON数据的方式:

var tradesInfo = TradesInfo.get({}, function(data){
    console.log(data);
    $scope.source.profile = data.profile;
            $scope.trades = $scope.source.profile.trades;
        $scope.activetrade = $scope.trades[0];
        $scope.ready = true;


    init();  //I need to call this function after update is complete

});

这就是init()函数中发生的事情:

function init(){
    alert($('#wrapper').width());
    alert($('#wrapper').height());
}

我知道必须有一些容易解决的问题,但我现在不能立即找到它。提前致谢。


你不能 一样简单。可能会有意外的$ compile调用,任意数量的指令,它们自己操作DOM并设置要在$ timeouts之后完成的事情... AngularJS系统的体系结构精美,因此各部分的行为独立,这对工资。只是找到另一种方法。
重写

1
我认为无论如何一定有可能做。我的意思是,带有指令的dom可以调用编译功能或执行其他无法预料的事情这一事实无法阻止它,恕我直言。
Dobby007

很抱歉将旧问题变为现实,但是您可以分享#wrapper的内容吗?您可能只是遍历交易?
FrEaKmAn

Answers:


64

实际上,在这种情况下,成角度的方法不是简单的方法,而是唯一正确的方法:)

您必须编写一条指令并将其附加到要知道其高度的元素上。然后从控制器$ broadcast一个事件,指令将捕获该事件,然后您就可以执行DOM操作。切勿在控制器中。

var tradesInfo = TradesInfo.get({}, function(data){
    console.log(data);
    $scope.source.profile = data.profile;
    ...

    $scope.$broadcast('dataloaded');
});


directive('heightStuff', ['$timeout', function ($timeout) {
    return {
        link: function ($scope, element, attrs) {
            $scope.$on('dataloaded', function () {
                $timeout(function () { // You might need this timeout to be sure its run after DOM render.
                    element.width()
                    element.height()
                }, 0, false);
            })
        }
    };
}]);

非常感谢你。有用。您能解释一下为什么必须等待$ ms超时为0ms吗?我问是因为我试图忽略它,在这种情况下,宽度和高度不正确。带超时功能的Javascript行为是什么?它是否在其他线程中运行代码?
Dobby007 2013年

不,实际上没有其他线程可以解释为什么浏览器会骗人。它们仅在闭包的和处进行dom回流,因此,如果需要元素的尺寸,则不能使用它们,因为它们尚未呈现。您必须推迟执行测量例程,这就是setTimeout为0的地方。它会告诉浏览器:在执行当前关闭并已注册超时后运行我。非常基本的示例:jsfiddle.net/Zmetser/VfTar
Oliver

8
那是不对的。当您尝试访问相关的样式属性时,浏览器将立即重新渲染该块-因此,在这种情况下,通常情况下您无需调用setTimeout-请参见演示jsfiddle.net/SLTpE。但是AngularJS可以采用另一种方式-当您更改模型而不是浏览器时,Angular不会立即更新DOM
amakhrov 2013年

2
我相信您需要致电$scope.$on(...)而不是$scope.on(...)(缺少一个美元符号)。
lanoxx

确保广播事件的大小写正确匹配('dataLoaded'!=='dataloaded')
Andy Ford

7

Olivér的回答很好,但是有一个问题:如果您忘记广播事件,则JavaScript将无法运行,而数据可能已更改。另一个解决方案是监视范围的变化,例如:

var tradesInfo = TradesInfo.get({}, function(data) {
  console.log(data);
  $scope.profile = data.profile;
  // ...
});


directive('heightStuff', ['$timeout',
  function($timeout) {
    return {
      scope: {
        myData: '='
      },
      link: function($scope, element, attrs) {
        $scope.$watch('myData', function() {
          $timeout(function() { // You might need this timeout to be sure its run after DOM render.
            element.width()
            element.height()
          }, 0, false);
        })
      }
    };
  }
]);
<div height-stuff my-data="profile"></div>

这样,每次数据更改都将调用javascript函数,而无需任何自定义事件。


5

另一个建议与JQuery一起使用。必须通过在指令中生成的网格来解决这一问题。我想滚动到网格中的特定行。使用$ emit从指令广播到父控制器:

在控制器中:

    ['$timeout',function($timeout){
...
 $scope.$on('dataloaded', function () {
            $timeout(function () { // You might need this timeout to be sure its run after DOM render.
                $scope.scrollToPosition();
            }, 0, false);
        });
        $scope.scrollToPosition = function () {
            var rowpos = $('#row_' + $scope.selectedActionID, "#runGrid").position();
            var tablepost = $('table', "#runGrid").position();
            $('#runGrid').scrollTop(rowpos.top - tablepost.top);
        }

在指令中

.directive('runGrid',['$timeout', function ($timeout) {
        // This directive generates the grip of data
        return {
            restrict: 'E',  //DOM Element
            scope: {    //define isolated scope
                list: '=',   //use the parent object
                selected: "="
            },

            templateUrl: '/CampaignFlow/StaticContent/Runs/run.grid.0.0.0.0.htm',  //HTML template URL

            controller: ['$scope', function ($scope) {  //the directive private controller, whith its private scope
                //$scope.statusList = [{ data_1: 11, data_2: 12 }, { data_1: 21, data_2: 22 }, { data_1: 31, data_2: 32 }];
                //Controller contains sort functionallity

                $scope.sort = { column: null, direction: 1 }
                $scope.column = null;
                $scope.direction = "asc";
                $scope.sortColumn = function (id) {
                    if(id!=$scope.column) {
                        $scope.column = id;
                        $scope.direction = "asc";
                    } else {
                        $scope.column = null;
                    }
                }
                $scope.toggleDir = function () {
                    $scope.direction = ($scope.direction == "asc") ? "desc" : "asc";
                }
               $scope.$emit('dataloaded');
            }]


        };
    }])

这是grid指令html模板的代码段:

 <div style="overflow-y:auto;height: 200px;" id="runGrid">
            <table class="table table-striped" style="table-layout:fixed">
           <tbody>
                <tr  ng-repeat="status in list" id="row_{{status.action_id}}" ng-class="(status.action_id==selected)?'selected':''">
                    <td>

列表和所选参数是从使用指令的html注入的

<run-grid list="list" selected="selectedActionID"></run-grid>

3

我想添加另一个答案,因为前面的答案都认为ngRepeat完成后需要运行的代码是角度代码,在这种情况下,上述所有答案都提供了一个很好而简单的解决方案,其中一些比其他答案更通用,如果它对摘要生命周期阶段很重要,那么您可以查看Ben Nadel的博客,但使用$ parse而不是$ eval除外。

但是以我的经验,正如OP所言,它通常在最终编译的DOM上运行一些jQuery插件或方法,在这种情况下,我发现最简单的解决方案是使用来创建指令setTimeout,因为该setTimeout函数被推送至浏览器队列的末尾,它总是在完成所有操作后一直保持正确的角度,通常ng-repeat在其父级postLinking函数之后继续

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

对于想知道为什么不使用$ timeout的人,它会导致另一个完全不需要的摘要循环。

编辑:

感谢drzaus提供有关如何使用$ timeout而不引起摘要的链接http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/


应该使用angular的$timeout
drzaus

我已经写了为什么我不认为在最后一行中使用$ timeout的原因,请再次阅读。
bresleveloper

很奇怪,不知道为什么我没看到。但是,如果您不希望摘要循环,则只需致电即可invokeApply=false。否则,有很多 参数,使用什么角度写的开发者来代替setTimeout
drzaus
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.