it-swarm.com.de

AngularJS: Wie wird zusätzlicher Code ausgeführt, nachdem AngularJS eine Vorlage gerendert hat?

Ich habe eine Vorlage Angular im DOM. Wenn mein Controller neue Daten von einem Service erhält, aktualisiert er das Modell im Bereich $ und rendert die Vorlage erneut. Alles in Ordnung.

Das Problem ist, dass ich nach dem erneuten Rendern der Vorlage und im DOM (in diesem Fall ein jQuery-Plugin) noch zusätzliche Arbeiten ausführen muss.

Anscheinend sollte es ein Ereignis geben, das man anhören kann, wie AfterRender, aber ich kann so etwas nicht finden. Vielleicht wäre eine Anweisung ein guter Weg, aber sie schien auch zu früh zu feuern.

Hier ist eine jsFiddle, die mein Problem umreißt: Fiddle-AngularIssue

== UPDATE ==

Ausgehend von hilfreichen Kommentaren bin ich entsprechend zu einer Direktive gewechselt, um die DOM-Manipulation zu handhaben, und habe ein model $ watch in die Direktive implementiert. Ich habe jedoch immer noch das gleiche Grundproblem. Der Code innerhalb des $ watch-Ereignisses wird ausgelöst, bevor die Vorlage kompiliert und in das DOM eingefügt wurde. Daher wertet das jquery-Plugin immer eine leere Tabelle aus.

Interessanterweise funktioniert das Ganze einwandfrei, wenn ich den asynchronen Aufruf entferne. Das ist also ein Schritt in die richtige Richtung.

Hier ist meine aktualisierte Fiddle um diese Änderungen wiederzugeben: http://jsfiddle.net/uNREn/12/

178
gorebash

Dieser Beitrag ist alt, aber ich ändere Ihren Code in:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

siehe jsfiddle .

Ich hoffe es hilft dir

39
dipold

Erstens sind Richtlinien der richtige Ort, um sich mit dem Rendern zu beschäftigen. Mein Rat wäre, DOM-manipulierende jQuery-Plugins durch Anweisungen wie diese zu verpacken.

Ich hatte das gleiche Problem und fand dieses Snippet. Es verwendet $watch Und $evalAsync, Um sicherzustellen, dass Ihr Code ausgeführt wird, nachdem Anweisungen wie ng-repeat Aufgelöst und Vorlagen wie {{ value }} Gerendert wurden.

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

Ich hoffe, dies kann Ihnen dabei helfen, Probleme zu vermeiden.

63
danijar

Befolgen Sie Miskos Rat, wenn Sie eine asynchrone Operation wünschen, dann anstelle von $ timeout () (was nicht funktioniert)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

benutze $ evalAsync () (was funktioniert)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

Geige . Ich habe auch einen Link zum Entfernen von Datenzeilen hinzugefügt, der $ scope.assignments ändert und eine Änderung der Daten/des Modells simuliert, um zu zeigen, dass das Ändern der Daten funktioniert.

Im Abschnitt Runtime auf der Seite Conceptual Overview wird erläutert, dass evalAsync verwendet werden sollte, wenn außerhalb des aktuellen Stack-Frames, jedoch vor dem Rendern des Browsers, ein Ereignis auftreten soll. (Hier raten ... "Aktueller Stack-Frame" enthält wahrscheinlich Angular DOM-Aktualisierungen.) Verwenden Sie $ timeout, wenn nach dem Rendern des Browsers etwas auftreten soll.

Wie Sie jedoch bereits herausgefunden haben, besteht hier meines Erachtens keine Notwendigkeit für eine asynchrone Operation.

34
Mark Rajcok

Ich habe festgestellt, dass die einfachste (billige und fröhliche) Lösung darin besteht, einfach einen leeren Bereich mit ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()" an das Ende des zuletzt gerenderten Elements anzufügen. Diese Funktion wird ausgeführt, wenn geprüft werden soll, ob das span-Element angezeigt werden soll. Führen Sie in dieser Funktion einen beliebigen anderen Code aus.

Mir ist klar, dass dies nicht die eleganteste Art ist, Dinge zu tun, aber es funktioniert für mich ...

Ich hatte eine ähnliche Situation, obwohl etwas umgekehrt, als ich zu Beginn einer Animation einen Ladeanzeiger entfernen musste. Auf Mobilgeräten wurde angular) viel schneller als die anzuzeigende Animation initialisiert und ein ng verwendet -cloak war unzureichend, da der Ladeindikator entfernt wurde, lange bevor echte Daten angezeigt wurden. In diesem Fall habe ich die Funktion my return 0 zum ersten gerenderten Element hinzugefügt und in dieser Funktion die Variable umgedreht, die den Ladeindikator verbirgt Natürlich habe ich dem von dieser Funktion ausgelösten Ladekennzeichen ein ng-hide hinzugefügt.

26
fuzion9
7
Misko Hevery

Schließlich fand ich die Lösung, ich benutzte einen REST Service, um meine Sammlung zu aktualisieren. Um datatable jquery zu konvertieren, ist der folgende Code:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });
4
Sergio Ceron

ich musste das ziemlich oft tun. Ich habe eine Direktive und muss einige JQuery-Sachen machen, nachdem Model-Sachen vollständig in das DOM geladen wurden. Also habe ich meine Logik in die link: -Funktion der Direktive eingefügt und den Code in ein setTimeout (function () {.....}, 1) eingeschlossen. setTimout wird ausgelöst, nachdem das DOM geladen wurde, und 1 Millisekunde ist die kürzeste Zeitspanne nach dem Laden des DOM, bevor der Code ausgeführt wird. Das scheint für mich zu funktionieren, aber ich möchte, dass angular= ein Ereignis ausgelöst wird, sobald eine Vorlage geladen wurde, damit die von dieser Vorlage verwendeten Direktiven Abfragematerialien ausführen und auf DOM-Elemente zugreifen können. Ich hoffe, dies hilft.

3
mgrimmenator

Sie können auch eine Direktive erstellen, die Ihren Code in der Link-Funktion ausführt.

Siehe diese Stackoverflow-Antwort .

2
Anthony O.

Weder $ scope. $ EvalAsync () noch $ timeout (fn, 0) haben bei mir zuverlässig funktioniert.

Ich musste beides kombinieren. Ich habe eine Direktive gemacht und auch eine Priorität höher als der Standardwert für gutes Maß gesetzt. Hier ist eine Anweisung dafür (Hinweis, ich benutze ngInject, um Abhängigkeiten einzufügen):

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
    // ### Directive Interface
    // Defines base properties for the directive.
    var directive = {
        restrict: 'A',
        priority: 101,
        link: link
    };
    return directive;

    // ### Link Function
    // Provides functionality for the directive during the DOM building/data binding stage.
    function link(scope, element, attrs) {
        $timeout(function() {
            scope.$evalAsync(attrs.postrenderAction);
        }, 0);
    }
}

So rufen Sie die Direktive auf:

<div postrender-action="functionToRun()"></div>

Wenn Sie es aufrufen möchten, nachdem eine ng-Wiederholung ausgeführt wurde, habe ich einen leeren Bereich in meine ng-Wiederholung und ng-if = "$ last" eingefügt:

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
2
Xchai

Ich kam mit einer ziemlich einfachen Lösung. Ich bin nicht sicher, ob es der richtige Weg ist, aber es funktioniert im praktischen Sinne. Schauen wir uns direkt an, was gerendert werden soll. Zum Beispiel in einer Direktive, die einige ng-repeats, ich würde auf die Länge des Textes (Sie können andere Dinge haben!) der Absätze oder des gesamten HTML achten. Die Direktive wird so aussehen:

.directive('myDirective', [function () {
    'use strict';
    return {

        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

NOTE dass der Code tatsächlich ausgeführt wird, nachdem Änderungen am Rendering vorgenommen wurden . Aber ich denke, in den meisten Fällen können Sie mit den Situationen umgehen, wenn Änderungen am Rendering vorgenommen werden. Sie können diese ps-Länge (oder ein anderes Maß) auch mit Ihrem Modell vergleichen, wenn Sie Ihren Code nur einmal ausführen möchten . nach dem Rendern abgeschlossen. Ich schätze alle Gedanken/Kommentare dazu.

1
Ehsan88

In einigen Szenarien, in denen Sie einen Dienst aktualisieren und zu einer neuen Ansicht (Seite) umleiten und dann Ihre Direktive geladen wird, bevor Ihre Dienste aktualisiert werden, können Sie $ rootScope. $ Broadcast verwenden, wenn Ihre $ watch oder $ timeout fehlschlagen

Aussicht

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

Regler

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

Richtlinie

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});
0
Sudharshan

Sie können das Modul 'jQuery Passthrough' der angle-ui utils verwenden. Ich habe ein jQuery Touch-Karussell-Plugin erfolgreich an einige Bilder gebunden, die ich asynchron von einem Webdienst abgerufen und mit ng-repeat gerendert habe.

0
Michael Kork.