web-dev-qa-db-fra.com

Exécuter le code jQuery une fois que AngularJS a terminé le rendu du code HTML

Dans le contrôleur, je reçois des données JSON en utilisant les services $ http ou $ resource. Ensuite, j'écris ces données dans $ scope et AngularJS met à jour la structure HTML de la page. Mon problème est que j'ai besoin de savoir quelle est la nouvelle taille (largeur et hauteur) de la liste (je veux dire, élément HTML DOM) qui est remplie avec la directive Angular ng-repeat Par conséquent, je dois exécuter du code javascript juste après Angular termine la mise à jour de la structure du DOM. Quelle est la bonne façon de le faire? J'ai effectué une recherche sur Internet au cours des quatre dernières heures mais je n’ai pas trouvé. aucune solution à mon problème.

Voici comment je reçois des données 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

});

Et voici ce qui se passe dans la fonction init():

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

Je sais qu'il doit y avoir quelque chose de facile pour résoudre ce problème mais je ne peux pas le trouver maintenant. Merci d'avance.

68
Dobby007

En fait, dans ce cas, le moyen angular n’est pas le moyen le plus simple, mais le seul moyen correct :)

Vous devez écrire une directive et attacher à l'élément dont vous voulez connaître la hauteur. Et à partir du contrôleur vous $ diffusez un événement, la directive le capturera et vous pourrez y effectuer la manipulation DOM. JAMAIS dans le contrôleur.

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);
            })
        }
    };
}]);
63
Oliver

La réponse d'Olivér est bonne, mais a un problème: si vous oubliez de diffuser l'événement, votre javascript ne fonctionnera pas alors que vos données pourraient avoir changé. Une autre solution serait de surveiller les modifications apportées au champ d'application, par exemple:

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>

De cette façon, les fonctions javascript sont appelées à chaque fois les données sont modifiées sans qu'un événement personnalisé soit nécessaire.

7
Kjir

Une autre suggestion de travailler avec JQuery. Il a fallu que cela fonctionne pour une grille générée dans une directive. Je voulais faire défiler une ligne spécifique de la grille. Utilisez $ emit pour diffuser de la directive au contrôleur parent:

Dans le contrôleur:

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

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


        };
    }])

Et ceci est un extrait du modèle html de la directive grid:

 <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>

la liste et les paramètres sélectionnés sont injectés à partir du code HTML utilisant la directive

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

J'aimerais ajouter une autre réponse, car la réponse précédente indique que le code nécessaire pour s'exécuter après l'exécution de ngRepeat est un code angular), qui, dans ce cas, donne une grande solution simple, certaines plus génériques que d’autres, et au cas où c’est important l’étape du cycle de vie, vous pouvez consulter le blog de Ben Nadel à ce sujet, à l’exception de l’utilisation de $ parse au lieu de $ eval.

Mais selon mon expérience, comme l'OP l'indique, il exécute généralement des plugins ou des méthodes jQuery sur le DOM compilé, ce qui m'a amené à penser que la solution la plus simple consiste à créer une directive avec setTimeout, puisque la fonction setTimeout est poussée à la fin de la file d’attente du navigateur, c’est toujours juste après que tout soit terminé en mode angulaire, généralement ng-repeat qui continue après la fonction postlinking de ses parents

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

Pour ceux qui se demandent pourquoi ne pas utiliser $ timeout, c’est qu’il provoque un autre cycle de digestion complètement inutile.

MODIFIER:

Merci à drzaus pour le lien sur la façon d'utiliser $ timeout sans provoquer de résumé http://www.codelord.net/2015/10/14/angular-nitpicking-differences-between-timeout-and-settimeout/

3
bresleveloper