Grazie a un'enorme quantità di preziose fonti ho ricevuto alcuni consigli generali per l'implementazione dei componenti nelle app AngularJS:
controllore
Il controller dovrebbe essere solo uno strato intermedio tra modello e vista. Cerca di renderlo il più sottile possibile.
Si consiglia vivamente di evitare la logica aziendale nel controller. Dovrebbe essere spostato nel modello.
Il controller può comunicare con altri controller usando il metodo di invocazione (possibile quando i bambini vogliono comunicare con il genitore) o $ emettono , $ trasmettono e $ su metodi. I messaggi emessi e trasmessi dovrebbero essere ridotti al minimo.
Il controller non dovrebbe preoccuparsi della presentazione o della manipolazione del DOM.
Cerca di evitare i controller nidificati . In questo caso il controller principale viene interpretato come modello. Iniettare invece i modelli come servizi condivisi.
L'ambito del controller deve essere utilizzato per il modello di rilegatura con vista e
incapsulamento di Visualizza modello come per il modello di progettazione del modello di presentazione.
Scopo
Considera l'ambito come di sola lettura nei modelli e di sola scrittura nei controller . Lo scopo dell'ambito è fare riferimento al modello, non essere il modello.
Quando si esegue l'associazione bidirezionale (modello ng), assicurarsi di non associarsi direttamente alle proprietà dell'ambito.
Modello
Il modello in AngularJS è un singleton definito dal servizio .
Il modello offre un modo eccellente per separare dati e visualizzazione.
I modelli sono candidati principali per i test unitari, poiché in genere hanno esattamente una dipendenza (una qualche forma di emettitore di eventi, nel caso comune $ rootScope ) e contengono una logica di dominio altamente testabile .
Il modello dovrebbe essere considerato come un'implementazione di un'unità particolare. Si basa sul principio della responsabilità singola. L'unità è un'istanza responsabile del proprio ambito di logica correlata che può rappresentare una singola entità nel mondo reale e descriverla nel mondo della programmazione in termini di dati e stato .
Il modello dovrebbe incapsulare i dati dell'applicazione e fornire un'API
per accedere e manipolare tali dati.
Il modello deve essere portatile in modo che possa essere facilmente trasportato in un'applicazione simile.
Isolando la logica dell'unità nel modello è stato reso più semplice individuare, aggiornare e gestire.
Il modello può utilizzare metodi di modelli globali più generali comuni per l'intera applicazione.
Cerca di evitare la composizione di altri modelli nel tuo modello usando l'iniezione delle dipendenze se non dipende realmente dalla riduzione dell'accoppiamento dei componenti e dalla testabilità e usabilità dell'unità .
Cerca di evitare l'uso dei listener di eventi nei modelli. Li rende più difficili da testare e generalmente uccide i modelli in termini di principio di responsabilità singola.
Implementazione del modello
Poiché il modello dovrebbe incapsulare alcune logiche in termini di dati e stato, dovrebbe limitare architettonicamente l'accesso ai suoi membri in modo da garantire un accoppiamento libero.
Il modo per farlo nell'applicazione AngularJS è definirlo usando il tipo di servizio di fabbrica . Questo ci consentirà di definire proprietà e metodi privati molto facilmente e anche di restituire quelli accessibili al pubblico in un unico posto che lo renderà davvero leggibile per gli sviluppatori.
Un esempio :
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
};
});
Creazione di nuove istanze
Cerca di evitare di avere una fabbrica che restituisce una nuova funzione capace poiché questa inizia a interrompere l'iniezione di dipendenza e la libreria si comporterà in modo imbarazzante, specialmente per terze parti.
Un modo migliore per ottenere lo stesso risultato è utilizzare la factory come API per restituire una raccolta di oggetti con metodi getter e setter collegati ad essi.
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
// ...
};
});
Modello globale
In generale, cerca di evitare tali situazioni e progetta i tuoi modelli correttamente in modo che possano essere iniettati nel controller e utilizzati nella tua vista.
In alcuni casi alcuni metodi richiedono l'accessibilità globale all'interno dell'applicazione. Per renderlo possibile è possibile definire la proprietà ' common ' in $ rootScope e associarla a commonModel durante il bootstrap dell'applicazione:
angular.module('app', ['app.common'])
.config(...)
.run(['$rootScope', 'commonModel', function ($rootScope, commonModel) {
$rootScope.common = 'commonModel';
}]);
Tutti i tuoi metodi globali vivranno all'interno della proprietà ' comune '. Questo è un qualche tipo di spazio dei nomi .
Ma non definire alcun metodo direttamente in $ rootScope . Ciò può comportare comportamenti imprevisti se utilizzato con la direttiva ngModel all'interno del proprio ambito di visualizzazione, in genere sporcare il proprio ambito e portare a metodi di ambito che prevalgono sui problemi.
Risorsa
Risorsa consente di interagire con diverse origini dati .
Dovrebbe essere implementato usando il principio della responsabilità singola .
In particolare, si tratta di un proxy riutilizzabile per gli endpoint HTTP / JSON.
Le risorse vengono iniettate nei modelli e offrono la possibilità di inviare / recuperare dati.
Implementazione delle risorse
Una fabbrica che crea un oggetto risorsa che consente di interagire con origini dati lato server RESTful.
L'oggetto risorsa restituito ha metodi di azione che forniscono comportamenti di alto livello senza la necessità di interagire con il servizio $ http di basso livello.
Servizi
Sia il modello che la risorsa sono servizi .
I servizi sono unità di funzionalità non associate , liberamente accoppiate e indipendenti.
I servizi sono una funzionalità che Angular offre alle app Web sul lato client dal lato server, dove i servizi sono stati comunemente utilizzati per molto tempo.
I servizi nelle app angolari sono oggetti sostituibili che sono collegati tra loro mediante l'iniezione delle dipendenze.
Angular viene fornito con diversi tipi di servizi. Ognuno con i propri casi d'uso. Per i dettagli, leggere Informazioni sui tipi di servizio .
Prova a considerare i principi fondamentali dell'architettura di servizio nella tua applicazione.
In generale secondo il Glossario dei servizi Web :
Un servizio è una risorsa astratta che rappresenta una capacità di eseguire attività che formano una funzionalità coerente dal punto di vista delle entità dei fornitori e delle entità richiedenti. Per essere utilizzato, un servizio deve essere realizzato da un agente fornitore concreto.
Struttura lato client
In generale, il lato client dell'applicazione è suddiviso in moduli . Ogni modulo dovrebbe essere testabile come unità.
Prova a definire i moduli in base alla caratteristica / funzionalità o vista , non per tipo. Vedi la presentazione di Misko per i dettagli.
I componenti del modulo possono essere raggruppati convenzionalmente per tipi come controller, modelli, viste, filtri, direttive ecc.
Ma il modulo stesso rimane riutilizzabile , trasferibile e testabile .
È anche molto più facile per gli sviluppatori trovare alcune parti del codice e tutte le sue dipendenze.
Per i dettagli, fare riferimento a Organizzazione del codice in grandi applicazioni AngularJS e JavaScript .
Un esempio di strutturazione delle cartelle :
|-- 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
Un buon esempio di strutturazione di applicazioni angolari è implementato da angular-app - https://github.com/angular-app/angular-app/tree/master/client/src
Questo è anche considerato dai moderni generatori di applicazioni - https://github.com/yeoman/generator-angular/issues/109