ng-repeat Zielereignis

207

Ich möchte eine jQuery-Funktion aufrufen, die auf div mit Tabelle abzielt. Diese Tabelle ist mit gefüllt ng-repeat.

Wenn ich es anrufe

$(document).ready()

Ich habe kein Ergebnis.

Ebenfalls

$scope.$on('$viewContentLoaded', myFunc);

hilft nicht.

Gibt es eine Möglichkeit, die Funktion direkt nach Abschluss der ng-repeat-Population auszuführen? Ich habe einen Rat zur Verwendung von Benutzerdefiniert gelesen directive, aber ich habe keine Ahnung, wie ich ihn mit ng-repeat und meinem div verwenden soll ...

ChruS
quelle

Antworten:

240

In der Tat sollten Sie Direktiven verwenden, und es gibt kein Ereignis, das an das Ende einer ng-Repeat-Schleife gebunden ist (da jedes Element einzeln erstellt wird und ein eigenes Ereignis hat). Aber a) die Verwendung von Anweisungen ist möglicherweise alles, was Sie benötigen, und b) es gibt einige ng-Repeat-spezifische Eigenschaften, mit denen Sie Ihr Ereignis "on ngRepeat beendet" ausführen können.

Wenn Sie lediglich Ereignisse für die gesamte Tabelle formatieren / hinzufügen möchten, können Sie dies in einer Direktive tun, die alle ngRepeat-Elemente umfasst. Wenn Sie andererseits jedes Element spezifisch ansprechen möchten, können Sie eine Anweisung innerhalb von ngRepeat verwenden, die nach ihrer Erstellung auf jedes Element einwirkt.

Dann gibt es die $index, $first, $middleund $lastEigenschaften können Sie auf Triggerereignisse verwenden. Also für diesen HTML:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

Sie können Anweisungen wie folgt verwenden:

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

Sehen Sie es in Aktion in diesem Plunker . Ich hoffe es hilft!

Tiago Roldão
quelle
Kann ich den myMainDirectiveCode erst nach dem Ende der Schleife ausführen lassen? Ich muss die Bildlaufleiste des übergeordneten Div aktualisieren.
ChruS
2
Natürlich! Ändern Sie einfach die "window.alert" in eine Ereignisauslösefunktion und fangen Sie sie mit der Hauptanweisung ab. Ich habe de Plunker als Beispiel aktualisiert, um dies zu tun.
Tiago Roldão
9
Es wird empfohlen, zuerst die jQuery-Bibliothek (vor Angular) einzuschließen. Dadurch werden alle Angular-Elemente mit jQuery (anstelle von jqLite) umbrochen. Dann können Sie anstelle von angle.element (element) .css () und $ (element) .children (). Css () einfach element.css () und element.children (). Css () schreiben. Siehe auch groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok
11
Hat jemand eine Idee, wie dies für nachfolgende Renderings in der ngRepeat-Direktive funktioniert? dh: Link
RavenHursT
1
Dies ist eine Namenskonvention für alle Winkelanweisungen. Siehe docs.angularjs.org/guide/directive#normalization
Tiago Roldão
68

Wenn Sie einfach am Ende der Schleife Code ausführen möchten, finden Sie hier eine etwas einfachere Variante, für die keine zusätzliche Ereignisbehandlung erforderlich ist:

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

Sehen Sie sich eine Live-Demo an.

Karlgold
quelle
Funktioniert das, wenn ich Controller als Syntax verwende? Bitte? Gibt es keine andere Möglichkeit, die mit dem Controller als funktioniert?
Dan Nguyen
63

Es ist nicht erforderlich, eine Richtlinie zu erstellen, insbesondere nicht, um eine ng-repeatvollständige Veranstaltung zu haben .

ng-init macht die Magie für dich.

  <div ng-repeat="thing in things" ng-init="$last && finished()">

Das $laststellt sicher, dass dies finishednur ausgelöst wird, wenn das letzte Element in das DOM gerendert wurde.

Vergessen Sie nicht, ein $scope.finishedEreignis zu erstellen .

Viel Spaß beim Codieren !!

EDIT: 23. Oktober 2016

finishedWenn Sie die Funktion auch aufrufen möchten, wenn sich kein Element im Array befindet, können Sie die folgende Problemumgehung verwenden

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

Fügen Sie einfach die obige Zeile oben im ng-repeatElement hinzu. Es wird geprüft, ob das Array keinen Wert hat, und die Funktion entsprechend aufgerufen.

Z.B

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">
Vikas Bansal
quelle
Was ist, wenn sich "Dinge" als leeres Array herausstellen?
Frane Poljak
1
@FranePoljak Ich habe die Lösung in der Antwort hinzugefügt.
Vikas Bansal
1
Im Falle eines leeren Arrays behandeln Sie es in Controller
JSAddict
Fix Vikas Bansal Antwort: // Verwenden Sie first div, wenn Sie überprüfen möchten, ob das Array keinen Wert hat, und rufen Sie die Funktion entsprechend auf. <div ng-if = "! things.length" ng-hide = "true" ng-init = "beendet ()"> </ div> <div ng-repeat = "Ding in Dingen" ng-init = "$ letzte && beendet () ">
Alex Teslenko
41

Hier ist eine Wiederholungsanweisung, die eine bestimmte Funktion aufruft, wenn sie wahr ist. Ich habe festgestellt, dass die aufgerufene Funktion $ timeout mit Intervall = 0 verwenden muss, bevor eine DOM-Manipulation durchgeführt wird, z. B. das Initialisieren von QuickInfos für die gerenderten Elemente. jsFiddle: http://jsfiddle.net/tQw6w/

Versuchen Sie in $ scope.layoutDone, die $ timeout-Zeile zu kommentieren und "NOT CORRECT!" Linie, um den Unterschied in den QuickInfos zu sehen.

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })
Joseph Oster
quelle
Ich wollte kein $ timeout verwenden, aber als Sie sagten, es könnte auf 0 gesetzt werden, habe ich beschlossen, es auszuprobieren, und es hat gut funktioniert. Ich frage mich, ob dies eine Frage der Verdauung ist und ob es eine Möglichkeit gibt, das zu tun, was das $ timeout tut, ohne $ timeout zu verwenden.
Jameela Huq
Können Sie bitte erklären, warum dies funktioniert? Ich versuche, auf dynamische IDs zuzugreifen, und dies funktioniert erst nach einer Zeitüberschreitung mit einer Verzögerung von 0. Ich habe diese "hackige" Lösung in mehreren Posts gesehen, aber niemand erklärt, warum sie funktioniert.
Jin
30

Hier ist ein einfacher Ansatz ng-init, für den nicht einmal eine benutzerdefinierte Direktive erforderlich ist. In bestimmten Szenarien hat es gut funktioniert, z. B. wenn beim Laden einer Seite ein Teil von ng-wiederholten Elementen automatisch zu einem bestimmten Element gescrollt werden muss. Die Bildlauffunktion muss also warten, bis das ng-repeatRendern im DOM abgeschlossen ist, bevor es ausgelöst werden kann.

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

Beachten Sie, dass dies nur für Funktionen nützlich ist, die Sie nach dem ersten Rendern von ng-repeat einmal aufrufen müssen. Wenn Sie eine Funktion aufrufen müssen, wenn der Inhalt von ng-repeat aktualisiert wird, müssen Sie eine der anderen Antworten in diesem Thread mit einer benutzerdefinierten Anweisung verwenden.

dshap
quelle
1
Schön, das hat den Job gemacht. Ich wollte Text in einem Textfeld automatisch auswählen, und das Timeout hat es geschafft. Andernfalls wurde der Text {{model.value}} ausgewählt und dann abgewählt, wenn der datengebundene model.value eingefügt wurde.
JoshGough
27

Als Ergänzung zu Pavel's Antwort wäre etwas Lesbareres und leicht verständlicheres:

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

Warum angular.noopgibt es das überhaupt noch ...?

Vorteile:

Sie müssen dafür keine Direktive schreiben ...

deostroll
quelle
20
Warum ein Ternär verwenden, wenn Sie boolesche Logik verwenden können:ng-init="$last && doSomething( )"
Nate Whittaker
Es ist einfacher, @NateWhittaker zu lesen (und es ist beeindruckend, schwerer zu lesen als ein ternärer Operator). Es ist gut, sauberen und einfach zu verstehenden Code zu haben
Justin
21

Vielleicht ein etwas einfacherer Ansatz mit ngInitund Lodashs debounceMethode ohne die Notwendigkeit einer benutzerdefinierten Direktive:

Regler:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

Vorlage:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

Aktualisieren

Es gibt eine noch einfachere reine AngularJS-Lösung mit ternärem Operator:

Vorlage:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

Beachten Sie, dass ngInit die Kompilierungsphase vor dem Link verwendet, dh der Ausdruck wird aufgerufen, bevor untergeordnete Anweisungen verarbeitet werden. Dies bedeutet, dass möglicherweise noch eine asynchrone Verarbeitung erforderlich ist.

Pavel Horal
quelle
Sollte beachten, dass Sie die lodash.js benötigen, damit dies funktioniert :) Außerdem: Ist der ", 20" -Teil von $ scope.refresh = die Anzahl der Millis, bevor diese Methode nach der letzten Iteration aufgerufen wird?
Jørgen Skår Fischer
Hinzugefügt Lodash vor dem debounce Link. Ja, 20 ist eine Verzögerung, bevor die Methode ausgeführt wird - geändert auf 0, da dies keine Rolle spielt, solange der Aufruf asynchron ist.
Pavel Horal
1
Denken Sie auch darüber nach, da ein ternärer Operator in einfachen Ausdrücken zulässig ist ng-init="$last ? refresh() : false". Und das erfordert nicht einmal Lodash.
Pavel Horal
<div ng-repeat = "i in [1,2,4]" ng-init = "doSomething ($ last)"> </ div> $ scope.doSomething = function (lastElement) {if (lastElem) bla bla bla }
JSAddict
4

Es kann auch erforderlich sein, wenn Sie die scope.$lastVariable überprüfen , um Ihren Trigger mit einem zu versehen setTimeout(someFn, 0). A setTimeout 0ist eine akzeptierte Technik in Javascript und es war unerlässlich, dass ich directiverichtig lief.

Cody
quelle
Ich habe das gleiche Problem gefunden. Sowohl in Backbone als auch in Angular konnte ich keinen Weg ohne den Timeout-Hack finden, wenn Sie beispielsweise CSS-Eigenschaftswerte lesen müssen, die auf ein DOM-Element angewendet werden.
Chovy
3

Dies ist eine Verbesserung der Ideen, die in anderen Antworten zum Ausdruck gebracht wurden, um zu zeigen, wie Sie Zugriff auf die ngRepeat-Eigenschaften ($ index, $ first, $ middle, $ last, $ even, $ odd) erhalten, wenn Sie deklarative Syntax verwenden und den Bereich isolieren ( Google hat Best Practice empfohlen) mit einer Element-Direktive. Beachten Sie den Hauptunterschied : scope.$parent.$last.

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});
Joshua David
quelle
Wäre es nicht besser, Direktiven als Attribute als Elemente zu verwenden? dh einschränken: 'A'?
Tejasvi Hegde
2
Der Punkt war, deklarative Syntax + isolierten Bereich zu zeigen ... ja, Sie können hier gerne eine Attributanweisung verwenden, wenn Sie dies wünschen. Es gibt eine Zeit und einen Ort für beide, abhängig von Ihrem Zweck.
Joshua David
3

Ich habe es so gemacht.

Erstellen Sie die Direktive

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

Nachdem Sie es auf dem Etikett hinzugefügt haben, wo diese ng-Wiederholung

<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

Und fertig damit wird am Ende der ng-Wiederholung ausgeführt.

Mvochoa
quelle
3
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

Meine Lösung bestand darin, ein div hinzuzufügen, um eine Funktion aufzurufen, wenn das Element das letzte in einer Wiederholung war.

scotty3
quelle
2

Ich möchte eine weitere Antwort hinzufügen, da die vorhergehenden Antworten davon ausgehen, dass der Code, der nach der Ausführung von ngRepeat ausgeführt werden muss, ein Winkelcode ist. In diesem Fall bieten alle obigen Antworten eine großartige und einfache Lösung, von denen einige allgemeiner sind als andere. und falls es für die Phase des Digest-Lebenszyklus wichtig ist, können Sie einen Blick auf Ben Nadels Blog werfen , mit der Ausnahme, dass Sie $ parse anstelle von $ eval verwenden.

Aber meiner Erfahrung nach werden, wie das OP feststellt, normalerweise einige JQuery-Plugins oder -Methoden auf dem endgültig kompilierten DOM ausgeführt. In diesem Fall stellte ich fest, dass die einfachste Lösung darin besteht, eine Direktive mit einem setTimeout zu erstellen, da die setTimeout-Funktion gepusht wird Bis zum Ende der Warteschlange des Browsers ist es immer richtig, nachdem alles in einem Winkel ausgeführt wurde, normalerweise ngReapet, das nach der PostLinking-Funktion seiner Eltern fortgesetzt wird

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

Für alle, die sich fragen, warum sie in diesem Fall nicht $ timeout verwenden sollten, ist dies ein weiterer Verdauungszyklus, der völlig unnötig ist

Bresleveloper
quelle
1

Ich musste Formeln mit MathJax rendern, nachdem ng-repeat beendet war. Keine der obigen Antworten löste mein Problem, also machte ich wie unten. Es ist keine schöne Lösung , hat aber für mich funktioniert ...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>
João Paulo
quelle
0

Ich fand hier eine gut geübte Antwort , aber es war immer noch notwendig, eine Verzögerung hinzuzufügen

Erstellen Sie die folgende Direktive:

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

Fügen Sie es Ihrem Repeater als Attribut hinzu:

<div ng-repeat="item in items" emit-last-repeater-element></div>

Laut Radu:

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

Bei mir funktioniert es, aber ich muss noch ein setTimeout hinzufügen

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });
Blomersi
quelle
-4

Wenn Sie den Klassennamen einfach so ändern möchten, dass er anders gerendert wird, reicht der folgende Code aus.

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

Dieser Code funktionierte für mich, als ich eine ähnliche Anforderung hatte, um den eingekauften Artikel in meiner Einkaufsliste in Strick-Trough-Schrift zu rendern.

user4808836
quelle