Ich habe einen Service, sagen wir:
factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
var service = {
foo: []
};
return service;
}]);
Und ich möchte foo
eine Liste steuern, die in HTML gerendert wird:
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
Damit der Controller aService.foo
erkennt, wann aktualisiert wird, habe ich dieses Muster zusammengeschustert, wobei ich dem Controller einen Service hinzufüge $scope
und dann Folgendes verwende $scope.$watch()
:
function FooCtrl($scope, aService) {
$scope.aService = aService;
$scope.foo = aService.foo;
$scope.$watch('aService.foo', function (newVal, oldVal, scope) {
if(newVal) {
scope.foo = newVal;
}
});
}
Dies fühlt sich langwierig an und ich habe es in jedem Controller wiederholt, der die Variablen des Dienstes verwendet. Gibt es eine bessere Möglichkeit, gemeinsam genutzte Variablen zu überwachen?
angularjs
watch
angular-services
Berto
quelle
quelle
aService.foo
im HTML-Markup verweisen ? (wie folgt : plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )Antworten:
Sie können immer das gute alte Beobachtermuster verwenden, wenn Sie die Tyrannei und den Aufwand von vermeiden möchten
$watch
.Im Dienste:
Und in der Steuerung:
quelle
$destory
Ereignis im Bereich warten und eine Methode zum Aufheben der Registrierung hinzufügenaService
$watch
Das vs-Beobachter-Muster entscheidet einfach, ob abgefragt oder gepusht werden soll, und ist im Grunde eine Frage der Leistung. Verwenden Sie es also, wenn es auf die Leistung ankommt. Ich verwende ein Beobachtermuster, wenn ich sonst komplexe Objekte "tief" beobachten müsste. Ich hänge ganze Dienste an den $ -Bereich an, anstatt einzelne Dienstwerte zu beobachten. Ich vermeide Angulars $ watch wie den Teufel, es gibt genug davon in Direktiven und in nativen eckigen Datenbindungen.In einem solchen Szenario, in dem mehrere / unbekannte Objekte möglicherweise an Änderungen interessiert sind, verwenden Sie
$rootScope.$broadcast
das zu ändernde Element.Anstatt eine eigene Registrierung von Listenern zu erstellen (die bei verschiedenen $ destroys bereinigt werden müssen), sollten Sie in der Lage sein,
$broadcast
über den betreffenden Dienst zu verfügen .Sie müssen weiterhin die
$on
Handler in jedem Listener codieren, aber das Muster ist von mehreren Aufrufen an entkoppelt$digest
und vermeidet so das Risiko von lang laufenden Beobachtern.Auf diese Weise können Zuhörer auch aus dem DOM und / oder verschiedenen untergeordneten Bereichen kommen und gehen, ohne dass der Dienst sein Verhalten ändert.
** Update: Beispiele **
Sendungen sind in "globalen" Diensten am sinnvollsten, die sich auf unzählige andere Dinge in Ihrer App auswirken können. Ein gutes Beispiel ist ein Benutzerdienst, bei dem eine Reihe von Ereignissen stattfinden können, z. B. Anmelden, Abmelden, Aktualisieren, Leerlauf usw. Ich glaube, hier sind Broadcasts am sinnvollsten, da jeder Bereich auf ein Ereignis warten kann, ohne Selbst wenn der Dienst injiziert wird, müssen keine Ausdrücke oder Cache-Ergebnisse ausgewertet werden, um nach Änderungen zu suchen. Es wird nur ausgelöst und vergessen (stellen Sie also sicher, dass es sich um eine Feuer-und-Vergessen-Benachrichtigung handelt, die keine Maßnahmen erfordert).
Der obige Dienst sendet eine Nachricht an jeden Bereich, wenn die Funktion save () abgeschlossen ist und die Daten gültig sind. Wenn es sich alternativ um eine $ -Ressource oder eine Ajax-Übermittlung handelt, verschieben Sie den Broadcast-Anruf in den Rückruf, damit er ausgelöst wird, wenn der Server geantwortet hat. Broadcasts passen besonders gut zu diesem Muster, da jeder Listener nur auf das Ereignis wartet, ohne den Umfang jedes einzelnen $ Digest überprüfen zu müssen. Der Hörer würde aussehen wie:
Einer der Vorteile davon ist der Wegfall mehrerer Uhren. Wenn Sie Felder kombinieren oder Werte wie im obigen Beispiel ableiten, müssen Sie sowohl die Eigenschaften Vorname als auch Nachname überwachen. Das Beobachten der Funktion getUser () würde nur funktionieren, wenn das Benutzerobjekt bei Aktualisierungen ersetzt wurde. Es würde nicht ausgelöst, wenn die Eigenschaften des Benutzerobjekts lediglich aktualisiert würden. In diesem Fall müssten Sie eine tiefe Beobachtung machen, und das ist intensiver.
$ Broadcast sendet die Nachricht aus dem aufgerufenen Bereich in untergeordnete Bereiche. Wenn Sie es also von $ rootScope aus aufrufen, wird es in jedem Bereich ausgelöst. Wenn Sie beispielsweise $ Broadcast aus dem Bereich Ihres Controllers senden, wird dieser nur in den Bereichen ausgelöst, die von Ihrem Controller-Bereich erben. $ emit geht in die entgegengesetzte Richtung und verhält sich ähnlich wie ein DOM-Ereignis, indem es die Scope-Kette in die Luft sprudelt.
Denken Sie daran, dass es Szenarien gibt, in denen $ Broadcast sehr sinnvoll ist, und es gibt Szenarien, in denen $ Watch eine bessere Option ist - insbesondere in einem isolierten Bereich mit einem ganz bestimmten Watch-Ausdruck.
quelle
Ich verwende einen ähnlichen Ansatz wie @dtheodot, verwende jedoch ein Winkelversprechen, anstatt Rückrufe weiterzuleiten
Verwenden Sie dann überall die
myService.setFoo(foo)
Methode, um denfoo
Service zu aktualisieren . In Ihrem Controller können Sie es verwenden als:Die ersten beiden Argumente
then
sind Erfolgs- und Fehlerrückrufe, das dritte ist der Benachrichtigungsrückruf.Referenz für $ q.
quelle
$scope.$watch
einer Dienstvariablen nicht zu funktionieren schien (der Bereich, den ich beobachtete, war ein Modal, von dem geerbt wurde$rootScope
) - dies funktionierte. Cooler Trick, danke fürs Teilen!Ohne Uhren oder Beobachter-Rückrufe ( http://jsfiddle.net/zymotik/853wvv7s/ ):
JavaScript:
HTML
Dieses JavaScript funktioniert, wenn wir ein Objekt anstelle eines Werts vom Dienst zurückgeben. Wenn ein JavaScript-Objekt von einem Dienst zurückgegeben wird, fügt Angular allen seinen Eigenschaften Überwachungsfunktionen hinzu.
Beachten Sie auch, dass ich 'var self = this' verwende, da ich bei der Ausführung des $ timeout einen Verweis auf das ursprüngliche Objekt behalten muss, andernfalls bezieht sich 'this' auf das Fensterobjekt.
quelle
$scope.count = service.count
funktioniert nicht.$scope.data = service.data
<p>Count: {{ data.count }}</p>
Ich bin auf diese Frage gestoßen, als ich nach etwas Ähnlichem gesucht habe, aber ich denke, sie verdient eine gründliche Erklärung der Vorgänge sowie einige zusätzliche Lösungen.
Wenn ein Winkelausdruck wie der von Ihnen verwendete im HTML-Code vorhanden ist, richtet Angular automatisch ein
$watch
for ein$scope.foo
und aktualisiert den HTML-Code bei jeder$scope.foo
Änderung.Das unausgesprochene Problem hierbei ist, dass eines von zwei Dingen so wirkt
aService.foo
, dass die Änderungen nicht erkannt werden. Diese beiden Möglichkeiten sind:aService.foo
wird jedes Mal auf ein neues Array gesetzt, wodurch der Verweis darauf veraltet ist.aService.foo
wird so aktualisiert, dass$digest
beim Update kein Zyklus ausgelöst wird.Problem 1: Veraltete Referenzen
Unter Berücksichtigung der ersten Möglichkeit würde unter der Annahme, dass a
$digest
angewendet wird, wennaService.foo
immer dasselbe Array verwendet würde, der automatisch festgelegte$watch
die Änderungen erkennen, wie im folgenden Codeausschnitt gezeigt.Lösung 1-a: Stellen Sie sicher, dass das Array oder Objekt bei jedem Update dasselbe Objekt ist
Code-Snippet anzeigen
Wie Sie sehen können, wird die angeblich angehängte ng-Wiederholung
aService.foo
beiaService.foo
Änderungen nicht aktualisiert , die angehängte ng-WiederholungaService2.foo
jedoch . Dies liegt daran, dass unser Verweis aufaService.foo
veraltet ist, unser Verweis aufaService2.foo
jedoch nicht. Wir haben einen Verweis auf das ursprüngliche Array mit erstellt$scope.foo = aService.foo;
, der dann beim nächsten Update vom Dienst verworfen wurde, was bedeutet, dass$scope.foo
nicht mehr auf das gewünschte Array verwiesen wird.Obwohl es verschiedene Möglichkeiten gibt, um sicherzustellen, dass die ursprüngliche Referenz erhalten bleibt, kann es manchmal erforderlich sein, das Objekt oder Array zu ändern. Oder was ist, wenn die Serviceeigenschaft auf ein Grundelement wie ein
String
oder verweistNumber
? In diesen Fällen können wir uns nicht einfach auf eine Referenz verlassen. Was können wir also tun?Einige der zuvor gegebenen Antworten geben bereits einige Lösungen für dieses Problem. Ich persönlich bin jedoch dafür, die einfache Methode zu verwenden, die Jin und die ganzen Wochen in den Kommentaren vorgeschlagen haben:
Lösung 1-b: Hängen Sie den Dienst an den Bereich an und verweisen Sie ihn
{service}.{property}
im HTML-Code.Das heißt, mach einfach das:
HTML:
JS:
Code-Snippet anzeigen
Auf diese Weise der
$watch
Wille EntschlossenheitaService.foo
auf jeden$digest
, der die korrekt aktualisierte Wert erhalten wird.Dies ist eine Art von dem, was Sie mit Ihrer Problemumgehung versucht haben, aber auf eine viel weniger runde Art und Weise. Sie hinzugefügt unnötig
$watch
in der Steuerung , die legt ausdrücklichfoo
das auf ,$scope
wenn sie sich ändert. Sie brauchen nicht , dass zusätzliche ,$watch
wenn Sie legenaService
stattaService.foo
auf das$scope
, und binden explizitaService.foo
im Markup.Nun, das ist alles schön und gut, vorausgesetzt, ein
$digest
Zyklus wird angewendet. In meinen obigen Beispielen habe ich Angulars$interval
Dienst verwendet, um die Arrays zu aktualisieren, wodurch$digest
nach jeder Aktualisierung automatisch eine Schleife ausgelöst wird. Was aber, wenn die Dienstvariablen (aus welchen Gründen auch immer) in der "Angular World" nicht aktualisiert werden? Mit anderen Worten, wir dont haben einen$digest
Zyklus automatisch aktiviert wird , sobald die Service - Eigenschaft ändert?Problem 2: Vermisst
$digest
Viele der hier aufgeführten Lösungen werden dieses Problem lösen, aber ich stimme Code Whisperer zu :
Daher würde ich es vorziehen, die
aService.foo
Referenz im HTML-Markup weiterhin zu verwenden, wie im zweiten Beispiel oben gezeigt, und keinen zusätzlichen Rückruf im Controller registrieren zu müssen.Lösung 2: Verwenden Sie einen Setter und Getter mit
$rootScope.$apply()
Ich war überrascht, dass noch niemand die Verwendung von Setter und Getter vorgeschlagen hat . Diese Funktion wurde in ECMAScript5 eingeführt und gibt es daher seit Jahren. Das bedeutet natürlich, dass diese Methode nicht funktioniert, wenn Sie aus irgendeinem Grund wirklich alte Browser unterstützen müssen, aber ich denke, dass Getter und Setter in JavaScript stark unterbeansprucht werden. In diesem speziellen Fall könnten sie sehr nützlich sein:
Code-Snippet anzeigen
Hier habe ich eine 'private' Variable in die Servicefunktion eingefügt :
realFoo
. Dieses gets aktualisiert und abgerufen die Verwendungget foo()
undset foo()
Funktionen jeweils auf demservice
Objekt.Beachten Sie die Verwendung von
$rootScope.$apply()
in der eingestellten Funktion. Dadurch wird sichergestellt, dass Angular über Änderungen informiert wirdservice.foo
. Wenn Sie 'inprog'-Fehler erhalten, lesen Sie diese nützliche Referenzseite , oder wenn Sie Angular> = 1.3 verwenden, können Sie einfach verwenden$rootScope.$applyAsync()
.Seien Sie auch vorsichtig, wenn
aService.foo
diese sehr häufig aktualisiert werden, da dies die Leistung erheblich beeinträchtigen kann. Wenn die Leistung ein Problem darstellt, können Sie mit dem Setter ein Beobachtermuster einrichten, das den anderen Antworten hier ähnelt.quelle
services
nicht nach Eigenschaften, die zum Service selbst gehören.Soweit ich das beurteilen kann, müssen Sie nichts so Aufwändiges tun. Sie haben Ihrem Bereich bereits foo aus dem Dienst zugewiesen, und da foo ein Array ist (und wiederum ein Objekt, wird es als Referenz zugewiesen!). Alles, was Sie tun müssen, ist etwa Folgendes:
Wenn eine andere Variable in derselben Strg von der Änderung von foo abhängt, benötigen Sie eine Uhr, um foo zu beobachten und Änderungen an dieser Variablen vorzunehmen. Aber solange es sich um eine einfache Referenz handelt, ist das Beobachten nicht erforderlich. Hoffe das hilft.
quelle
$watch
mit einem Primitiven arbeiten. Stattdessen habe ich eine Methode für den Dienst definiert, die den primitiven Wert zurückgibt:somePrimitive() = function() { return somePrimitive }
Und ich habe dieser Methode eine $ scope-Eigenschaft zugewiesen :$scope.somePrimitive = aService.somePrimitive;
. Dann habe ich die Scope-Methode im HTML verwendet:<span>{{somePrimitive()}}</span>
$scope.foo = aService.foo
wird die Bereichsvariable nicht automatisch aktualisiert.Sie können den Dienst in $ rootScope einfügen und Folgendes beobachten:
In Ihrem Controller:
Eine andere Möglichkeit ist die Verwendung einer externen Bibliothek wie: https://github.com/melanke/Watch.JS
Funktioniert mit: IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rhino 1.7+
Sie können die Änderungen eines, mehrerer oder aller Objektattribute beobachten.
Beispiel:
quelle
Sie können die Änderungen innerhalb der Fabrik selbst beobachten und dann eine Änderung senden
Dann in Ihrem Controller:
Auf diese Weise fügen Sie den gesamten zugehörigen Factory-Code in die Beschreibung ein und können sich dann nur auf die Übertragung von außen verlassen
quelle
== AKTUALISIERT ==
Sehr einfach jetzt in $ watch.
Stift hier .
HTML:
JavaScript:
quelle
Aufbauend auf der Antwort dtheodor könnten Sie etwas Ähnliches wie das Folgende verwenden, um sicherzustellen, dass Sie nicht vergessen, die Registrierung des Rückrufs aufzuheben ... Einige lehnen es jedoch möglicherweise ab, den Rückruf
$scope
an einen Dienst weiterzuleiten.Array.remove ist eine Erweiterungsmethode, die folgendermaßen aussieht:
quelle
Hier ist mein allgemeiner Ansatz.
In Ihrem Controller
quelle
Für diejenigen wie mich, die nur nach einer einfachen Lösung suchen, ist dies fast genau das, was Sie von der Verwendung normaler $ watch in Controllern erwarten. Der einzige Unterschied besteht darin, dass die Zeichenfolge in ihrem Javascript-Kontext und nicht in einem bestimmten Bereich ausgewertet wird. Sie müssen $ rootScope in Ihren Dienst einfügen, obwohl es nur verwendet wird, um sich ordnungsgemäß in die Digest-Zyklen einzubinden.
quelle
Bei einem sehr ähnlichen Problem habe ich eine Funktion im Gültigkeitsbereich beobachtet und die Funktion die Dienstvariable zurückgeben lassen. Ich habe eine js Geige erstellt . Sie finden den Code unten.
quelle
Ich kam zu dieser Frage, aber es stellte sich heraus, dass mein Problem darin bestand, dass ich setInterval verwendete, wenn ich den Winkelintervall-Intervall-Anbieter hätte verwenden sollen. Dies gilt auch für setTimeout (verwenden Sie stattdessen $ timeout). Ich weiß, dass es nicht die Antwort auf die Frage des OP ist, aber es könnte einigen helfen, wie es mir geholfen hat.
quelle
setTimeout
oder jede andere Nicht-Angular-Funktion verwenden, aber vergessen Sie nicht, den Code in den Rückruf mit einzuschließen$scope.$apply()
.Ich habe auf dem anderen Thread eine wirklich gute Lösung mit einem ähnlichen Problem gefunden, aber einem völlig anderen Ansatz. Quelle: AngularJS: $ watch in der Direktive funktioniert nicht, wenn der Wert von $ rootScope geändert wird
Grundsätzlich sagt die Lösung dort NICHT zu verwenden,
$watch
da es sich um eine sehr schwere Lösung handelt. Stattdessen schlagen sie vor,$emit
und zu verwenden$on
.Mein Problem war , zu sehen eine Variable in meinem Dienst und reagiert in Richtlinie . Und mit der oben genannten Methode ist es sehr einfach!
Mein Modul / Service-Beispiel:
Also im Grunde beobachte ich meinen
user
- wann immer er auf einen neuen Wert gesetzt wird ich$emit
einenuser:change
Status.In meinem Fall habe ich in der Richtlinie Folgendes verwendet:
Jetzt höre ich in der Direktive auf die
$rootScope
und auf die gegebene Änderung - ich reagiere jeweils. Sehr einfach und elegant!quelle
// service: (hier nichts besonderes)
// Strg:
quelle
Ein bisschen hässlich, aber ich habe meinem Dienst die Registrierung von Bereichsvariablen hinzugefügt, um umzuschalten:
Und dann in der Steuerung:
quelle
this
von diesem Dienst zurück, anstattself
?return this;
für Ihre Konstrukteure ;-)Schauen Sie sich diesen Plunker an: Dies ist das einfachste Beispiel, das ich mir vorstellen kann
http://jsfiddle.net/HEdJF/
quelle
Ich habe hier einige schreckliche Beobachtermuster gesehen, die bei großen Anwendungen zu Speicherverlusten führen.
Ich bin vielleicht etwas spät dran, aber so einfach ist das.
Die Überwachungsfunktion sucht nach Referenzänderungen (primitiven Typen), wenn Sie etwas wie Array-Push sehen möchten. Verwenden Sie einfach:
someArray.push(someObj); someArray = someArray.splice(0);
Dadurch wird die Referenz aktualisiert und die Uhr von überall aktualisiert. Einschließlich einer Services-Getter-Methode. Alles, was ein Grundelement ist, wird automatisch aktualisiert.
quelle
Ich bin zu spät zum Teil, aber ich habe einen schöneren Weg gefunden, dies zu tun als die oben angegebene Antwort. Anstatt eine Variable zuzuweisen, die den Wert der Dienstvariablen enthält, habe ich eine an den Bereich angehängte Funktion erstellt, die die Dienstvariable zurückgibt.
Regler
Ich denke, das wird tun, was Sie wollen. Mein Controller überprüft mit dieser Implementierung ständig den Wert meines Dienstes. Ehrlich gesagt ist dies viel einfacher als die ausgewählte Antwort.
quelle
Ich habe zwei einfache Dienstprogramme geschrieben, mit denen ich Änderungen der Diensteigenschaften nachverfolgen kann.
Wenn Sie die lange Erklärung überspringen möchten, können Sie zu jsfiddle gehen
Dieser Dienst wird in der Steuerung folgendermaßen verwendet: Angenommen, Sie haben einen Dienst "testService" mit den Eigenschaften 'prop1', 'prop2', 'prop3'. Sie möchten die Bereiche 'prop1' und 'prop2' beobachten und ihnen zuweisen. Mit dem Uhrendienst sieht es so aus:
Ich würde es am Ende meines asynchronen Codes auslösen, um die $ Digest-Schleife auszulösen. So wie das:
Das alles zusammen wird also so aussehen (Sie können es ausführen oder die Geige öffnen ):
quelle