Dank einer Vielzahl wertvoller Quellen habe ich einige allgemeine Empfehlungen für die Implementierung von Komponenten in AngularJS-Apps erhalten:
Regler
Der Controller sollte nur eine Zwischenschicht zwischen Modell und Ansicht sein. Versuche es so dünn wie möglich zu machen .
Es wird dringend empfohlen, Geschäftslogik in der Steuerung zu vermeiden . Es sollte zum Modell verschoben werden.
Der Controller kann mit anderen Controllern über den Methodenaufruf (möglich, wenn Kinder mit dem Elternteil kommunizieren möchten) oder die Methoden $ emit , $ Broadcast und $ on kommunizieren . Die gesendeten und gesendeten Nachrichten sollten auf ein Minimum beschränkt werden.
Der Controller sollte sich nicht um Präsentation oder DOM-Manipulation kümmern .
Versuchen Sie , verschachtelte Controller zu vermeiden . In diesem Fall wird der übergeordnete Controller als Modell interpretiert. Injizieren Sie stattdessen Modelle als Shared Services.
Scope in der Steuerung sollte verwendet werden Bindungsmodell mit Blick und
Einkapseln Ansicht Modell für Presentation Model Design - Muster.
Umfang
Behandeln Sie den Bereich in Vorlagen als schreibgeschützt und in Controllern als schreibgeschützt . Der Zweck des Bereichs besteht darin, sich auf das Modell zu beziehen, nicht auf das Modell.
Stellen Sie beim bidirektionalen Binden (ng-Modell) sicher, dass Sie nicht direkt an die Bereichseigenschaften binden.
Modell
Das Modell in AngularJS ist ein vom Dienst definierter Singleton .
Das Modell bietet eine hervorragende Möglichkeit, Daten und Anzeige zu trennen.
Modelle sind Hauptkandidaten für Unit-Tests, da sie normalerweise genau eine Abhängigkeit haben (irgendeine Form von Ereignisemitter, im allgemeinen Fall $ rootScope ) und eine hoch testbare Domänenlogik enthalten .
Das Modell sollte als Implementierung einer bestimmten Einheit betrachtet werden. Es basiert auf dem Prinzip der Einzelverantwortung. Unit ist eine Instanz, die für ihren eigenen Umfang verwandter Logik verantwortlich ist, die eine einzelne Entität in der realen Welt darstellen und in der Programmierwelt in Bezug auf Daten und Status beschreiben kann .
Das Modell sollte die Daten Ihrer Anwendung kapseln und eine API
für den Zugriff auf und die Bearbeitung dieser Daten bereitstellen .
Das Modell sollte tragbar sein, damit es problemlos zu einer ähnlichen Anwendung transportiert werden kann.
Durch das Isolieren der Einheitenlogik in Ihrem Modell haben Sie das Auffinden, Aktualisieren und Warten vereinfacht.
Das Modell kann Methoden allgemeinerer globaler Modelle verwenden, die für die gesamte Anwendung gelten.
Versuchen Sie, die Zusammensetzung anderer Modelle in Ihrem Modell mithilfe der Abhängigkeitsinjektion zu vermeiden, wenn es nicht wirklich darauf ankommt, die Kopplung der Komponenten zu verringern und die Testbarkeit und Verwendbarkeit der Einheiten zu verbessern .
Vermeiden Sie die Verwendung von Ereignis-Listenern in Modellen. Dies erschwert das Testen und tötet Modelle im Allgemeinen nach dem Prinzip der Einzelverantwortung.
Modellimplementierung
Da das Modell eine gewisse Logik in Bezug auf Daten und Status enthalten sollte, sollte es den Zugriff auf seine Mitglieder architektonisch einschränken, damit wir eine lose Kopplung gewährleisten können.
In der AngularJS-Anwendung können Sie dies mithilfe des werkseitigen Servicetyps definieren . Auf diese Weise können wir private Eigenschaften und Methoden sehr einfach definieren und auch öffentlich zugängliche Eigenschaften an einem einzigen Ort zurückgeben, sodass sie für Entwickler wirklich lesbar sind.
Ein Beispiel :
angular.module('search')
.factory( 'searchModel', ['searchResource', function (searchResource) {
var itemsPerPage = 10,
currentPage = 1,
totalPages = 0,
allLoaded = false,
searchQuery;
function init(params) {
itemsPerPage = params.itemsPerPage || itemsPerPage;
searchQuery = params.substring || searchQuery;
}
function findItems(page, queryParams) {
searchQuery = queryParams.substring || searchQuery;
return searchResource.fetch(searchQuery, page, itemsPerPage).then( function (results) {
totalPages = results.totalPages;
currentPage = results.currentPage;
allLoaded = totalPages <= currentPage;
return results.list
});
}
function findNext() {
return findItems(currentPage + 1);
}
function isAllLoaded() {
return allLoaded;
}
// return public model API
return {
/**
* @param {Object} params
*/
init: init,
/**
* @param {Number} page
* @param {Object} queryParams
* @return {Object} promise
*/
find: findItems,
/**
* @return {Boolean}
*/
allLoaded: isAllLoaded,
/**
* @return {Object} promise
*/
findNext: findNext
};
});
Neue Instanzen erstellen
Vermeiden Sie es, über eine Factory zu verfügen, die eine neue Funktion zurückgibt, da dies die Abhängigkeitsinjektion beeinträchtigt und sich die Bibliothek insbesondere für Dritte unangenehm verhält.
Eine bessere Möglichkeit, dasselbe zu erreichen, besteht darin, die Factory als API zu verwenden, um eine Sammlung von Objekten mit den damit verbundenen Getter- und Setter-Methoden zurückzugeben.
angular.module('car')
.factory( 'carModel', ['carResource', function (carResource) {
function Car(data) {
angular.extend(this, data);
}
Car.prototype = {
save: function () {
// TODO: strip irrelevant fields
var carData = //...
return carResource.save(carData);
}
};
function getCarById ( id ) {
return carResource.getById(id).then(function (data) {
return new Car(data);
});
}
// the public API
return {
// ...
findById: getCarById
// ...
};
});
Globales Modell
Versuchen Sie im Allgemeinen, solche Situationen zu vermeiden und Ihre Modelle richtig zu gestalten, damit sie in die Steuerung eingespritzt und aus Ihrer Sicht verwendet werden können.
In einigen Fällen erfordern einige Methoden eine globale Zugänglichkeit innerhalb der Anwendung. Um dies zu ermöglichen, können Sie die Eigenschaft ' common ' in $ rootScope definieren und sie während des Bootstraps der Anwendung an commonModel binden :
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
Alle Ihre globalen Methoden befinden sich in einem " gemeinsamen " Eigentum. Dies ist eine Art Namespace .
Definieren Sie jedoch keine Methoden direkt in Ihrem $ rootScope . Dies kann zu unerwartetem Verhalten führen, wenn es mit der ngModel-Direktive in Ihrem Ansichtsbereich verwendet wird, was im Allgemeinen Ihren Bereich verschmutzt und dazu führt, dass Bereichsmethoden Probleme überschreiben.
Ressource
Mit Resource können Sie mit verschiedenen Datenquellen interagieren .
Sollte nach dem Prinzip der Einzelverantwortung umgesetzt werden .
Im besonderen Fall ist es wiederverwendbar Proxy für HTTP / JSON-Endpunkte.
Ressourcen werden in Modelle eingefügt und bieten die Möglichkeit, Daten zu senden / abzurufen.
Ressourcenimplementierung
Eine Factory, die ein Ressourcenobjekt erstellt, mit dem Sie mit serverseitigen RESTful-Datenquellen interagieren können.
Das zurückgegebene Ressourcenobjekt verfügt über Aktionsmethoden, die Verhalten auf hoher Ebene bereitstellen, ohne dass mit dem $ http-Dienst auf niedriger Ebene interagiert werden muss.
Dienstleistungen
Sowohl Modell als auch Ressource sind Dienste .
Dienste sind nicht zugeordnet, lose gekoppelt Funktionseinheiten, die in sich geschlossen sind.
Dienste sind eine Funktion, die Angular von der Serverseite aus für clientseitige Webanwendungen bereitstellt, wo Dienste seit langem häufig verwendet werden.
Dienste in Angular-Apps sind austauschbare Objekte, die mithilfe der Abhängigkeitsinjektion miteinander verbunden werden.
Angular bietet verschiedene Arten von Diensten. Jeder mit seinen eigenen Anwendungsfällen. Weitere Informationen finden Sie unter Grundlegendes zu Servicetypen.
Versuchen Sie, die Hauptprinzipien der Servicearchitektur in Ihrer Anwendung zu berücksichtigen .
Im Allgemeinen laut Web Services Glossar :
Ein Dienst ist eine abstrakte Ressource, die die Fähigkeit darstellt, Aufgaben auszuführen, die aus Sicht von Anbieterentitäten und anfordernden Entitäten eine kohärente Funktionalität bilden. Um verwendet zu werden, muss ein Service von einem konkreten Anbieteragenten realisiert werden.
Client-seitige Struktur
Im Allgemeinen wird die Client-Seite der Anwendung in Module aufgeteilt . Jedes Modul sollte als Einheit testbar sein.
Versuchen Sie, Module abhängig von Merkmal / Funktionalität oder Ansicht zu definieren , nicht nach Typ. Weitere Informationen finden Sie in der Präsentation von Misko .
Modulkomponenten können herkömmlicherweise nach Typen wie Steuerungen, Modellen, Ansichten, Filtern, Anweisungen usw. gruppiert werden.
Das Modul selbst bleibt jedoch wiederverwendbar , übertragbar und testbar .
Für Entwickler ist es auch viel einfacher, einige Teile des Codes und alle seine Abhängigkeiten zu finden.
Bitte beachten Sie - Code - Organisation in Groß AngularJS und JavaScript - Anwendungen für weitere Einzelheiten.
Ein Beispiel für die Strukturierung von Ordnern :
|-- src/
| |-- app/
| | |-- app.js
| | |-- home/
| | | |-- home.js
| | | |-- homeCtrl.js
| | | |-- home.spec.js
| | | |-- home.tpl.html
| | | |-- home.less
| | |-- user/
| | | |-- user.js
| | | |-- userCtrl.js
| | | |-- userModel.js
| | | |-- userResource.js
| | | |-- user.spec.js
| | | |-- user.tpl.html
| | | |-- user.less
| | | |-- create/
| | | | |-- create.js
| | | | |-- createCtrl.js
| | | | |-- create.tpl.html
| |-- common/
| | |-- authentication/
| | | |-- authentication.js
| | | |-- authenticationModel.js
| | | |-- authenticationService.js
| |-- assets/
| | |-- images/
| | | |-- logo.png
| | | |-- user/
| | | | |-- user-icon.png
| | | | |-- user-default-avatar.png
| |-- index.html
Ein gutes Beispiel für die Strukturierung von Winkelanwendungen ist die Angular-App - https://github.com/angular-app/angular-app/tree/master/client/src
Dies wird auch von modernen Anwendungsgeneratoren berücksichtigt - https://github.com/yeoman/generator-angular/issues/109