30

Ich lese diese beiden großen Artikel:AngularJS: Wie sollten Controller und Fabriken/Services mit einem reichen, hierarchischen Objektmodell strukturiert sein?

The state of angularjs controllers von Jonathan Creamer

und

Rethinking AngularJS Controllers von Todd Motto

In diesen Artikeln werden die Autoren über die richtige Art und Weise sprechen Controller zu verwenden (was sie sind blutleere Brücken zwischen dem Blick und dem Modell) und Fabriken/Dienstleistungen (wo die Geschäftslogik wirklich leben sollte).

Das sind großartige Informationen, und ich war wirklich aufgeregt, die Controller in einem meiner Projekte zu refactoring, aber ich fand schnell, dass die in den Artikeln gezeigte Struktur bricht, wenn Sie ein reichhaltiges Objektmodell haben.

Hier ist eine kurze Zusammenfassung des Setup von "Rethinking AngularJS Controllers":

Hier ist der Controller:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) { 

    var vm = this; 

    vm.messages = InboxFactory.messages; 

    vm.openMessage = function (message) { 
     InboxFactory.openMessage(message); 
    }; 

    vm.deleteMessage = function (message) { 
     InboxFactory.deleteMessage(message); 
    }; 

    InboxFactory 
     .getMessages() 
     .then(function() { 
     vm.messages = InboxFactory.messages; 
    }); 

}); 

und hier ist die Fabrik:

app.factory('InboxFactory', function InboxFactory ($location, NotificationFactory) { 

    factory.messages = []; 

    factory.openMessage = function (message) { 
    $location.search('id', message.id).path('/message'); 
    }; 

    factory.deleteMessage = function (message) { 
    $http.post('/message/delete', message) 
    .success(function (data) { 
     factory.messages.splice(index, 1); 
     NotificationFactory.showSuccess(); 
    }) 
    .error(function() { 
     NotificationFactory.showError(); 
    }); 
    }; 

    factory.getMessages = function() { 
    return $http.get('/messages') 
    .success(function (data) { 
     factory.messages = data; 
    }) 
    .error(function() { 
     NotificationFactory.showError(); 
    }); 
    }; 

    return factory; 

}); 

Das ist großartig und weil providers (die Fabrik) sind Singletons, die Daten werden über Ansichten hinweg gepflegt und können abgerufen werden, ohne sie von der API neu laden zu müssen.

Dies funktioniert problemlos , wenn messages ein Top-Level-Objekt sind. Aber was passiert, wenn sie es nicht sind? Was ist, wenn es sich um eine App zum Durchsuchen der Posteingänge anderer Benutzer handelt? Vielleicht sind Sie ein Administrator und möchten die Posteingänge eines beliebigen Benutzers verwalten und durchsuchen können. Vielleicht brauchen Sie die Postfächer mehrerer Benutzer gleichzeitig. Wie funktioniert das? Das Problem besteht darin, dass Posteingangnachrichten in dem Dienst gespeichert sind, d. H. InboxFactory.messages.

Was passiert, wenn die Hierarchie wie folgt aus:

      Organization 
           | 
       __________________|____________________ 
      |     |     | 
     Accounting  Human Resources   IT 
      |     |     | 
    ________|_______  _____|______  ______|________ 
    |  |  | |  |  |  |  |  | 
    John  Mike Sue Tom Joe Brad May Judy  Jill 
    |  |  | |  |  |  |  |  | 
    Inbox Inbox Inbox Inbox Inbox Inbox Inbox Inbox Inbox 

Jetzt messages sind mehrere Ebenen tief in der Hierarchie, und haben keine Bedeutung auf eigene Faust. Sie können keine Nachrichten in der Factory speichern, InboxFactory.messages, da Sie Nachrichten für mehrere Benutzer gleichzeitig abrufen müssen.

Jetzt haben Sie eine OrganizationFactory, eine DepartmentFactory, eine UserFactory und eine InboxFactory. Das Abrufen von "Nachrichten" muss im Zusammenhang mit einer user stehen, die im Zusammenhang mit einer department steht, die im Zusammenhang mit einer organization steht. Wie und wo sollten die Daten gespeichert werden? Wie sollte es zurückgezogen werden?

Also wie soll das gelöst werden? Wie sollten Controller, Fabriken/Services und reiche Objektmodelle strukturiert sein?

An diesem Punkt in meinem Denken, neige ich dazu, es nur schlank zu halten und kein reiches Objektmodell zu haben. Speichern Sie die Objekte einfach auf dem $ -Skope, der in den Controller injiziert wurde. Wenn Sie zu einer neuen Ansicht navigieren, laden Sie sie von der API neu. Wenn Sie müssen einige Daten über Ansichten beibehalten, können Sie diese Brücke mit einem Service oder Fabrik bauen, aber es sollte nicht die Art, wie Sie tun meisten Dinge.

Wie haben andere das gelöst? Gibt es irgendwelche Muster dafür?

+0

Sollte dies nicht eine Aufgabe für die Back-End-Endpunkt sein? Wenn Sie also als Jonh 'GET/messages' eingeloggt sind, werden Nachrichten nur für John zurückgegeben und die Schnittstelle unterscheidet sich von der, die der Chef der Organisation sieht, wenn er eingeloggt ist. – Terafor

Antwort

0

Nachdem ich viel getüftelt und verschiedene Ansätze versucht habe, ist meine letzte Entscheidung, dass Sie Ihr Rich-Objekt-Modell nicht über Ansichten hinweg beibehalten sollten.

Ich halte das Objektmodell super schlank und lade genau das, was ich für jede Ansicht brauche. Es gibt High-Level-Daten, die ich behalte (Benutzerinformationen wie Name, ID, E-Mail usw. und Organisationsdaten, mit welcher Organisation sie angemeldet sind), aber alles andere wird für die aktuelle Ansicht geladen.

Mit diesem schlanken Ansatz, hier ist was meine Fabrik aussehen würde:

app.factory('InboxFactory', function InboxFactory ($location, NotificationFactory) { 

factory.messages = []; 

factory.openMessage = function (message) { 
    $location.search('id', message.id).path('/message'); 
}; 

factory.deleteMessage = function (message) { 
    $http.post('/message/delete', message) 
    .success(function (data) { 
    NotificationFactory.showSuccess(); 
    return data; 
    }) 
    .error(function() { 
    NotificationFactory.showError(); 
    return null; 
    }); 
}; 

factory.getMessages = function (userId) { 
    return $http.get('/messages/user/id/'+userId) 
    .success(function (data) { 
    return data; 
    }) 
    .error(function() { 
    NotificationFactory.showError(); 
    return null; 
    }); 
}; 

return factory; 

}); 

und die Steuerung:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) { 

    var vm = this; 

    vm.messages = {}; 

    vm.openMessage = function (message) { 
    InboxFactory.openMessage(message); 
    }; 

    vm.deleteMessage = function (message) { 
    InboxFactory.deleteMessage(message); 
    }; 

    InboxFactory 
    .getMessages(userId) //you can get the userId from anywhere you want. 
    .then(function (data) { 
     vm.messages = data; 
    }); 

}); 

Die Vorteile so weit sind:

  • vereinfachte Anwendungslogik
  • schlank und gemein, leicht (ich lade nur genau das, was ich für den aktuellen Zustand benötigen)
  • weniger Speicherverbrauch, der insgesamt eine bessere Leistung übersetzt, vor allem auf mobilen
3

Sie können ein Rich-Objekt-Modell verwenden, aber für Objekte, die keine Top-Level-Objekte sind, sollten ihre Fabriken eine API zum Erstellen neuer Instanzen bereitstellen, anstatt als Singletons verwendet zu werden. Dies steht etwas im Widerspruch zu dem Design vieler Apps, die Sie heutzutage sehen, die eher funktional als objektorientiert sind. Ich möchte nicht die Vor- und Nachteile beider Ansätze kommentieren, und ich denke nicht, dass Angular Sie dazu zwingt Das Eine oder das Andere.

Ihr Beispiel, neu gestaltet, in Pseudo-Code:

app.controller('InboxCtrl', function InboxCtrl (InboxFactory) { 

    var inbox = InboxFactory.createInbox(); 

    $scope.getMessages = function(){ 
     inbox.getMessages() 
      .then(...) 

    $scope.deleteMessages = function(){ 
     inbox.deleteMessages() 
      .then(...) 

}); 
+0

Danke AlexMa. Darauf lehne ich mich hin. Machen Sie es so, würden Sie befürworten, das Objekt-Diagramm irgendwo zwischen den Ansichten gespeichert zu haben? Oder befürworten Sie lieber das Diagramm, das für jede zutreffende Ansicht neu erstellt wurde? Ich lehne mich dem späteren zu, um die App schlank und leicht zu halten. – richard

+1

würde ich zustimmen; Ich versuche zu vermeiden, dass der App-Status komplexer wird, als es sein muss, da er Nebenwirkungen erzeugt und später schwieriger zu testen und zu ändern ist. – AlexMA

2

Ihre Situation wird viel einfacher, wenn Sie eine Route basierten Ansatz annehmen (a la ngRoute oder so ähnlich). Betrachten Sie diese Alternative - Warnung ungeprüfter Code:

app.config(function($routeProvider) { 
    $routeProvider 
    .when('/inbox/:inboxId', 
     templateUrl: 'views/inbox.html', 
     controller: 'InboxCtrl', 
     controllerAs: 'inbox', 
     resolve: { 
     inboxMessages: function(InboxFactory) { 
      // Use use :inboxId route param if you need to work with multiple 
      // inboxes. Taking some libery here, we'll assuming 
      // `InboxFactory.getMessages()` returns a promise that will resolve to 
      // an array of messages; 
      return InboxFactory.getMessages(); 
     } 
     } 
    // ... other routes 
    .otherwise: { 
     // ... 
    }; 
}); 

app.controller('InboxCtrl', function InboxCtrl (InboxFactory, inboxMessages) { 
    var vm = this; 
    vm.messages = inboxMessages; 
    vm.openMessage = InboxFactory.openMessage; 
    vm.deleteMessage = InboxFactory.deleteMessage; 
}); 

Schauen Sie, wie schlank der Controller jetzt ist! Zugegeben, ich habe an einigen Stellen etwas kompaktere Syntax verwendet, aber das zeigt, wie unser Controller wirklich nur Dinge zusammenklebt.

Wir können die Dinge weiter rationalisieren, indem wir InboxFactory.messages loswerden, wann würden wir es tatsächlich benutzen? Wir müssen nur sicherstellen, dass es nach InboxFactory.getMessages aufgefüllt werden muss. Lassen Sie uns also dieses Versprechen für die Nachrichten selbst auflösen.

Das Speichern von Daten in Singletons auf diese Weise kann in einigen Fällen die einfachste Lösung sein, macht jedoch das Leben schwierig, wenn diese Daten im laufenden Betrieb abgerufen werden müssen. Sie werden sich am besten an APIs und Fabriken lehnen (wie AlexMA vorschlägt), die notwendigen Daten herunterziehen, wenn sich eine Route ändert (z. B. möchte der Benutzer einen anderen Posteingang ansehen) und diese Daten direkt in den entsprechenden Controller einspeisen.

Ein weiterer Vorteil dieses Formulars besteht darin, dass wir unsere Daten zum Zeitpunkt der Instanziierung des Controllers zur Hand haben. Wir müssen nicht mit asynchronen Zuständen jonglieren oder uns darum kümmern, viel Code in Callbacks zu schreiben. Als eine Folge davon bekommen wir Datenladefehler, bevor eine neue Ansicht des Posteingangs angezeigt wird, und der Benutzer bleibt nicht in einem halb gebackenen Zustand stecken.

Weiter zu dem Punkt Ihrer Frage, obwohl Sie feststellen, dass die Last zu wissen, wie Ihre reiche Modellstruktur zusammenpasst, nicht mehr das Problem des Controllers ist. Es erhält nur einige Daten und macht der Ansicht eine Reihe von Methoden zugänglich.

+0

eckig ui Router bietet verschachtelte Routen und ist sehr praktisch für diese Anwendungsfälle – Eloims