Wie kann ich eine Direktive ausführen, nachdem der Dom das Rendern beendet hat?

115

Ich habe ein scheinbar einfaches Problem ohne offensichtliche Lösung (durch Lesen der Angular JS-Dokumente) .

Ich habe eine Angular JS-Direktive, die einige Berechnungen basierend auf der Höhe anderer DOM-Elemente durchführt, um die Höhe eines Containers im DOM zu definieren.

Ähnliches geschieht in der Richtlinie:

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

Das Problem ist , dass , wenn die Richtlinie ausgeführt wird , $('site-header')kann nicht gefunden werden, ein leeres Array zurückkehr anstelle des jQuery gewickelt DOM - Element ich brauche.

Gibt es einen Rückruf, den ich in meiner Direktive verwenden kann und der erst ausgeführt wird, nachdem das DOM geladen wurde, und über die normalen Abfragen im jQuery-Selektorstil auf andere DOM-Elemente zugreifen kann?

Jannis
quelle
1
Sie können scope. $ On () und scope. $ Emit () verwenden, um benutzerdefinierte Ereignisse zu verwenden. Ich bin mir nicht sicher, ob dies der richtige / empfohlene Ansatz ist.
Tosh

Antworten:

137

Es hängt davon ab, wie Ihr $ ('Site-Header') aufgebaut ist.

Sie können versuchen, $ timeout mit einer Verzögerung von 0 zu verwenden. Etwas wie:

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

Erklärungen, wie es funktioniert: eins , zwei .

Vergessen Sie nicht, $timeoutIhre Richtlinie einzufügen:

.directive('sticky', function($timeout)
Artem Andreev
quelle
5
Danke, ich habe lange versucht, dies zum Laufen zu bringen, bis mir klar wurde, dass ich nicht $timeoutin die Richtlinie übergegangen war . Doh. Alles funktioniert jetzt, Prost.
Jannis
5
Ja, Sie müssen zur $timeoutRichtlinie wie .directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
folgt
19
Ihre verknüpften Erklärungen erklären, warum der Timeout-Trick in JavaScript funktioniert, jedoch nicht im Kontext von AngularJS. Aus der offiziellen Dokumentation : " [...] 4. Die $ evalAsync-Warteschlange wird verwendet, um Arbeiten zu planen, die außerhalb des aktuellen Stapelrahmens, jedoch vor dem Rendern der Browseransicht, ausgeführt werden müssen. Dies erfolgt normalerweise mit setTimeout (0) , aber Der Ansatz setTimeout (0) leidet unter Langsamkeit und kann zu Flackern der Ansicht führen, da der Browser die Ansicht nach jedem Ereignis rendert. [...] "(Hervorhebung von mir)
Alberto
12
Ich habe ein ähnliches Problem und habe festgestellt, dass ich ungefähr 300 ms benötige, damit das DOM geladen werden kann, bevor ich meine Anweisung ausführe. Ich mag es wirklich nicht, scheinbar willkürliche Zahlen wie diese einzugeben. Ich bin sicher, dass die Ladegeschwindigkeiten von DOM je nach Benutzer variieren werden. Wie kann ich also sicher sein, dass 300 ms für jeden funktionieren, der meine App verwendet?
Keepitreal
5
Ich bin nicht sehr glücklich über diese Antwort. Obwohl sie die Frage des OP zu beantworten scheint, ist sie sehr spezifisch für ihren Fall und ihre Relevanz für die allgemeinere Form des Problems (dh das Ausführen einer Direktive nach dem Laden eines Doms) ist nicht offensichtlich + Es ist einfach zu hackig. Nichts spezielles über Winkel
Abbood
44

So mache ich das:

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

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

});
rjm226
quelle
1
Sollte nicht einmal angle.element benötigen, da dort bereits ein Element verfügbar ist:element.ready(function(){
timhc22
1
Das @ timhc22-Element ist ein Verweis auf das DOMElement, das die Direktive ausgelöst hat. Ihre Empfehlung würde nicht zu einem DOMElement-Verweis auf das Seitenobjektobjekt führen.
Tobius
das funktioniert nicht richtig. Durch diesen Ansatz erhalte ich offsetWidth = 0
Alexey Sh.
37

Wahrscheinlich braucht der Autor meine Antwort nicht mehr. Der Vollständigkeit halber denke ich jedoch, dass andere Benutzer es nützlich finden könnten. Die beste und einfachste Lösung besteht darin, $(window).load()die zurückgegebene Funktion im Hauptteil zu verwenden . (Alternativ können Sie verwenden document.ready. Es hängt wirklich davon ab, ob Sie alle Bilder benötigen oder nicht).

Die Verwendung ist $timeoutmeiner bescheidenen Meinung nach eine sehr schwache Option und kann in einigen Fällen fehlschlagen.

Hier ist der vollständige Code, den ich verwenden würde:

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

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello
quelle
1
Können Sie erläutern, warum es "in einigen Fällen fehlschlagen kann"? Welche Fälle meinst du?
Rryter
6
Sie gehen davon aus, dass jQuery hier verfügbar ist.
Jonathan Cremin
3
@ JonathanCremin jQuery Auswahl ist das Problem nach dem OP
Nick Devereaux
1
Dies funktioniert jedoch hervorragend, wenn es einen Beitrag gibt, der neue Elemente mit der Direktive erstellt, wird das Fensterladen nach dem ersten Laden nicht ausgelöst und funktioniert daher nicht richtig.
Brian Scott
@BrianScott - Ich habe eine Kombination aus $ (window) .load für das erste Rendern der Seite (mein Anwendungsfall wartete auf eingebettete Schriftdateien) und dann element.ready verwendet, um das Wechseln der Ansichten zu erledigen.
aaaaaa
8

Es gibt ein ngcontentloadedEreignis, ich denke, Sie können es verwenden

.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
quelle
21
Können Sie erklären, was der $ $windowtut?
Wels
2
sieht aus wie ein Coffeescript, vielleicht sollte es $ ($ window) und $ window sein, die in die Direktive
eingefügt werden
5

Wenn Sie $ timeout aufgrund externer Ressourcen nicht verwenden können und aufgrund eines bestimmten Zeitproblems keine Direktive verwenden können, verwenden Sie Broadcast.

Hinzufügen, $scope.$broadcast("variable_name_here");nachdem die gewünschte externe Ressource oder lang laufende Steuerung / Direktive abgeschlossen wurde.

Fügen Sie dann das Folgende hinzu, nachdem Ihre externe Ressource geladen wurde.

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

Zum Beispiel im Versprechen einer verzögerten HTTP-Anfrage.

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

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
quelle
2
Dies wird das Problem nicht lösen, da geladene Daten nicht bedeuten, dass sie bereits im DOM gerendert werden, selbst wenn sie sich in den richtigen Bereichsvariablen befinden, die an DOM-Elemente gebunden sind. Zwischen dem Moment, in dem sie in den Bereich geladen werden, und der gerenderten Ausgabe im Dom liegt eine Zeitspanne.
René Stalder
1

Ich hatte ein ähnliches Problem und möchte meine Lösung hier teilen.

Ich habe folgendes HTML:

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

Problem: In der Link-Funktion der Direktive des Eltern-Div wollte ich das Kind-Div # sub abfragen. Aber es gab mir nur ein leeres Objekt, weil ng-include nicht beendet war, als die Link-Funktion der Direktive ausgeführt wurde. Also habe ich zuerst eine schmutzige Problemumgehung mit $ timeout gemacht, die funktioniert hat, aber der Verzögerungsparameter hing von der Clientgeschwindigkeit ab (niemand mag das).

Funktioniert aber schmutzig:

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

Hier ist die saubere Lösung:

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

Vielleicht hilft es jemandem.

Jaheraho
quelle