Run jQuery code after AngularJS completes rendering HTML

前端 未结 4 513
别跟我提以往
别跟我提以往 2020-12-02 08:08

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

相关标签:
4条回答
  • 2020-12-02 08:48

    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:

    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 time the data changes without any need for a custom event.

    0 讨论(0)
  • 2020-12-02 08:50

    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:

    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:

     <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

    <run-grid list="list" selected="selectedActionID"></run-grid>
    
    0 讨论(0)
  • 2020-12-02 09:02

    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.

    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);
                })
            }
        };
    }]);
    
    0 讨论(0)
  • 2020-12-02 09:02

    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.

    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 setTimeout function gets pushed to the end of the queue of the browser, its always right after everything is done in angular, usually ng-repeat which continues after it's parents postLinking function

    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.

    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/

    0 讨论(0)
提交回复
热议问题