Ich suche nach der besten Methode zum Binden an eine Service-Eigenschaft in AngularJS.
Ich habe mehrere Beispiele durchgearbeitet, um zu verstehen, wie man an Eigenschaften in einem Dienst bindet, der mit AngularJS erstellt wurde.
Im Folgenden habe ich zwei Beispiele für das Binden an Eigenschaften in einem Dienst. Sie arbeiten beide. Im ersten Beispiel werden grundlegende Bindungen verwendet, und im zweiten Beispiel wurde $ scope. $ Watch verwendet, um an die Serviceeigenschaften zu binden
Werden diese Beispiele bevorzugt, wenn Sie an Eigenschaften eines Dienstes binden, oder gibt es eine andere Option, von der ich nicht weiß, dass sie empfohlen wird?
Die Voraussetzung dieser Beispiele ist, dass der Dienst seine Eigenschaften "lastUpdated" und "calls" alle 5 Sekunden aktualisiert. Sobald die Serviceeigenschaften aktualisiert wurden, sollte die Ansicht diese Änderungen widerspiegeln. Beide Beispiele funktionieren erfolgreich. Ich frage mich, ob es einen besseren Weg gibt.
Grundbindung
Der folgende Code kann hier angezeigt und ausgeführt werden: http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Die andere Möglichkeit, die Bindung an Serviceeigenschaften zu lösen, besteht darin, $ scope. $ Watch im Controller zu verwenden.
$ scope. $ watch
Der folgende Code kann hier angezeigt und ausgeführt werden: http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
Ich bin mir bewusst, dass ich $ rootcope. $ Broadcast im Dienst und $ root. $ On im Controller verwenden kann, aber in anderen Beispielen, die ich erstellt habe und die $ Broadcast / $ bei der ersten Sendung verwenden, wird vom nicht erfasst Controller, aber zusätzliche Anrufe, die gesendet werden, werden im Controller ausgelöst. Wenn Sie einen Weg kennen, um das Problem mit $ rootcope. $ Broadcast zu lösen, geben Sie bitte eine Antwort.
Um jedoch noch einmal zu erwähnen, was ich bereits erwähnt habe, möchte ich die bewährte Methode zum Binden an Serviceeigenschaften kennen.
Aktualisieren
Diese Frage wurde ursprünglich im April 2013 gestellt und beantwortet. Im Mai 2014 gab Gil Birman eine neue Antwort, die ich als richtige Antwort geändert habe. Da die Antwort von Gil Birman nur sehr wenige Stimmen hat, befürchte ich, dass die Leute, die diese Frage lesen, seine Antwort zugunsten anderer Antworten mit viel mehr Stimmen ignorieren. Bevor Sie sich für die beste Antwort entscheiden, empfehle ich die Antwort von Gil Birman.
quelle
Antworten:
Betrachten Sie einige Vor- und Nachteile des zweiten Ansatzes :
0
{{lastUpdated}}
statt{{timerData.lastUpdated}}
, was genauso gut sein könnte{{timer.lastUpdated}}
, was ich argumentieren könnte, ist besser lesbar (aber lassen Sie uns nicht streiten ... Ich gebe diesem Punkt eine neutrale Bewertung, damit Sie selbst entscheiden)+1 Es kann zweckmäßig sein, dass der Controller als eine Art API für das Markup fungiert, sodass Sie (theoretisch) die API-Zuordnungen des Controllers aktualisieren können, wenn sich die Struktur des Datenmodells ändert, ohne den HTML-Teil zu berühren.
-1 Allerdings ist Theorie nicht immer üben und ich mich in der Regel finden Markup zu modifizieren und Controller - Logik , wenn Änderungen gefordert sind, sowieso . Der zusätzliche Aufwand beim Schreiben der API macht den Vorteil zunichte.
-1 Außerdem ist dieser Ansatz nicht sehr trocken.
-1 Wenn Sie die Daten an
ng-model
Ihren Code binden möchten, werden Sie noch weniger trocken, da Sie die Daten$scope.scalar_values
im Controller neu verpacken müssen , um einen neuen REST-Aufruf durchzuführen.-0.1 Es gibt einen winzigen Performance-Hit, der zusätzliche Beobachter schafft. Wenn dem Modell Dateneigenschaften zugeordnet sind, die in einem bestimmten Controller nicht überwacht werden müssen, entsteht ein zusätzlicher Overhead für die Deep Watcher.
-1 Was ist, wenn mehrere Controller dieselben Datenmodelle benötigen? Das bedeutet, dass Sie bei jeder Modelländerung mehrere APIs aktualisieren müssen.
$scope.timerData = Timer.data;
fängt gerade an, mächtig verlockend zu klingen ... Lassen Sie uns etwas tiefer in diesen letzten Punkt eintauchen ... Über welche Art von Modelländerungen haben wir gesprochen? Ein Modell im Backend (Server)? Oder ein Modell, das nur im Frontend erstellt wird und lebt? In beiden Fällen gehört die Datenzuordnungs-API im Wesentlichen zur Front-End-Serviceschicht (einer Winkelfabrik oder einem Service). (Beachten Sie, dass Ihr erstes Beispiel - meine Präferenz - keine solche API in der Service-Schicht hat , was in Ordnung ist, weil es einfach genug ist, dass es sie nicht benötigt.)Abschließend muss nicht alles entkoppelt werden. Und soweit das Markup vollständig vom Datenmodell entkoppelt ist, überwiegen die Nachteile die Vorteile.
Controller sollten im Allgemeinen nicht mit
$scope = injectable.data.scalar
's übersät sein . Sie sollten vielmehr mit$scope = injectable.data
's,promise.then(..)
' s und$scope.complexClickAction = function() {..}
's bestreut werdenAls alternativer Ansatz zur Erzielung einer Datenentkopplung und damit zur Ansichtskapselung ist es nur mit einer Direktive sinnvoll, die Ansicht vom Modell zu entkoppeln . Aber auch dort keine skalaren Werte in den oder Funktionen. Das spart keine Zeit und macht den Code weder wartbar noch lesbar. Dies erleichtert das Testen nicht einmal, da robuste Tests im Winkel normalerweise das resultierende DOM ohnehin testen . Fordern Sie in einer Direktive Ihre Daten-API in Objektform und bevorzugen Sie die Verwendung nur der von er erstellten ers .
$watch
controller
link
$watch
ng-bind
Beispiel http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
UPDATE : Ich bin endlich auf diese Frage zurückgekommen, um hinzuzufügen, dass ich nicht denke, dass einer der beiden Ansätze "falsch" ist. Ursprünglich hatte ich geschrieben, dass die Antwort von Josh David Miller falsch war, aber im Nachhinein sind seine Punkte völlig gültig, insbesondere sein Punkt über die Trennung von Bedenken.
Abgesehen von der Trennung von Bedenken (aber tangential) gibt es einen weiteren Grund für defensives Kopieren , den ich nicht berücksichtigt habe. Diese Frage befasst sich hauptsächlich mit dem Lesen von Daten direkt von einem Dienst. Was aber, wenn ein Entwickler in Ihrem Team entscheidet, dass der Controller die Daten auf triviale Weise transformieren muss, bevor die Ansicht sie anzeigt? (Ob Controller Daten überhaupt transformieren sollen, ist eine andere Diskussion.) Wenn sie nicht zuerst eine Kopie des Objekts erstellt, kann sie unabsichtlich Regressionen in einer anderen Ansichtskomponente verursachen, die dieselben Daten verwendet.
Was diese Frage wirklich hervorhebt, sind architektonische Mängel der typischen Winkelanwendung (und wirklich jeder JavaScript-Anwendung): enge Kopplung von Bedenken und Objektveränderlichkeit. Ich habe mich kürzlich in die Architektur von Anwendungen mit React und unveränderlichen Datenstrukturen verliebt . Dies löst die folgenden zwei Probleme auf wunderbare Weise:
Trennung von Bedenken : Eine Komponente verbraucht alle von über Requisiten es die Daten und hat wenig bis keine Abhängigkeit von globalen Singletons (wie Angular - Dienste), und weiß nichts über das, was passiert ist oben in der Ansicht Hierarchie.
Mutabilität : Alle Requisiten sind unveränderlich, wodurch das Risiko einer unwissentlichen Datenmutation ausgeschlossen wird.
Angular 2.0 ist jetzt auf dem richtigen Weg, sich stark von React zu leihen, um die beiden oben genannten Punkte zu erreichen.
quelle
Aus meiner Sicht
$watch
wäre dies der beste Weg.Sie können Ihr Beispiel tatsächlich ein wenig vereinfachen:
Das ist alles was du brauchst.
Da die Eigenschaften gleichzeitig aktualisiert werden, benötigen Sie nur eine Uhr. Da sie von einem einzelnen, eher kleinen Objekt stammen, habe ich es geändert, um nur die
Timer.data
Eigenschaft zu beobachten. Der letzte übergebene Parameter$watch
weist ihn an, auf tiefe Gleichheit zu prüfen, anstatt nur sicherzustellen, dass die Referenz dieselbe ist.Um einen kleinen Kontext zu bieten, würde ich diese Methode der direkten Platzierung des Servicewerts auf dem Geltungsbereich vorziehen, um eine ordnungsgemäße Trennung der Bedenken sicherzustellen. Ihre Ansicht sollte nichts über Ihre Dienste wissen müssen, um zu funktionieren. Die Aufgabe des Controllers ist es, alles zusammenzukleben; Seine Aufgabe ist es, die Daten von Ihren Diensten abzurufen und sie auf die erforderliche Weise zu verarbeiten und dann Ihrer Ansicht die erforderlichen Details zu liefern. Aber ich glaube nicht, dass es seine Aufgabe ist, den Service direkt an die Ansicht weiterzugeben. Was macht der Controller sonst überhaupt dort? Die AngularJS-Entwickler folgten denselben Überlegungen, als sie beschlossen, keine "Logik" in die Vorlagen aufzunehmen (z
if
. B. Anweisungen).Um fair zu sein, gibt es hier wahrscheinlich mehrere Perspektiven und ich freue mich auf andere Antworten.
quelle
{{lastUpdated}}
vs.{{timerData.lastUpdated}}
Timer.data
in einer $ watchTimer
im $ scope definiert werden, da der Zeichenfolgenausdruck, den Sie an $ watch übergeben, anhand des Bereichs ausgewertet wird. Hier ist ein Plunker , der zeigt, wie das funktioniert. Der Parameter objectEquality ist hier dokumentiert - 3. Parameter - aber nicht wirklich gut erklärt.$watch
ist es ziemlich ineffizient. Siehe Antworten unter stackoverflow.com/a/17558885/932632 und stackoverflow.com/questions/12576798/…Spät zur Party, aber für zukünftige Googler - verwenden Sie nicht die bereitgestellte Antwort.
JavaScript hat einen Mechanismus zum Übergeben von Objekten als Referenz, während es nur eine flache Kopie für die Werte "Zahlen, Zeichenfolgen usw." übergibt.
Warum setzen wir den Dienst im obigen Beispiel nicht dem Gültigkeitsbereich aus , anstatt die Attribute eines Dienstes zu binden ?
Dieser einfache Ansatz ermöglicht es Angular, bidirektionale Bindungen und alle magischen Dinge auszuführen, die Sie benötigen. Hacken Sie Ihren Controller nicht mit Beobachtern oder nicht benötigten Markups.
Wenn Sie sich Sorgen machen, dass Ihre Ansicht Ihre Serviceattribute versehentlich überschreibt, verwenden
defineProperty
Sie diese Option, um sie lesbar, aufzählbar, konfigurierbar zu machen oder Getter und Setter zu definieren. Sie können viel Kontrolle erlangen, indem Sie Ihren Service solider gestalten.Letzter Tipp: Wenn Sie mehr Zeit mit der Arbeit an Ihrem Controller als mit Ihren Diensten verbringen, machen Sie es falsch :(.
In diesem speziellen Demo-Code, den Sie angegeben haben, würde ich Ihnen Folgendes empfehlen:
Bearbeiten:
Wie oben erwähnt, können Sie das Verhalten Ihrer Serviceattribute mithilfe von steuern
defineProperty
Beispiel:
Jetzt in unserem Controller, wenn wir das tun
Unser Service wird den Wert von ändern
propertyWithSetter
und den neuen Wert auch irgendwie in der Datenbank veröffentlichen!Oder wir können jeden Ansatz wählen, den wir wollen.
Wenden Sie sich an die MDN - Dokumentation für
defineProperty
.quelle
$scope.model = {timerData: Timer.data};
indem ich es einfach an das Modell angehängt habe, anstatt direkt auf Scope.Ich denke, diese Frage hat eine kontextbezogene Komponente.
Wenn Sie einfach Daten von einem Dienst abrufen und diese Informationen in seine Ansicht übertragen, ist es meiner Meinung nach in Ordnung, direkt an die Serviceeigenschaft zu binden. Ich möchte nicht viel Boilerplate-Code schreiben , um Serviceeigenschaften einfach Modelleigenschaften zuzuordnen, die in meiner Ansicht verwendet werden sollen.
Darüber hinaus basiert die Leistung im Winkel auf zwei Dingen. Die erste ist, wie viele Bindungen sich auf einer Seite befinden. Das zweite ist, wie teuer Getter-Funktionen sind. Misko spricht hier darüber
Wenn Sie eine instanzspezifische Logik für die Servicedaten ausführen müssen (im Gegensatz zu Datenmassagen, die innerhalb des Service selbst angewendet werden) und das Ergebnis dies Auswirkungen auf das Datenmodell hat, das der Ansicht ausgesetzt ist, würde ich sagen, dass ein $ watcher angemessen ist solange die Funktion nicht besonders teuer ist. Im Falle einer teuren Funktion würde ich vorschlagen, die Ergebnisse in einer lokalen Variablen (an den Controller) zwischenzuspeichern, Ihre komplexen Operationen außerhalb der $ watcher-Funktion auszuführen und dann Ihren Bereich an das Ergebnis zu binden.
Als Einschränkung sollten Sie keine Eigenschaften direkt an Ihrem $ scope hängen . Die
$scope
Variable ist NICHT Ihr Modell. Es enthält Verweise auf Ihr Modell.In meinen Augen "Best Practice" für die einfache Übertragung von Informationen vom Service bis zur Ansicht:
Und dann würde Ihre Ansicht enthalten
{{model.timerData.lastupdated}}
.quelle
Aufbauend auf den obigen Beispielen dachte ich, ich würde eine Möglichkeit einführen, eine Controller-Variable transparent an eine Service-Variable zu binden.
Im folgenden Beispiel werden Änderungen an der Controller-
$scope.count
Variablen automatisch in der Service-count
Variablen übernommen.In der Produktion verwenden wir diese Bindung tatsächlich, um eine ID für einen Dienst zu aktualisieren, der dann asynchron Daten abruft und seine Dienstvariablen aktualisiert. Weitere Bindung bedeutet, dass Controller automatisch aktualisiert werden, wenn der Dienst selbst aktualisiert wird.
Der folgende Code funktioniert unter http://jsfiddle.net/xuUHS/163/.
Aussicht:
Service / Controller:
quelle
Ich denke, es ist eine bessere Möglichkeit, den Dienst selbst zu binden, als die Attribute darauf.
Hier ist der Grund:
Sie können es auf diesem Plunker spielen .
quelle
Ich würde meine Beobachter lieber so wenig wie möglich halten. Mein Grund basiert auf meinen Erfahrungen und man könnte es theoretisch argumentieren.
Das Problem bei der Verwendung von Watchern besteht darin, dass Sie jede Eigenschaft im Bereich verwenden können, um eine der Methoden in einer beliebigen Komponente oder einem beliebigen Dienst aufzurufen.
In einem realen Projekt werden Sie bald eine nicht nachvollziehbare (besser gesagt schwer nachvollziehbare) Kette von Methoden aufrufen und Werte ändern, was den Onboarding -Prozess besonders tragisch macht.
quelle
Das Binden von Daten, die einen Dienst senden, ist keine gute Idee (Architektur), aber wenn Sie sie mehr benötigen, empfehle ich Ihnen zwei Möglichkeiten, dies zu tun
1) Sie können die Daten erhalten, die nicht in Ihrem Dienst enthalten sind. Sie können Daten in Ihrem Controller / Ihrer Direktive abrufen, und Sie werden kein Problem damit haben, sie irgendwo zu binden
2) Sie können anglejs-Ereignisse verwenden. Wann immer Sie möchten, können Sie ein Signal (von $ rootScope) senden und es abfangen, wo immer Sie möchten. Sie können sogar Daten zu diesem Ereignisnamen senden.
Vielleicht kann dir das helfen. Wenn Sie mehr mit Beispielen benötigen, finden Sie hier den Link
http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html
quelle
Wie wäre es mit
Wo ist ParentScope ein injizierter Dienst?
quelle
Die elegantesten Lösungen ...
Außerdem schreibe ich EDAs (Event-Driven Architectures), sodass ich dazu neige, Folgendes zu tun [stark vereinfachte Version]:
Dann setze ich einen Listener in meinen Controller auf den gewünschten Kanal und halte meinen lokalen Bereich auf diese Weise auf dem neuesten Stand.
Abschließend gibt es nicht viel von einem „Best Practice“ - besser gesagt, seine meist bevorzugt - solange Sie halten sind Dinge SOLID und unter Verwendung schwache Kopplung. Der Grund, warum ich den letzteren Code befürworten würde, ist, dass EDAs die niedrigste von Natur aus mögliche Kopplung aufweisen . Und wenn Sie sich darüber keine Sorgen machen, lassen Sie uns vermeiden, gemeinsam an demselben Projekt zu arbeiten.
Hoffe das hilft...
quelle