¿Cómo puedo ejecutar una directiva después de que el dom haya terminado de renderizarse?

115

Tengo un problema aparentemente simple sin una solución aparente (al leer los documentos de Angular JS) .

Tengo una directiva Angular JS que hace algunos cálculos basados ​​en la altura de otros elementos DOM para definir la altura de un contenedor en el DOM.

Algo similar a esto está sucediendo dentro de la directiva:

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

El problema es que cuando se ejecuta la directiva, $('site-header')no se puede encontrar, devolviendo una matriz vacía en lugar del elemento DOM envuelto con jQuery que necesito.

¿Hay una devolución de llamada que pueda usar dentro de mi directiva que solo se ejecute después de que se haya cargado el DOM y pueda acceder a otros elementos del DOM a través de las consultas de estilo del selector de jQuery normal?

Jannis
fuente
1
Puede usar scope. $ On () y scope. $ Emit () para usar eventos personalizados. Sin embargo, no estoy seguro de si este es el enfoque correcto / recomendado.
Tosh

Respuestas:

137

Depende de cómo esté construido su $ ('site-header').

Puede intentar usar $ timeout con 0 retraso. Algo como:

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

Explicaciones de cómo funciona: uno , dos .

No olvide inyectar $timeoutsu directiva:

.directive('sticky', function($timeout)
Artem Andreev
fuente
5
Gracias, intenté hacer que esto funcionara durante años hasta que me di cuenta de que no había pasado $timeouta la directiva. Doh. Todo funciona ahora, salud.
Jannis
5
Sí, debe pasar $timeouta una directiva como esta:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov
19
Sus explicaciones vinculadas explican por qué el truco del tiempo de espera funciona en JavaScript, pero no en el contexto de AngularJS. De la documentación oficial : " [...] 4. La cola $ evalAsync se usa para programar el trabajo que debe ocurrir fuera del marco de pila actual, pero antes de que la vista del navegador se procese. Esto generalmente se hace con setTimeout (0) , pero el enfoque setTimeout (0) sufre de lentitud y puede causar que la vista parpadee ya que el navegador muestra la vista después de cada evento. [...] "(énfasis mío)
Alberto
12
Estoy enfrentando un problema similar y he descubierto que necesito alrededor de 300 ms para permitir que el DOM se cargue antes de ejecutar mi directiva. Realmente no me gusta introducir números aparentemente arbitrarios como ese. Estoy seguro de que las velocidades de carga de DOM variarán según el usuario. Entonces, ¿cómo puedo estar seguro de que 300ms funcionará para cualquiera que use mi aplicación?
Keepitreal
5
no estoy muy contento con esta respuesta ... si bien parece responder a la pregunta del OP ... es muy específico para su caso y su relevancia para la forma más general del problema (es decir, ejecutar una directiva después de que se haya cargado un dom) no es obvio + es demasiado hacky ... nada en él específico sobre angular en absoluto
abbood
44

Así es como lo hago:

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

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

});
rjm226
fuente
1
Ni siquiera debería necesitar angular.element porque el elemento ya está disponible allí:element.ready(function(){
timhc22
1
El elemento @ timhc22 es una referencia al DOMElement que activó la directiva, su recomendación no resultaría en una referencia DOMElement al objeto Document de las páginas.
tobius
eso no funciona correctamente. Obtengo offsetWidth = 0 a través de este enfoque
Alexey Sh.
37

Probablemente el autor ya no necesite mi respuesta. Aún así, en aras de la integridad, creo que otros usuarios pueden encontrarlo útil. La mejor y más sencilla solución es utilizarla $(window).load()dentro del cuerpo de la función devuelta. (alternativamente puede usar document.ready. Realmente depende de si necesita todas las imágenes o no).

Usar, $timeouten mi humilde opinión, es una opción muy débil y puede fallar en algunos casos.

Aquí está el código completo que usaría:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
fuente
1
¿Puede explicar por qué "puede fallar en algunos casos"? ¿A qué casos te refieres?
rryter
6
Está asumiendo que jQuery está disponible aquí.
Jonathan Cremin
3
@JonathanCremin La selección de jQuery es el problema en cuestión según el OP
Nick Devereaux
1
Esto funciona muy bien, sin embargo, si hay una publicación que crea nuevos elementos con la directiva, la carga de la ventana no se activará después de la carga inicial y, por lo tanto, no funcionará correctamente.
Brian Scott
@BrianScott: utilicé una combinación de $ (ventana) .load para la representación de la página inicial (mi caso de uso estaba esperando archivos de fuentes incrustados) y luego element.ready para encargarme de cambiar de vista.
aaaaaa
8

hay un ngcontentloadedevento, creo que puedes usarlo

.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
fuente
21
¿Puedes explicar qué $ $windowestá haciendo?
Bagre
2
parece un coffeescript, tal vez estaba destinado a ser $ ($ window) y $ window inyectados en la directiva
mdob
5

Si no puede usar $ timeout debido a recursos externos y no puede usar una directiva debido a un problema específico con el tiempo, use broadcast.

Agregue $scope.$broadcast("variable_name_here");después de que se haya completado el recurso externo deseado o el controlador / directiva de larga ejecución.

Luego agregue lo siguiente después de que se haya cargado su recurso externo.

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

Por ejemplo, en la promesa de una solicitud HTTP diferida.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
fuente
2
Esto no va a resolver el problema, ya que los datos cargados no significan que ya estén renderizados en el DOM, incluso si están en las variables de alcance adecuadas vinculadas a los elementos DOM. Hay un intervalo de tiempo entre el momento en que se cargan en el alcance y la salida renderizada en el dom.
René Stalder
1

Tuve un problema similar y quiero compartir mi solución aquí.

Tengo el siguiente HTML:

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

Problema: En la función de enlace de la directiva del div principal, quería consultar el div # sub secundario. Pero solo me dio un objeto vacío porque ng-include no había terminado cuando se ejecutó la función de enlace de la directiva. Entonces, primero hice una solución sucia con $ timeout, que funcionó, pero el parámetro de retardo dependía de la velocidad del cliente (a nadie le gusta eso).

Funciona pero sucio:

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

Aquí está la solución limpia:

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

Quizás ayude a alguien.

jaheraho
fuente