Como posso executar uma diretiva depois que o dom terminar de renderizar?

115

Eu tenho um problema aparentemente simples, sem solução aparente (lendo a documentação Angular JS) .

Eu tenho uma diretiva Angular JS que faz alguns cálculos com base na altura de outros elementos DOM para definir a altura de um contêiner no DOM.

Algo semelhante a isso está acontecendo dentro da diretiva:

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

O problema é que, quando a diretiva é executada, $('site-header')não pode ser encontrada, retornando um array vazio em vez do elemento DOM empacotado do jQuery de que preciso.

Existe um retorno de chamada que eu possa usar dentro da minha diretiva que só é executado depois que o DOM foi carregado e eu posso acessar outros elementos do DOM por meio das consultas de estilo do seletor jQuery normais?

Jannis
fonte
1
Você pode usar scope. $ On () e scope. $ Emit () para usar eventos personalizados. Não tenho certeza se esta é a abordagem certa / recomendada.
Tosh

Respostas:

137

Depende de como seu $ ('cabeçalho do site') é construído.

Você pode tentar usar $ timeout com 0 atraso. Algo como:

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

Explicações de como funciona: um , dois .

Não se esqueça de injetar $timeoutem sua diretiva:

.directive('sticky', function($timeout)
Artem Andreev
fonte
5
Obrigado, tentei fazer isso funcionar por muito tempo, até perceber que não havia passado $timeoutpara a diretiva. Doh. Tudo funciona agora, saúde.
Jannis
5
Sim, você precisa passar $timeoutpara uma diretiva como esta:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov
19
Suas explicações vinculadas explicam por que o truque do tempo limite funciona em JavaScript, mas não no contexto do AngularJS. Da documentação oficial : " [...] 4. A fila $ evalAsync é usada para agendar o trabalho que precisa ocorrer fora do quadro de pilha atual, mas antes da renderização da visualização do navegador. Isso geralmente é feito com setTimeout (0) , mas a abordagem setTimeout (0) sofre de lentidão e pode causar oscilação na visualização, pois o navegador renderiza a visualização após cada evento. [...] "(grifo meu)
Alberto
12
Estou enfrentando um problema semelhante e descobri que preciso de cerca de 300 ms para permitir que o DOM carregue antes de executar minha diretiva. Eu realmente não gosto de inserir números aparentemente arbitrários como esse. Tenho certeza de que as velocidades de carregamento do DOM variam de acordo com o usuário. Então, como posso ter certeza de que 300ms funcionarão para qualquer pessoa que use meu aplicativo?
keepitreal de
5
não muito feliz com esta resposta .. embora pareça responder à pergunta do OP .. é muito específico para o caso deles e é relevante para a forma mais geral do problema (ou seja, executar uma diretiva após o carregamento de um dom) não é óbvio + é muito hacky .. nada nele específico sobre angular
abbood
44

É assim que eu faço:

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

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

});
rjm226
fonte
1
Nem deveria precisar de angular.element porque o elemento já está disponível lá:element.ready(function(){
timhc22 01 de
1
O elemento @ timhc22 é uma referência ao DOMElement que acionou a diretiva, sua recomendação não resultaria em uma referência do DOMElement ao objeto Document da página.
Tobius
que não funciona corretamente. Estou obtendo offsetWidth = 0 por meio dessa abordagem
Alexey Sh.
37

Provavelmente o autor não precisará mais da minha resposta. Ainda assim, para sermos completos, acho que outros usuários podem considerá-lo útil. A melhor e mais simples solução é usar $(window).load()dentro do corpo da função retornada. (como alternativa, você pode usar document.ready. Realmente depende se você precisa de todas as imagens ou não).

Usar, $timeoutna minha humilde opinião, é uma opção muito fraca e pode falhar em alguns casos.

Aqui está o código completo que eu usaria:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
fonte
1
Você pode explicar por que "pode ​​falhar em alguns casos"? Que casos você quer dizer?
rryter
6
Você está assumindo que o jQuery está disponível aqui.
Jonathan Cremin
3
@JonathanCremin jQuery A seleção é o problema em questão de acordo com o OP
Nick Devereaux
1
Isso funciona muito bem, no entanto, se houver uma postagem que cria novos itens com a diretiva, o carregamento da janela não será acionado após o carregamento inicial e, portanto, não funcionará corretamente.
Brian Scott de
@BrianScott - Usei uma combinação de $ (window) .load para a renderização da página inicial (meu caso de uso estava aguardando arquivos de fonte incorporados) e, em seguida, element.ready para cuidar da troca de visualizações.
aaaaaa
8

há um ngcontentloadedevento, acho que você pode usá-lo

.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
fonte
21
Você pode explicar o que $ $windowestá fazendo?
Catfish
2
parece algum coffeescript, talvez fosse $ ($ window) e $ window sendo injetado na diretiva
mdob de
5

Se você não pode usar $ timeout devido a recursos externos e não pode usar uma diretiva devido a um problema específico de tempo, use broadcast.

Adicione $scope.$broadcast("variable_name_here");após a conclusão do recurso externo desejado ou do controlador / diretiva de longa duração.

Em seguida, adicione o seguinte depois que seu recurso externo for carregado.

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

Por exemplo, na promessa de uma solicitação HTTP adiada.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
fonte
2
Isso não vai resolver o problema, uma vez que os dados carregados não significam que eles já foram renderizados no DOM, mesmo se estiverem nas variáveis ​​de escopo adequadas associadas aos elementos DOM. Há um intervalo de tempo entre o momento em que são carregados no escopo e a saída renderizada no dom.
René Stalder
1

Eu tive um problema semelhante e quero compartilhar minha solução aqui.

Eu tenho o seguinte HTML:

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

Problema: Na função de link da diretiva do div pai, eu queria jquery'ing o div filho # sub. Mas ele apenas me deu um objeto vazio porque o ng-include não tinha terminado quando a função de link da diretiva foi executada. Então, primeiro fiz uma solução suja com $ timeout, que funcionou, mas o parâmetro de atraso dependia da velocidade do cliente (ninguém gosta disso).

Funciona mas está sujo:

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

Aqui está a solução limpa:

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

Talvez ajude alguém.

Jaheraho
fonte