Come posso eseguire una direttiva dopo che il dom ha terminato il rendering?


115

Ho un problema apparentemente semplice senza soluzione apparente (leggendo i documenti di Angular JS) .

Ho una direttiva JS angolare che esegue alcuni calcoli basati sull'altezza di altri elementi DOM per definire l'altezza di un contenitore nel DOM.

Qualcosa di simile a questo sta accadendo all'interno della direttiva:

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

Il problema è che quando la direttiva viene eseguita, $('site-header')non può essere trovata, restituendo un array vuoto invece dell'elemento DOM avvolto in jQuery di cui ho bisogno.

C'è un callback che posso usare all'interno della mia direttiva che viene eseguito solo dopo che il DOM è stato caricato e posso accedere ad altri elementi DOM tramite le normali query in stile selettore jQuery?


1
È possibile utilizzare scope. $ On () e scope. $ Emit () per utilizzare eventi personalizzati. Non sono sicuro che questo sia l'approccio giusto / consigliato.
Tosh

Risposte:


137

Dipende da come è costruito il tuo $ ('site-header').

Puoi provare a usare $ timeout con 0 ritardo. Qualcosa di simile a:

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

Spiegazioni su come funziona: uno , due .

Non dimenticare di iniettare $timeoutnella tua direttiva:

.directive('sticky', function($timeout)

5
Grazie, ho provato a farlo funzionare per anni finché non mi sono reso conto di non essere passato $timeoutalla direttiva. Doh. Adesso funziona tutto, applausi.
Jannis

5
Sì, devi passare $timeoutalla direttiva in questo modo:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov

19
Le tue spiegazioni collegate spiegano perché il trucco del timeout funziona in JavaScript, ma non nel contesto di AngularJS. Dalla documentazione ufficiale : " [...] 4. La coda $ evalAsync viene utilizzata per programmare il lavoro che deve avvenire al di fuori dello stack frame corrente, ma prima del rendering della vista del browser. Questo di solito viene fatto con setTimeout (0) , ma l'approccio setTimeout (0) soffre di lentezza e può causare sfarfallio della vista poiché il browser rende la vista dopo ogni evento. [...] "(enfasi mia)
Alberto

12
Sto affrontando un problema simile e ho scoperto che ho bisogno di circa 300 ms per consentire il caricamento del DOM prima di eseguire la mia direttiva. Non mi piace davvero inserire numeri apparentemente arbitrari come quello. Sono sicuro che le velocità di caricamento del DOM varieranno a seconda dell'utente. Quindi come posso essere sicuro che 300 ms funzioneranno per chiunque utilizzi la mia app?
keepitreal

5
non troppo contento di questa risposta .. anche se sembra rispondere alla domanda dell'OP .. è molto specifico per il loro caso e la sua rilevanza per la forma più generale del problema (cioè l'esecuzione di una direttiva dopo che un dom è stato caricato) non è ovvia + è semplicemente troppo hacky .. niente di specifico su
angular

44

Ecco come lo faccio:

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

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

});

1
Non dovrebbe nemmeno aver bisogno di angular.element perché l'elemento è già disponibile lì:element.ready(function(){
timhc22

1
L'elemento @ timhc22 è un riferimento al DOMElement che ha attivato la direttiva, la tua raccomandazione non risulterebbe in un riferimento DOMElement all'oggetto Document delle pagine.
tobius

che non funziona correttamente. Sto ottenendo offsetWidth = 0 attraverso questo approccio
Alexey Sh.

37

Probabilmente l'autore non avrà più bisogno della mia risposta. Tuttavia, per ragioni di completezza, ritengo che altri utenti potrebbero trovarlo utile. La soluzione migliore e più semplice è usare $(window).load()all'interno del corpo della funzione restituita. (in alternativa puoi usare document.ready. Dipende davvero se hai bisogno di tutte le immagini o meno).

L'utilizzo $timeouta mio modesto parere è un'opzione molto debole e in alcuni casi potrebbe non funzionare.

Ecco il codice completo che userei:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});

1
Puoi spiegare perché "in alcuni casi potrebbe non funzionare"? Quali casi intendi?
riprogrammazione

6
Stai assumendo che jQuery sia disponibile qui.
Jonathan Cremin

3
@JonathanCremin jQuery la selezione è il problema in questione secondo l'OP
Nick Devereaux

1
Funziona benissimo, tuttavia se c'è un post che crea nuovi elementi con la direttiva, il caricamento della finestra non si attiverà dopo il caricamento iniziale e quindi non funzionerà correttamente.
Brian Scott

@BrianScott - Ho usato una combinazione di $ (window) .load per il rendering della pagina iniziale (il mio caso d'uso era in attesa di file di font incorporati) e quindi element.ready per occuparmi del cambio di visualizzazione.
aaaaaa

8

c'è un ngcontentloadedevento, penso che tu possa 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)

       }
   }
});

21
Puoi spiegare cosa $ $windowsta facendo?
Catfish

2
sembra un coffeescript, forse doveva essere $ ($ window) e $ window viene iniettato nella direttiva
mdob

5

Se non puoi usare $ timeout a causa di risorse esterne e non puoi usare una direttiva a causa di un problema specifico con la temporizzazione, usa broadcast.

Inserisci $scope.$broadcast("variable_name_here"); dopo il completamento della risorsa esterna desiderata o del controller / direttiva a esecuzione prolungata.

Quindi aggiungi quanto segue dopo che la tua risorsa esterna è stata caricata.

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

Ad esempio nella promessa di una richiesta HTTP differita.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}

2
Questo non risolverà il problema, poiché i dati caricati non significano che siano già stati renderizzati nel DOM, anche se sono nelle variabili di ambito appropriate legate agli elementi DOM. C'è un intervallo di tempo tra il momento in cui vengono caricati nello scope e l'output renderizzato nel dom.
René Stalder

1

Ho avuto un problema simile e voglio condividere la mia soluzione qui.

Ho il seguente codice HTML:

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

Problema: nella funzione di collegamento della direttiva del div genitore volevo jquery'ing il sub div # sub. Ma mi ha dato un oggetto vuoto perché ng-include non era terminato quando è stata eseguita la funzione link della direttiva. Quindi prima ho fatto una soluzione alternativa con $ timeout, che ha funzionato ma il parametro delay dipendeva dalla velocità del client (a nessuno piace).

Funziona ma sporco:

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

Ecco la soluzione pulita:

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

Forse aiuta qualcuno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.