Wie man einen Ausdruck "abschaut"


70

Angenommen, ich habe eine ng-Wiederholung mit einem großen Array.

Wenn ng-repeat ausgeführt wird, wird jedes Element dieses Arrays einem isolierten Bereich hinzugefügt und das Array selbst in einem Bereich gespeichert. Das bedeutet, dass $ Digest das gesamte Array auf Änderungen überprüft und darüber hinaus jedes einzelne Element in diesem Array auf Änderungen überprüft .

Sehen Sie diesen Plunker als Beispiel dafür, wovon ich spreche.

In meinem Anwendungsfall ändere ich nie ein einzelnes Element meines Arrays, sodass ich sie nicht beobachten muss. Ich werde immer nur das gesamte Array ändern. In diesem Fall würde ng-repeat die Tabelle in ihrer Gesamtheit neu rendern. (Wenn ich mich irre, lass es mich wissen.)

In einem Array von (sagen wir) 1000 Zeilen sind das 1000 weitere Ausdrücke, die ich nicht auswerten muss.

Wie kann ich jedes Element vom Watcher abmelden, während ich noch das Hauptarray beobachte?

Vielleicht könnte ich, anstatt mich abzumelden, mehr Kontrolle über meinen $ Digest haben und irgendwie jede einzelne Zeile überspringen?

Dieser spezielle Fall ist tatsächlich ein Beispiel für ein allgemeineres Problem. Ich weiß, dass $ watch eine Abmeldungsfunktion zurückgibt , aber das hilft nicht, wenn eine Direktive die Uhren registriert, was die meiste Zeit der Fall ist.

Antworten:


89

Um einen Repeater mit einem großen Array zu haben, das Sie nicht sehen, um jeden Gegenstand zu sehen.

Sie müssen eine benutzerdefinierte Direktive erstellen, die ein Argument und einen Ausdruck für Ihr Array verwendet. In der Verknüpfungsfunktion sehen Sie dann nur das Array und die Verknüpfungsfunktion aktualisiert den HTML-Code programmgesteuert (anstatt eine zu verwenden) ng-repeat)

so etwas wie (Pseudo-Code):

app.directive('leanRepeat', function() {
    return {
        restrict: 'E',
        scope: {
           'data' : '='
        },
        link: function(scope, elem, attr) {
           scope.$watch('data', function(value) {
              elem.empty(); //assuming jquery here.
              angular.forEach(scope.data, function(d) {
                  //write it however you're going to write it out here.
                  elem.append('<div>' + d + '</div>');
              });
           });
        }
    };
});

... was wie ein Schmerz im Hintern scheint.

Alternative Hackish-Methode

Möglicherweise können Sie eine Schleife durchlaufen $scope.$$watchersund prüfen $scope.$$watchers[0].exp.exp, ob sie mit dem Ausdruck übereinstimmt, den Sie entfernen möchten, und ihn dann mit einem einfachen splice()Aufruf entfernen . Die PITA hier ist, dass Dinge wie Blah {{whatever}} Blahzwischen Tags der Ausdruck sind und sogar Wagenrückläufe enthalten.

Auf der anderen Seite können Sie möglicherweise einfach den $ -Bereich Ihrer ng-Wiederholung durchlaufen und einfach alles entfernen und dann explizit die gewünschte Uhr hinzufügen ... Ich weiß nicht.

In jedem Fall scheint es ein Hack zu sein.

So entfernen Sie einen von $ scope erstellten Watcher. $ Watch

Sie können die Registrierung von a $watchmit der vom $watchAufruf zurückgegebenen Funktion aufheben:

Zum Beispiel, um $watchnur einmal ein Feuer zu haben :

var unregister = $scope.$watch('whatever', function(){ 
     alert('once!');
     unregister();
});

Sie können die Funktion zum Aufheben der Registrierung natürlich jederzeit aufrufen ... das war nur ein Beispiel.

Fazit: Es gibt keine großartige Möglichkeit, genau das zu tun, was Sie verlangen

Aber eines ist zu beachten: Lohnt es sich überhaupt, sich Sorgen zu machen? Ist es außerdem wirklich eine gute Idee, Tausende von Datensätzen in jeweils Dutzende von DOMElements zu laden? Denkanstöße.

Ich hoffe das hilft.


EDIT 2 (schlechte Idee entfernt)


Danke, tolle Antwort. Ich werde diese Optionen ausprobieren und mit dem antworten, was am besten funktioniert. re: Lohnt es sich überhaupt, sich Sorgen zu machen - es ist in Chrome in Ordnung, bringt uns aber im IE um. Wir verwenden (derzeit) das unendliche Scrollen, um mehr Datensätze abzurufen, sodass die 20 oder mehr, die wir beim ersten Laden haben, die meiste Zeit ausreichend und schnell sind. Wenn der Benutzer jedoch einen Bildlauf und einen Bildlauf durchführt, kann es eine gute Anzahl von Datensätzen geben.
Roy Truelove

4
Es ist okay ... alles bringt mich im IE um. ;)
Ben Lesh

Ich würde empfehlen, ein System zu verwenden, das nur das in das DOM einfügt, was beim Scrollen sichtbar ist. Vielleicht versuchen Sie es mit ng-grid github.com/angular-ui/ng-grid
Andrew Joslin

1
@blesh - würde das bei Ihrer letzten Bearbeitung das Problem lösen? Ich weiß, dass es die Benutzeroberfläche nicht aktualisieren würde, aber es würde trotzdem jedes Zeilenobjekt in der Uhr sein, nicht wahr?
Roy Truelove

@ RoyTruelove, ja, ich nehme an, du hast recht. Wahrscheinlich wäre die einzig sichere Wette, eine eigene Direktive zu erstellen, die die Wiederholung für Sie erledigt hat.
Ben Lesh

16

$ watch gibt eine Funktion zurück, die die $ watch beim Aufruf aufhebt. Das ist also alles, was Sie für "watchOnce" brauchen:

var unwatchValue = scope.$watch('value', function(newValue, oldValue) {
  // Do your thing
  unwatchValue();
});

8

Bearbeiten: Siehe die andere Antwort, die ich gepostet habe.

Ich habe Bleshs Idee auf eine trennbare Weise umgesetzt. Meine ngOnceDirektive zerstört nur den untergeordneten Bereich, ngRepeatder für jedes Element erstellt wird. Dies bedeutet, dass der Bereich nicht von den Eltern erreicht wird scope.$digestund die Beobachter niemals ausgeführt werden.

Quelle und Beispiel für JSFiddle

Die Richtlinie selbst:

angular.module('transclude', [])
 .directive('ngOnce', ['$timeout', function($timeout){
    return {
      restrict: 'EA',
      priority: 500,
      transclude: true,
      template: '<div ng-transclude></div>',
        compile: function (tElement, tAttrs, transclude) {
            return function postLink(scope, iElement, iAttrs, controller) {
                $timeout(scope.$destroy.bind(scope), 0);
            }
        }
    };
}]);

Es benutzen:

      <li ng-repeat="item in contents" ng-once>
          {{item.title}}: {{item.text}}
      </li>

Note erstellt ng-once keinen eigenen Bereich, was bedeutet, dass sich dies auf Geschwisterelemente auswirken kann. Diese machen alle dasselbe:

  <li ng-repeat="item in contents" ng-once>
      {{item.title}}: {{item.text}}
  </li>
  <li ng-repeat="item in contents">
      <ng-once>
          {{item.title}}: {{item.text}}
      </ng-once>
  </li>
  <li ng-repeat="item in contents">
      {{item.title}}: {{item.text}} <ng-once></ng-once>
  </li>

Beachten Sie, dass dies eine schlechte Idee sein kann


3

Sie können die bindonceDirektive zu Ihrer hinzufügen ng-repeat. Sie müssen es von https://github.com/pasvaz/bindonce herunterladen .

bearbeiten : ein paar Vorbehalte:

Wenn Sie {{}}in Ihrer Vorlage Interpolation verwenden, müssen Sie diese durch ersetzen <span bo-text>.

Wenn Sie ng-Direktiven verwenden, müssen Sie diese durch die richtigen bo-Direktiven ersetzen .

Auch, wenn Sie setzen sind bindonceund ng-repeatauf demselben Elemente, sollten Sie versuchen , entweder das Bewegungs bindoncezu einem übergeordneten Elemente (siehe https://github.com/Pasvaz/bindonce/issues/25#issuecomment-25457970 ) oder das Hinzufügen track byzu Ihrem ng-repeat.


Ich habe dies in eines meiner Datengitter eingefügt, das 20 Spalten anzeigt, aber nach 100 Zeilen immer noch zurückbleibt. Wenn ich die Daten in der Spalte für einen Text wie "Daten" austausche, bleibt dies nicht zurück.
Adam K Dean

2
Ich habe Bo-Text verwendet, bin aber immer noch auf die Probleme gestoßen. Es ist eine Schande, dass es keine asynchrone ng-Wiederholung gibt, die nur aktualisiert wird, wenn ein Ereignis ausgelöst wird. Es würde das Leben viel einfacher machen!
Adam K Dean

2

Wenn Sie angularjs 1.3oder höher verwenden, können Sie die Einzelbindungssyntax als verwenden

<li ng-repeat="item in ::contents">{{item}}</li>

Dies bindet den Wert und entfernt die Beobachter, sobald der erste Digest-Zyklus ausgeführt wird und sich der Wert undefinedzum definedersten Mal von bis ändert .

Ein sehr hilfreicher BLOG dazu.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.