Wo soll der Modellstatus in Angular.js gespeichert werden?

72

Ich finde Angulars Verwendung von Modellen verwirrend. Angular scheint den Ansatz zu verfolgen, dass ein Modell beliebig sein kann - IE Angular enthält keine explizite Modellklasse und Sie können Vanilla-JavaScript-Objekte als Modelle verwenden.

In fast jedem Angular-Beispiel, das ich gesehen habe, ist das Modell effektiv ein Objekt, das entweder von Hand erstellt oder von einem API-Aufruf über eine Ressource zurückgegeben wird. Da fast jedes Angular-Beispiel, das ich mir angesehen habe, einfach ist, werden normalerweise die im $ scope in einem Controller gespeicherten Modelldaten und alle mit dem Modell verbundenen Zustände, z. B. die Auswahl, auch im $ scope im Controller gespeichert. Dies funktioniert gut für einfache Apps / Beispiele, aber dies scheint eine übermäßige Vereinfachung zu sein, wenn Apps komplexer werden. Der in einem Controller gespeicherte Modellstatus kann kontextabhängig werden und verloren gehen, wenn sich beispielsweise der Kontext ändert. Ein Controller speichert selectedGalleryund selectedPhotokann nur global speichern selectedImage, nicht aselectedPhotopro Galerie. In einer solchen Situation könnte die Verwendung eines Controllers pro Galerie dieses Problem beseitigen, erscheint jedoch aus Sicht der Benutzeroberfläche verschwenderisch und wahrscheinlich unangemessen und unnötig.

Angulars Definition von Modellen scheint näher an dem zu liegen, was ich als VO / DTO betrachten würde, das ein dummes Objekt ist, das zwischen Server und Client übertragen wird. Mein Instinkt ist es, ein solches Objekt in ein Modell zu verpacken, das ich als Modell bezeichnen würde - eine Klasse, die den Status in Bezug auf das DTO / VO beibehält (z. B. Auswahl), Mutatoren bietet, die zur Manipulation des DTO / VO erforderlich sind, und den Rest des benachrichtigt Anwendung von Änderungen an den zugrunde liegenden Daten. Natürlich wird dieser letzte Teil von Angulars Bindungen gut erledigt, aber ich sehe immer noch einen starken Anwendungsfall für die ersten beiden Verantwortlichkeiten.

Allerdings habe ich dieses Muster in den Beispielen, die ich mir angesehen habe, nicht wirklich gesehen, aber ich habe auch nicht gesehen, was ich als skalierbare Alternative betrachten würde. Angular scheint implizit davon abzuraten, Services als Modelle zu verwenden, indem Singletons erzwungen werden (ich weiß, dass es Möglichkeiten gibt, dies zu umgehen, aber sie scheinen nicht weit verbreitet oder anerkannt zu sein).

Wie soll ich den Status der Modelldaten beibehalten?

[Bearbeiten] Die zweite Antwort in dieser Frage ist interessant und entspricht in etwa dem, was ich derzeit verwende.

Ablenkung
quelle
Was magst du nicht an einem Service pro Modelltyp? A galleryServicekönnte eine Reihe von Galerien speichern.
Mark Rajcok
@ MarkRajcok Ich habe überhaupt kein Problem mit Singleton Services. In vielen Situationen sind sie alles, was Sie brauchen, und in der von Ihnen beschriebenen Situation würde das gut funktionieren. Aber was ist, wenn jede Galerie eine Reihe von Fotografien hat, von denen jede den Zustand beibehalten muss?
Undistraction
3
Ich nehme an, dass ich hier im laufenden Betrieb zu stark vereinfache und entwerfe ... Ich hätte drei Modellobjekte: 1) Fotoobjekt, 2) Galerieobjekt (eine Eigenschaft davon ist ein Array von Fotoobjekten), 3 ) galleryCollection-Objekt (eine Eigenschaft davon ist ein Array von Galerieobjekten). (Die galleryCollection ist möglicherweise kein separates Objekt - sie ist möglicherweise nur Teil des galleryService selbst.) Methoden und Eigenschaften können für alle drei Objekte vorhanden sein. In meinen Augen ist jedes Foto und jede Galerie ein separates Objekt. Sie werden nur von einem Dienst gruppiert / verwaltet / abgerufen. Die Modelle können außerhalb des Dienstes definiert werden.
Mark Rajcok
1
Ich stimme @MarkRajcok zu (wie es oft der Fall ist). Die sauberste und einfachste Methode besteht darin, die von ihm beschriebenen Dienste zu verwenden. Dies vereinfacht das Testen erheblich und macht jeden Dienst erweiterbarer und wiederverwendbarer. Ich denke, es ist wichtig, Services nicht als Rückgabe eines Modellobjekts, sondern als Rückgabe einer Modell- API anzusehen . Diese API verwenden Sie in einem Controller, um auf ein oder eine Sammlung von Modellobjekten zuzugreifen. So kann ein galleryDienst über die bekannten Methoden (Abrufen, Aktualisieren, Löschen usw.) verfügen, während er den Status intern verwaltet und Objekte mit einzelnen Aufzeichnungsmethoden zurückgibt, z $resource.
Josh David Miller
5
Für alle anderen, die sich fragen: VO = Wertobjekt , DTO = Datenübertragungsobjekt .
Tamlyn

Antworten:

28

Status (und Modelle) werden in $ scope gespeichert

$ scope ist das Datenspeicherobjekt von Angular. Es ist analog zu einer Datenbank. $ scope selbst ist nicht das Modell, aber Sie können Modelle in $ scope speichern.

Jeder $ scope hat einen übergeordneten $ scope, bis zu $ ​​rootScope, der eine Baumstruktur bildet, die Ihr DOM lose widerspiegelt. Wenn Sie eine Direktive aufrufen, für die ein neuer $ scope erforderlich ist, z. B. ng-controller, wird ein neues $ scope-Objekt erstellt und dem Baum hinzugefügt.

$ scope-Objekte werden durch prototypische Vererbung verbunden. Dies bedeutet, dass wenn Sie ein Modell auf einer höheren Ebene im Baum hinzufügen, es allen niedrigeren Ebenen zur Verfügung steht. Dies ist eine phänomenal leistungsstarke Funktion, die die $ scope-Hierarchie für den Vorlagenautor nahezu transparent macht.

Controller initialisieren $ scope

Der Zweck des Controllers besteht darin, $ scope zu initialisieren . Der gleiche Controller kann viele $ scope-Objekte in verschiedenen Teilen der Seite initialisieren. Der Controller wird instanziiert, richtet das $ scope-Objekt ein und wird dann beendet. Sie können denselben Controller verwenden, um viele $ scopes in verschiedenen Teilen der Seite zu initialisieren.

Im Fall Ihrer Bildergalerie hätten Sie einen imageGallery-Controller, den Sie dann mit der ng-controller-Direktive auf jeden Teil des DOM anwenden würden, für den Sie eine Galerie sein möchten. Dieser Teil der Seite erhält einen eigenen $ -Bereich, in dem Sie das selectedPhoto-Attribut speichern.

Prototypische Bereiche

$ scope erbt von seinem übergeordneten Element mithilfe einer einfachen alten prototypischen Vererbung bis hin zu $ ​​rootScope, sodass Sie Ihre Objekte an einer beliebigen Stelle in der Hierarchie speichern können, die sinnvoll ist. Sie erhalten einen Baum mit $ scope-Objekten, der sich ungefähr auf Ihr aktuelles DOM bezieht. Wenn sich Ihr DOM ändert, werden bei Bedarf neue $ scope-Objekte für Sie erstellt.

$ scope ist nur ein einfaches JavaScript-Objekt. Es ist nicht verschwenderischer, mehrere $ scope-Objekte zu erstellen, als ein Array mit mehreren currentImage-Objekten zu erstellen. Es ist eine sinnvolle Möglichkeit, Ihren Code zu organisieren.

Auf diese Weise beseitigt Angular das alte Problem "Wo speichere ich meine Daten?", Das wir häufig in JavaScript finden. Dies ist die Quelle eines der wirklich großen Produktivitätsgewinne, die wir durch Angular erzielen.

Haben Sie globale Daten (z. B. eine Benutzer-ID)? Speichern Sie es auf $ rootScope. Haben Sie lokale Daten (z. B. ein aktuelles Bild in einer Galerie, in der mehrere Galerieinstanzen vorhanden sind)? Speichern Sie es auf dem $ scope-Objekt, das zu dieser Galerie gehört.

$ scope steht Ihnen automatisch im richtigen Teil der Vorlage zur Verfügung.

Winkelmodelle sind dünn

Aus einem Rails-Hintergrund stammend, in dem wir uns auf fette Modelle und dünne Controller konzentrieren, fand ich Angulars "kaum da" -Modelle überraschend. Tatsächlich führt das Einfügen einer Menge Geschäftslogik in Ihr Modell häufig zu Problemen auf der ganzen Linie, wie wir manchmal beim Benutzermodell in Rails sehen, das, wenn Sie nicht vorsichtig sind, wächst, bis es nicht mehr wartbar ist.

Ein Winkelmodell ist einfach ein JavaScript-Objekt oder ein Grundelement.

Jedes Objekt kann ein Modell sein. Modelle werden normalerweise mit JSON im Controller oder AJAXed von einem Server definiert. Ein Modell kann ein JSON-Objekt sein oder nur eine Zeichenfolge, ein Array oder sogar eine Zahl.

Natürlich hindert Sie nichts daran, Ihrem Modell zusätzliche Funktionen hinzuzufügen und diese im JSON-Objekt zu speichern, wenn Sie möchten, aber dies würde in einem Paradigma portieren, das nicht wirklich zu Angular passt.

Winkelobjekte sind normalerweise Datenrepositorys, keine Funktionen.

Das Modell am Frontend ist nicht das echte Modell

Natürlich ist das Modell, das Sie auf dem Client halten, nicht das echte Modell. Ihr eigentliches Modell, Ihre einzige Quelle der Wahrheit, lebt auf dem Server. Wir synchronisieren es mithilfe einer API, aber wenn es einen Konflikt zwischen beiden gibt, ist das Modell in Ihrer Datenbank offensichtlich der ultimative Sieger.

Dies gibt Ihnen Privatsphäre für Dinge wie Rabattcodes usw. Das Modell, das Sie in Ihrem Frontend finden, ist eine synchronisierte Version der öffentlichen Eigenschaften des realen Modells, das entfernt ist.

Geschäftslogik kann in Diensten leben.

Angenommen, Sie möchten eine Methode schreiben, um etwas mit Ihrem Modell zu tun, es zu synchronisieren oder beispielsweise zu validieren. In anderen Frameworks könnten Sie versucht sein, Ihr Modell mit einer entsprechenden Methode zu erweitern. In Angular würden Sie eher einen Dienst schreiben.

Services sind Singleton-Objekte. Wie bei jedem anderen JavaScript-Objekt können Sie Funktionen oder Daten in diese einfügen. Angular wird mit einer Reihe integrierter Dienste geliefert, z. B. $ http. Sie können Ihre eigenen erstellen und mithilfe der Abhängigkeitsinjektion diese automatisch für Ihre Controller bereitstellen.

Ein Service kann Methoden enthalten, um beispielsweise mit einer RESTful-API zu kommunizieren, Ihre Daten zu validieren oder andere Arbeiten auszuführen, die Sie möglicherweise ausführen müssen.

Dienstleistungen sind keine Modelle

Natürlich sollten Sie Dienste nicht als Modelle verwenden. Verwenden Sie sie als Objekte, die Dinge tun können. Manchmal machen sie Sachen mit deinem Modell. Es ist eine andere Denkweise, aber eine praktikable.

überleuchtet
quelle
3
Vielen Dank. Alles gute Punkte. Sie sprechen jedoch nicht den Kern der Frage an. Schlagen Sie vor, dass alle Status in den "echten" Modellen auf dem Server gespeichert werden sollen? Denn das negiert effektiv einen der Hauptvorteile eines reichen Kunden - die Geschwindigkeit. Ich würde argumentieren, dass in vielen Fällen ein clientseitiges Modell genauso "real" ist wie das serverseitige Modell, obwohl es offensichtlich von der Architektur abhängt. In meiner Frage gehe ich speziell auf den Status ein, der nur für den Client relevant ist - den Auswahlstatus - und nicht auf einen dauerhafteren Status, der auf dem Server beibehalten werden muss.
Undistraction
2
Ja, Sie haben natürlich Recht, der Status sollte auf dem Client gehalten werden und Front-End-Aufgaben sollten auf dem Client stattfinden. Ich fand es einfach nützlich, über das Thema zu sprechen.
Superluminary
1
Auf jeden Fall gut, um darüber zu reden. Ich denke, das ist immer noch ein ungelöstes Problem. In diesem Staat lauern die Monster und niemand kümmert sich gerne darum.
Undistraction
2
Der Zustand wird für uns natürlich von Angular mit $ scope wunderbar gelöst.
Superluminary
Netter Beitrag, dies ist das Paradigma, dem ich in meiner App folgen möchte.
mjj1409
7

Vergessen wir zunächst nicht, dass Angular ein webbasiertes Framework ist. Wenn Sie Ihren Status ausschließlich in einem Objekt beibehalten, überlebt es nicht, dass Benutzer in ihrem Browser auf Aktualisieren klicken. Um herauszufinden, wie der Status von Modelldaten in einer webbasierten Anwendung beibehalten werden kann, müssen Sie herausfinden, wie Sie sie beibehalten, damit Ihr Code in einer Browserumgebung funktioniert.

Angular macht es Ihnen wirklich einfach, Ihren Zustand beizubehalten, indem Sie:

  1. Ein Aufruf einer RESTful $ -Ressource
  2. Eine URL, die eine Instanz Ihres Modells darstellt

In Ihrem einfachen Beispiel kann das Speichern von Benutzeraktionen wie selectedGalleryund selectedPhotomithilfe einer URL wie folgt dargestellt werden:

// List of galleries
.../gallery

// List of photos in a gallery
.../gallery/23

// A specific photo
.../gallery/23/photo/2

Die URL ist wichtig, da Ihr Benutzer mit den Schaltflächen backund im Browserverlauf navigieren kann forward. Wenn Sie diesen Status mit anderen Teilen Ihrer Anwendung teilen möchten, bietet die Webanwendung eine Vielzahl von Methoden, mit denen Sie Cookies / localStorage, versteckte Frames / Felder oder sogar das Speichern auf Ihrem Server erreichen können.

Nachdem Sie Ihre Strategie definiert haben, wie der unterschiedliche Status Ihrer Anwendung beibehalten werden soll, sollte es einfacher sein, zu entscheiden, ob Sie mit einem Singleton-Objekt, wie es von .serviceoder einer Instanz über bereitgestellt wird, auf diese beibehaltenen Informationen zugreifen möchten .factory.

Marcoseu
quelle
3
Dies ist sehr sinnvoll, und ich stimme voll und ganz zu, dass der Anwendungsstatus anhand des Pfads / der Abfragezeichenfolge verfolgt werden muss. Dies ist jedoch sicherlich eine Projektion des Anwendungsstatus und keine Möglichkeit, ihn zu speichern. Die URL-Leiste ist eine seltsame Kombination aus Modell und Ansicht, spiegelt jedoch letztendlich den internen Anwendungsstatus wider oder fordert zu Änderungen in diesem Status auf. Die restliche Anwendung selbst benötigt noch Möglichkeiten, diesen Status intern zu verfolgen, und dies auf einem Dienst / Modell zu tun, auf dem die Akteure der Anwendung den Zugriff teilen können, scheint mir die richtige Lösung zu sein.
Undistraction
Auch .service und .factory geben beide Singleton Services zurück (was sehr kontraintuitiv ist).
Undistraction
4
@marcoseu, interessant ... obwohl du das kannst, solltest du? Jetzt funktioniert die Abhängigkeitsinjektion in dieser Factory sehr unterschiedlich. Wenn Sie also eine Funktion (ein Objekt) anstelle eines Objekts (dh {}) zurückgeben, müssen Sie neweine Instanz erstellen. Dies bedeutet, dass Sie diese Instanz mit der normalen Abhängigkeitsinjektionssyntax nicht für andere Entitäten freigeben können. Ich denke, dies wäre verwirrend für andere Angular-Entwickler, die Ihren Code verwenden könnten.
Mark Rajcok
1
@MarkRajcok Ich gebe absichtlich eine neue Funktion zurück, damit das newSchlüsselwort angibt, dass dieser Dienst nicht mit anderen Entitäten geteilt werden soll. Zum Beispiel habe ich einen Wrapper um ui.bootstrap.alert, den ich instanziiere, indem ich $ scope in meinem Controller übergebe. Wenn ich später einen Alarm anzeigen muss, rufe ich einfach an alert.success("Ok"). Trotzdem stimme ich zu, dass nicht klar ist, wann der Anrufer new verwenden soll, aber es liegt wirklich am Benutzer des Dienstes, zu verstehen, wie der Dienst verwendet werden soll.
Marcoseu
1
@MarkRajcok Mir ist klar, dass die Verwendung von newverwirrend ist, aber es hat mich daran erinnert, dass der Dienst, den ich anrufe , nicht geteilt werden darf. Wenn die Bedenken jedoch nur auf der Syntaxseite liegen, können Sie auch ein Objekt zurückgeben, dh: new in the aufrufen .factory. Ich habe den Plunker aktualisiert, um zu veranschaulichen, wie dies getan werden kann.
Marcoseu
1

Angular hat keine Meinung dazu, wie Sie sogenannte "Modellobjekte" speichern. Der Angular-Controller dient $scopeausschließlich als "Ansichtsmodell" für die Verwaltung Ihrer Benutzeroberfläche. Ich schlage vor, diese beiden Konzepte in Ihrem Code zu trennen.

Wenn Sie möchten, dass Angular Scope Change Notification ( $watch) verwendet wird, können Sie ein Scope-Objekt verwenden, um Ihre Modelldaten zu speichern, wenn Sie dies wünschen ( var myScope = $rootScope.$new()). Verwenden Sie nur nicht dasselbe Bereichsobjekt, an das Ihre Benutzeroberfläche gebunden ist.

Ich empfehle, zu diesem Zweck benutzerdefinierte Dienste zu schreiben. Der Datenfluss sieht also folgendermaßen aus:

AJAX -> Benutzerdefinierter Dienst -> Modellbereichsobjekt -> Controller -> UI-Bereichsobjekt -> DOM

Oder dieses:

AJAX -> Benutzerdefinierte Dienste -> Einfache alte JavaScript-Objekte -> Controller -> UI-Bereichsobjekt -> DOM

DJsmith
quelle
3
Ich habe diese Konzepte bereits getrennt, weshalb ich diese Frage gestellt habe. Die Standard-Angular-Methode zum Umgang mit dem Modellstatus scheint darin zu bestehen, in diesem Ansichtsmodell zu speichern, was genau das ist, was mich unangenehm macht. Das Erstellen eines neuen Bereichs ist für mich keine gute Lösung. Es fühlt sich wie ein Missbrauch des Anwendungsbereichs an.
Undistraction