在 AngularJS 完成渲染 HTML 后运行 jQuery 代码

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16935766/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 18:06:28  来源:igfitidea点击:

Run jQuery code after AngularJS completes rendering HTML

javascriptjqueryangularjs

提问by Dobby007

In controller I get some JSON data using $http or $resource services. Then I write this data in $scope and AngularJS updates HTML structure of the page. My problem is that I need to know what is the new size (width and height) of the list (I mean, HTML DOM element) that is filled with Angular ng-repeatdirective. Consequently, I have to run javascript code right after Angular finishes updating DOM structure. What is the proper way to do it? I have searched internet over the last four hours but I couldn't find any solution to my problem.

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

This is how I receive JSON data:

这是我接收 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

});

And this is what happens in init()function:

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

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

I know that there must be something easy to solve this problem but I can't just find it now. Thanks in advance.

我知道一定有一些容易解决这个问题的方法,但我现在找不到它。提前致谢。

采纳答案by Oliver

Actually in this case the angular way is not the easy way but the only right way :)

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

You have to write a directive and attach to the element you want to know the height of. And from the controller you $broadcast an event, the directive'll catch the event and there you can do the DOM manipulation. NEVER in the controller.

您必须编写一个指令并附加到您想知道其高度的元素。并且从控制器您 $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);
            })
        }
    };
}]);

回答by Kjir

Olivér's answer is good, but has an issue: if you forget to broadcast the event, your javascript will not run whereas your data might have changed. Another solution would be to watch for changes on the scope, for instance:

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>

This way the javascript functions are called every timethe data changes without any need for a custom event.

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

回答by user3484816

Another suggestion to work with JQuery. Had to work this through for a grid that was generated in a directive. I wanted to scroll to a specific row in the grid. Use $emit to broadcast from directive to parent controller:

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

In Controller:

在控制器中:

    ['$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);
        }

In directive

在指令中

.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');
            }]


        };
    }])

And this is a snippet of the grid directive html template:

这是网格指令 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>

the list and selected parameters are injected from the html that is using the directive

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

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

回答by bresleveloper

I would like to add another answer, since the preceding answers takes it that the code needed to run after the ngRepeat is done is an angular code, which in that case all answers above give a great and simple solution, some more generic than others, and in case its important the digest life cycle stage you can take a look at Ben Nadel's blog about it, with the exception of using $parse instead of $eval.

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

But in my experience, as the OP states, it is usually running some jQuery plugins or methods on the finally compiled DOM, which in that case I found that the most simple solution is to create a directive with a setTimeout, since the setTimeoutfunction gets pushed to the end of the queue of the browser, its always right after everything is done in angular, usually ng-repeatwhich continues after it's parents postLinking function

但根据我的经验,正如 OP 所述,它通常在最终编译的 DOM 上运行一些 jQuery 插件或方法,在这种情况下,我发现最简单的解决方案是使用 a 创建指令setTimeout,因为setTimeout函数被推送到浏览器队列的末尾,它总是在一切都以 angular 完成ng-repeat后立即生效,通常在其父 postLinking 函数之后继续

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

For whoever wondering why not to use $timeout, its that it causes another digest cycle that is completely unnecessary.

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

EDIT:

编辑:

Thanx to drzaus for the link to how to use $timeout without causing digest http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/

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