Comment puis-je exécuter une directive une fois le rendu du dom terminé?

115

J'ai un problème apparemment simple sans solution apparente (en lisant la documentation Angular JS) .

J'ai une directive Angular JS qui effectue des calculs basés sur la hauteur d'autres éléments DOM pour définir la hauteur d'un conteneur dans le DOM.

Quelque chose de similaire se passe dans la directive:

return function(scope, element, attrs) {
    $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
}

Le problème est que lorsque la directive s'exécute, elle $('site-header')est introuvable, renvoyant un tableau vide au lieu de l'élément DOM enveloppé jQuery dont j'ai besoin.

Y a-t-il un rappel que je peux utiliser dans ma directive qui ne fonctionne qu'après le chargement du DOM et que je peux accéder à d'autres éléments DOM via les requêtes de style de sélecteur jQuery normales?

Jannis
la source
1
Vous pouvez utiliser scope. $ On () et scope. $ Emit () pour utiliser des événements personnalisés. Je ne sais pas si c'est la bonne approche / recommandée.
Tosh

Réponses:

137

Cela dépend de la façon dont votre $ ('site-header') est construit.

Vous pouvez essayer d'utiliser $ timeout avec un délai de 0. Quelque chose comme:

return function(scope, element, attrs) {
    $timeout(function(){
        $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
    });        
}

Explications comment ça marche: un , deux .

N'oubliez pas d'injecter $timeoutdans votre directive:

.directive('sticky', function($timeout)
Artem Andreev
la source
5
Merci, j'ai essayé de faire fonctionner cela pendant des siècles jusqu'à ce que je réalise que je n'étais pas passé $timeoutdans la directive. Doh. Tout fonctionne maintenant, bravo.
Jannis
5
Oui, vous devez passer $timeoutà une directive comme celle-ci:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov
19
Vos explications liées expliquent pourquoi l'astuce du délai d'expiration fonctionne en JavaScript, mais pas dans le contexte d'AngularJS. Extrait de la documentation officielle : " [...] 4. La file d'attente $ evalAsync est utilisée pour planifier le travail qui doit se produire en dehors du cadre de pile actuel, mais avant le rendu de la vue du navigateur. Cela se fait généralement avec setTimeout (0) , mais l'approche setTimeout (0) souffre de lenteur et peut provoquer un scintillement de la vue puisque le navigateur restitue la vue après chaque événement. [...] "(c'est moi qui souligne)
Alberto
12
Je suis confronté à un problème similaire et j'ai constaté qu'il me fallait environ 300 ms pour permettre au DOM de se charger avant d'exécuter ma directive. Je n'aime vraiment pas brancher des nombres apparemment arbitraires comme ça. Je suis sûr que les vitesses de chargement du DOM varient en fonction de l'utilisateur. Alors, comment puis-je être sûr que 300 ms fonctionnera pour toute personne utilisant mon application?
keepitreal
5
pas trop content de cette réponse .. alors qu'elle semble répondre à la question de l'OP .. c'est très spécifique à leur cas et sa pertinence par rapport à la forme plus générale du problème (ie exécuter une directive après le chargement d'un dom) n'est pas évident + c'est juste trop hacky .. rien dedans spécifique à propos d'angulaire du tout
abbood
44

Voici comment je le fais:

app.directive('example', function() {

    return function(scope, element, attrs) {
        angular.element(document).ready(function() {
                //MANIPULATE THE DOM
        });
    };

});
rjm226
la source
1
Ne devrait même pas avoir besoin de angular.element car l'élément y est déjà disponible:element.ready(function(){
timhc22
1
L'élément @ timhc22 est une référence au DOMElement qui a déclenché la directive, votre recommandation n'entraînerait pas une référence DOMElement à l'objet de document pages.
tobius
cela ne fonctionne pas correctement. J'obtiens offsetWidth = 0 grâce à cette approche
Alexey Sh.
37

L'auteur n'aura probablement plus besoin de ma réponse. Pourtant, par souci d'exhaustivité, je pense que d'autres utilisateurs pourraient le trouver utile. La meilleure et la plus simple solution consiste à utiliser $(window).load()dans le corps de la fonction retournée. (vous pouvez également utiliserdocument.ready . Cela dépend vraiment si vous avez besoin de toutes les images ou non).

En utilisant $timeout à mon humble avis est une option très faible et peut échouer dans certains cas.

Voici le code complet que j'utiliserais:

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function($scope, $elem, attrs){

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
la source
1
Pouvez-vous expliquer pourquoi il «peut échouer dans certains cas»? De quels cas parlez-vous?
rryter le
6
Vous supposez que jQuery est disponible ici.
Jonathan Cremin
3
@JonathanCremin La sélection de jQuery est le problème en jeu selon l'OP
Nick Devereaux
1
Cela fonctionne très bien, mais s'il y a un article qui construit de nouveaux éléments avec la directive, le chargement de la fenêtre ne se déclenchera pas après le chargement initial et ne fonctionnera donc pas correctement.
Brian Scott
@BrianScott - J'ai utilisé une combinaison de $ (window) .load pour le rendu de page initial (mon cas d'utilisation attendait les fichiers de polices incorporés), puis element.ready pour prendre en charge le changement de vue.
aaaaaa
8

il y a un ngcontentloadedévénement, je pense que tu peux l'utiliser

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function(scope, elem, attrs){

                $$window = $ $window


                init = function(){
                    contentHeight = elem.outerHeight()
                    //do the things
                }

                $$window.on('ngcontentloaded',init)

       }
   }
});
sunderls
la source
21
Pouvez-vous expliquer ce que $ $windowfait?
Catfish
2
ressemble à un coffeescript, peut-être était-il censé être $ ($ window) et $ window injectés dans la directive
mdob
5

Si vous ne pouvez pas utiliser $ timeout en raison de ressources externes et que vous ne pouvez pas utiliser une directive en raison d'un problème spécifique de synchronisation, utilisez la diffusion.

Ajoutez une $scope.$broadcast("variable_name_here");fois que la ressource externe souhaitée ou le contrôleur / directive de longue durée est terminé.

Ajoutez ensuite ce qui suit après le chargement de votre ressource externe.

$scope.$on("variable_name_here", function(){ 
   // DOM manipulation here
   jQuery('selector').height(); 
}

Par exemple dans la promesse d'une requête HTTP différée.

MyHttpService.then(function(data){
   $scope.MyHttpReturnedImage = data.image;
   $scope.$broadcast("imageLoaded");
});

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
la source
2
Cela ne résoudra pas le problème, car les données chargées ne signifient pas qu'elles sont déjà rendues dans le DOM, même si elles sont dans les variables de portée appropriées liées aux éléments DOM. Il y a un intervalle de temps entre le moment où ils sont chargés dans la portée et la sortie rendue dans le dom.
René Stalder
1

J'ai eu un problème similaire et je souhaite partager ma solution ici.

J'ai le code HTML suivant:

<div data-my-directive>
  <div id='sub' ng-include='includedFile.htm'></div>
</div>

Problème: Dans la fonction de liaison de la directive du div parent, je voulais lancer une requête sur le div # sub enfant. Mais cela m'a juste donné un objet vide car ng-include n'était pas terminé lorsque la fonction de liaison de la directive a été exécutée. J'ai donc d'abord fait une solution de contournement sale avec $ timeout, qui a fonctionné mais le paramètre de délai dépendait de la vitesse du client (personne n'aime ça).

Fonctionne mais sale:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        $timeout(function() {
            //very dirty cause of client-depending varying delay time 
            $('#sub').css(/*whatever*/);
        }, 350);
    };
    return directive;
}]);

Voici la solution propre:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        scope.$on('$includeContentLoaded', function() {
            //just happens in the moment when ng-included finished
            $('#sub').css(/*whatever*/);
        };
    };
    return directive;
}]);

Peut-être que ça aide quelqu'un.

Jaheraho
la source