341 Stimmen

Wohin mit den Modelldaten und dem Verhalten? [tl; dr; Use Services]

Ich arbeite mit AngularJS für mein neuestes Projekt. In der Dokumentation und Tutorials werden alle Modelldaten in den Controller-Bereich gesetzt. Ich verstehe, dass es dort sein muss, um für den Controller und damit innerhalb der entsprechenden Ansichten verfügbar zu sein.

Ich glaube aber nicht, dass das Modell dort tatsächlich umgesetzt werden sollte. Es könnte komplex sein und zum Beispiel private Attribute haben. Außerdem könnte man es in einem anderen Kontext/App wiederverwenden wollen. Wenn man alles in den Controller packt, bricht das MVC-Muster völlig.

Das Gleiche gilt für das Verhalten eines jeden Modells. Wenn ich verwenden würde DCI-Architektur und das Verhalten vom Datenmodell zu trennen, müsste ich zusätzliche Objekte einführen, um das Verhalten zu speichern. Dies würde durch die Einführung von Rollen und Kontexten geschehen.

DCI == D ata C ollaboration I nteraktion

Natürlich könnten die Modelldaten und das Verhalten mit einfachen Javascript-Objekten oder einem beliebigen "Klassen"-Muster implementiert werden. Aber was wäre der AngularJS Weg, es zu tun? Mit Diensten?

Es läuft also auf diese Frage hinaus:

Wie implementiert man Modelle, die vom Controller entkoppelt sind, gemäß den Best Practices von AngularJS?

155voto

Andrew Joslin Punkte 42973

Sie sollten Dienste verwenden, wenn Sie etwas wollen, das von mehreren Controllern genutzt werden kann. Hier ist ein einfaches, erfundenes Beispiel:

myApp.factory('ListService', function() {
  var ListService = {};
  var list = [];
  ListService.getItem = function(index) { return list[index]; }
  ListService.addItem = function(item) { list.push(item); }
  ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
  ListService.size = function() { return list.length; }

  return ListService;
});

function Ctrl1($scope, ListService) {
  //Can add/remove/get items from shared list
}

function Ctrl2($scope, ListService) {
  //Can add/remove/get items from shared list
}

81voto

Ben G Punkte 3925

Ich probiere gerade dieses Muster aus, das zwar nicht DCI ist, aber eine klassische Dienst/Modell-Entkopplung bietet (mit Diensten für die Kommunikation mit Webdiensten (aka Modell-CRUD) und einem Modell, das die Objekteigenschaften und Methoden definiert).

Beachten Sie, dass ich dieses Muster nur verwende, wenn das Modellobjekt funktionierende Methoden benötigt für sich allein Eigenschaften, die ich wahrscheinlich überall verwenden werde (z. B. verbesserte Getter/Setters). Ich bin no dafür plädiert, dies systematisch für jeden Dienst zu tun.

EDIT: Ich pflegte zu denken, dieses Muster würde gegen die "Angular Modell ist plain old Javascript Objekt" Mantra gehen, aber es scheint mir jetzt, dass dieses Muster völlig in Ordnung ist.

EDIT (2): Um noch deutlicher zu werden, verwende ich eine Model-Klasse nur für einfache Getter / Setter (z.B.: zur Verwendung in View-Templates). Für große Geschäftslogik, empfehle ich die Verwendung von separaten Service(s), die "wissen" über das Modell, aber sind getrennt von ihnen gehalten, und nur Geschäftslogik enthalten. Nennen Sie es eine "Geschäftsexperten"-Dienstschicht, wenn Sie wollen.

service/ElementServices.js (beachten Sie, wie Element in die Erklärung eingefügt wird)

MyApp.service('ElementServices', function($http, $q, Element)
{
    this.getById = function(id)
    {
        return $http.get('/element/' + id).then(
            function(response)
            {
                //this is where the Element model is used
                return new Element(response.data);
            },
            function(response)
            {
                return $q.reject(response.data.error);
            }
        );
    };
    ... other CRUD methods
}

model/Element.js (unter Verwendung von Angularjs Factory, für die Erstellung von Objekten)

MyApp.factory('Element', function()
{
    var Element = function(data) {
        //set defaults properties and functions
        angular.extend(this, {
            id:null,
            collection1:[],
            collection2:[],
            status:'NEW',
            //... other properties

            //dummy isNew function that would work on two properties to harden code
            isNew:function(){
                return (this.status=='NEW' || this.id == null);
            }
        });
        angular.extend(this, data);
    };
    return Element;
});

29voto

S.C. Punkte 2824

In der Angularjs-Dokumentation steht eindeutig:

Im Gegensatz zu vielen anderen Frameworks macht Angular keine Einschränkungen oder Anforderungen an das Modell. Es gibt keine Klassen, von denen geerbt werden muss oder spezielle Accessor-Methoden für den Zugriff oder die Änderung des Modells. Das Modell kann primitiv, ein Objekthash oder ein vollständiger Objekttyp sein. Kurz gesagt das Modell ist ein einfaches JavaScript-Objekt.

- AngularJS Entwicklerhandbuch - V1.5 Konzepte - Modell

Das bedeutet, dass es an Ihnen liegt, wie Sie ein Modell deklarieren. Es ist ein einfaches Javascript-Objekt.

Ich persönlich werde Angular Services nicht verwenden, da sie sich wie Singleton-Objekte verhalten sollen, die man zum Beispiel verwenden kann, um globale Zustände in der gesamten Anwendung zu erhalten.

8voto

Rune FS Punkte 20934

DCI ist ein Paradigma und als solche gibt es keine AngularJS Weg, es zu tun, entweder die Sprache unterstützen DCI oder es nicht. JS unterstützt DCI ziemlich gut, wenn man bereit ist, die Quelltransformation zu verwenden, und mit einigen Nachteilen, wenn man das nicht tut. Auch hier hat DCI nicht mehr mit Dependency Injection zu tun als etwa eine C#-Klasse und ist definitiv auch kein Dienst. Der beste Weg, DCI mit angulusJS zu realisieren, ist also, DCI auf die JS-Art zu realisieren, was ziemlich nahe daran ist, wie DCI überhaupt formuliert ist. Solange Sie keine Quelltransformation durchführen, werden Sie nicht in der Lage sein, es vollständig zu tun, da die Rollenmethoden auch außerhalb des Kontexts Teil des Objekts sein werden, aber das ist im Allgemeinen das Problem mit DCI auf Basis von Methodeninjektion. Wenn Sie sich fullOO.info die maßgebliche Seite für DCI, könnten Sie sich die Ruby-Implementierungen ansehen, die ebenfalls Methodeninjektion verwenden, oder Sie könnten einen Blick auf aquí für weitere Informationen über DCI. Es geht hauptsächlich um RUby-Beispiele, aber das DCI-Zeug ist nicht davon abhängig. Einer der Schlüssel zu DCI ist, dass das, was das System tut, von dem, was das System ist, getrennt ist. Die Datenobjekte sind also ziemlich dumm, aber sobald sie an eine Rolle in einem Kontext gebunden sind, machen Rollenmethoden ein bestimmtes Verhalten verfügbar. Eine Rolle ist einfach ein Bezeichner, nichts weiter, und wenn man über diesen Bezeichner auf ein Objekt zugreift, sind Rollenmethoden verfügbar. Es gibt kein Rollenobjekt/-klasse. Bei der Methodeninjektion ist das Scoping von Rollenmethoden nicht genau wie beschrieben, aber nahe dran. Ein Beispiel für einen Kontext in JS könnte sein

function transfer(source,destination){
   source.transfer = function(amount){
        source.withdraw(amount);
        source.log("withdrew " + amount);
        destination.receive(amount);
   };
   destination.receive = function(amount){
      destination.deposit(amount);
      destination.log("deposited " + amount);
   };
   this.transfer = function(amount){
    source.transfer(amount);
   };
}

5voto

Brett Cassette Punkte 51

Wie von anderen Postern erwähnt, bietet Angular keine Out-of-the-Box-Basisklasse für die Modellierung, aber man kann sinnvollerweise mehrere Funktionen bereitstellen:

  1. Methoden zur Interaktion mit einer RESTful API und zur Erstellung neuer Objekte
  2. Herstellen von Beziehungen zwischen Modellen
  3. Validierung von Daten vor der Übermittlung an das Backend; auch nützlich für die Anzeige von Fehlern in Echtzeit
  4. Caching und Lazy-Loading, um unnötige HTTP-Anfragen zu vermeiden
  5. Zustandsmaschinen-Hooks (vor/nach Speichern, Aktualisieren, Erstellen, Neu usw.)

Eine Bibliothek, die all diese Dinge gut erledigt, ist ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource ). Vollständige Offenlegung - ich habe diese Bibliothek geschrieben - und ich habe sie erfolgreich beim Aufbau mehrerer Unternehmensanwendungen eingesetzt. Sie ist gut getestet und bietet eine API, die den Rails-Entwicklern vertraut sein sollte.

Mein Team und ich entwickeln diese Bibliothek aktiv weiter, und ich würde mich freuen, wenn mehr Angular-Entwickler dazu beitragen und sie testen würden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X