Questo documento è finalizzato al presentare una serie di buone pratiche e linee guida per lo stile e l'applicazione di AngularJS. Queste pratiche derivano da:
- Codice AngularJS
- Codice e articoli che ho letto
- Esperienza
Nota 1: Questo è ancora un abbozzo della guida stilistica: il suo obiettivo è di essere guidato dalla community, quindi eventuali migliorie sarebbero molto apprezzate da parte di tutti.
In questa guida non si accennerà a comuni linee guida sulla programmazione JavaScript. Queste possono essere trovate nei seguenti link:
- Google's JavaScript style guide
- Mozilla's JavaScript style guide
- Douglas Crockford's JavaScript style guide
- Airbnb JavaScript style guide
Per la programmazione AngularJS è raccomandato: Google's JavaScript style guide.
Nel repository AngularJS su GitHub c'è una sezione simile curata da ProLoser. Potete visionarla quì.
- Generale
- Module
- Controller
- Directive
- Filter
- Service
- Template
- Routing
- Testing
- Collaborazioni
- Collaboratori
Dal momento che una grande applicazione di AngularJS implica tante componenti, sarebbe consigliabile strutturare i file e le directory in maniera gerarchica. Ci sono due possibili approcci:
- Creare una divisione ad alto livello in base al tipo di componenti ed una a basso livello in base alle funzionalità
In questo modo le directory avranno questa struttura:
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── home
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── about
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── home
│ │ │ └── directive1.js
│ │ └── about
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── home
│ │ └── about
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── partials
├── lib
└── test
- Creare una divisione ad alto livello in base alle funzionalità ed una a basso livello in base al tipo di componenti
Questa divisione avrà invece questo tipo di struttura:
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── home
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ └── filter2.js
│ │ └── services
│ │ ├── service1.js
│ │ └── service2.js
│ └── about
│ ├── controllers
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ └── filter3.js
│ └── services
│ └── service3.js
├── partials
├── lib
└── test
- Quando si creano le directive sarebbe utile mettere tutti i file associati ad una data directive (es: template, CSS/SASS file, JavaScript) in una singola cartella. Se scegliete di usare questo stile, siate coerenti e usatelo in ogni occasione.
app
└── directives
├── directive1
│ ├── directive1.html
│ ├── directive1.js
│ └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
└── directive2.sass
Questo approccio può essere combinato con entrambe le strutture di directory trattate in precedenza
- Un'ulteriore leggera variazione di entrambe le strutture è quella usata in ng-boilerplate. In questa, le unit tests per un determinato componente sono poste nella stessa cartella del componente stesso. In questo modo quando vengono fatti cambiamenti ad un componente è più semplice trovare il relativo test. Il test, in questo modo, fa anche da documentazione e mostra i casi d'uso.
services
├── cache
│ ├── cache1.js
│ └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js
- Il file
app-js
contiene la definizione dei route, impostazioni e/o bootstrap manuali (se richiesti). - Ogni file JavaScript dovrebbe contenere solo un singolo componente. Il file dovrebbe essere chiamato con il nome del componente.
- Per il progetto AngularJS, usate una struttura di template simile alla seguente:
- The
app.js
file contains route definitions, configuration and/or manual bootstrap (if required). ng-boilerplate.
Personalmente preferisco la prima struttura perché rende più facili da trovare i componenti più comuni.
Convenzioni su come chiamare i componenti possono essere trovate nelle sezioni relative agli stessi.
TLDR; Metti lo script in basso.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MyApp</title>
</head>
<body>
<div ng-app="myApp">
<div ng-view></div>
</div>
<script src="angular.js"></script>
<script src="app.js"></script>
</body>
</html>
Mantieni il codice semplice e metti le direttive AngularJS in fondo. In questo modo è più semplice la lettura e la mantenibilità del codice HTML.
<form class="frm" ng-submit="login.authenticate()">
<div>
<input class="ipt" type="text" placeholder="name" require ng-model="user.name">
</div>
</form>
Gli altri attrubuti HTML dovrebbero seguire la seguente guida
- Eseguire un watch solo per le variabili più importanti (es: usando un tipo di
comunicazione real-time, non permettere un ciclo
$digest
per ogni messaggio ricevuto). - Per contenuti che sono inizializzati solo una volta e mai cambiati, usare un
single-time watcher come
bindonce
. - Rendere le computazioni in
$watch
il più semplici possibile. Rendendo pesanti e lenti i calcoli in un singolo$watch
, si abbasseranno le prestazioni dell'intera applicazione (il ciclo$digest
è eseguito in un singolo thread a causa della natura single-threaded di JavaScript). - Settare il ternzo parametro nella funzione
$timeout
a false, per evitare un ulteriore ciclo$digest
quando variabili non necessarie sono implicate nella funzione callback di$timeout
.
- Usare:
$timeout
invece disetTimeout
$interval
invece disetInterval
$window
invece diwindow
$document
invece didocument
$http
invece di$.ajax
Questo renderà il testing più semplice e, in alcuni casi, impedirà
comportamenti inaspettati (come il dimenticarsi $scope.$apply
in
setTimeout
).
-
Automatizzare il lavoro usando utility come:
-
Usare promise (
$q
) invece dei callback. Questo renderà il codice più elegante e pulito, oltre che salvarvi dall'inferno dei callback. -
Usare
$resource
invece di$http
. Il più alto livello d'astrazione vi salverà dalle ridondanze. -
Usare un pre-minificatore per AngularJS (ad esempio ngmin o ng-annotate) per evitare problemi dopo la minificazione.
-
Non usare variabili globali. Risolvere tutte le dipendenze usando il Dipendency Injection.
-
Non riempire lo
$scope
se non con variabili e funzioni usate nel template. -
E' da preferire l'utilizzo di controller invece di
ngInit
. Il solo utilizzo appropriato dingInit
è per rinominare particolari proprietà dingRepeat
. A parte quest'ultimo caso, si dovrebbero usare controller per inizializzare variabili nello scope. -
Non usare il prefisso
$
per i nomi di variabili, proprietà o metodi. Questo prefisso è riservato ad AngularJS. -
Quando si risolvono dipendenze attraverso il meccanismo DI di AngularJS, ordinare tutte le dipendenze in base al loro tipo - le dipendenze built-in di AngularJS dovrebbero essere le prime, seguite da quelle create da voi:
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
return {
//Something
};
});
- Il nome dei module dovrebbe essere assegnato secondo il lowerCamelCase. Per
indicare che un module
b
è submodule dia
si possono concatenare usando un namespacing come:a.b
.
Ci sono due metodi più comuni per strutturare i modulo:
- In base alla funzionalità
- In base al tipo di componente
Al momento non c'è grande differenza, ma il primo metodo sembra più pulito. Inoltre, se verrà implementato il lazy-loading modules (il che non è tra i piani di AngularJS), le prestazioni delle app aumenteranno.
- Non manipolare il DOM nei controller: questo renderà i controller difficili da testare e violerà il principio di separazione degli interessi. Usare, invece, le directive.
- Il nome dei controller è assegnato in base alla loro funzionalità (ad esempio
shopping cart, homepage, admin panel) più il suffisso 'Ctrl'. I nomi utilizzano
in questo caso l'UpperCamelCase (
HomePageCtrl
,ShoppingCartCtrl
,AdminPanelCtrl
, ecc.). - I controller non dovrebbero mai essere definiti come globali (anche se AngularJS lo permette, inquinare il namspace globale è una brutta partica).
- Usare la sintassi ad array per la definizione dei controller:
module.controller('MyCtrl', ['dependency1', 'dependency2', ..., 'dependencyn', function (dependency1, dependency2, ..., dependencyn) {
//...body
}]);
Usando questo tipo di definizione si evitano problemi con la minificazione. So possono generare definizioni ad array da quelle standard utilizzando tool come ng-annotate o le task di grunt grunt-ng-annotate Usare il nome originale delle dipendenze dei controller. Questo vi aiuterà nel produrre un codice più leggibile:
module.controller('MyCtrl', ['$scope', function (s) {
//...body
}]);
è più leggibile di:
module.controller('MyCtrl', ['$scope', function ($scope) {
//...body
}]);
Questo principio si applica soprattutto quando i file sono così grandi da aver bisogno di scrollare la pagina. Questo farebbe dimenticare facilmente al lettore quale variabile è legata a quale dipendenza.
- Rendere i controller il più leggeri possibile. Astrarre le funzioni comuni in service.
- Comunicare all'interno dei controller invocando metodi (possibile quando un
figlio vuole comunicare con il genitore) o con i metodi
$emit
,$broadcast
e$on
. I messaggi emessi e trasmessi dovrebbero ridursi al minimo. - Creare una lista di tutti i messaggi che sono passati usando
$emit
e$broadcast
e manovrarli con attenzione per evitare collisioni di nomi ed bug. - Quando si ha bisogno di formattare dati, incapsulare la logica di formattazione in un filter e dichiararlo come dipendenza:
module.filter('myFormat', function () {
return function () {
//body...
};
});
module.controller('MyCtrl', ['$scope', 'myFormatFilter', function ($scope, myFormatFilter) {
//body...
}]);
- In caso di controller annidati, usare "nested scoping" (la sintassi
controllerAs
):
app.js
module.config(function ($routeProvider) {
$routeProvider
.when('/route', {
templateUrl: 'partials/template.html',
controller: 'HomeCtrl',
controllerAs: 'home'
});
});
HomeCtrl
function HomeCtrl() {
this.bindingValue = 42;
}
template.html
<div ng-bind="home.bindingValue"></div>
- Assegnare i nomi alle directive seguendo il lowerCamelCase
- Usare
scope
invece di$scope
alle funzioni link. Per le funzioni compile e post/pre link, avrete già definito i parametri che verranno passati quando la funzione verrà invocata e non vi sarà possibile cambiarli usando il DI. Questo stile è utilizzato anche nel codice di AngularJS - Usare prefissi personalizzati per le direttive per evitare collisioni con librerie di terze parti.
- Non usare i prefissi
ng
eui
, poichè sono già utilizzati da AngularJS e AngularJS UI. - La manipolazione del DOM deve essere effettuata solo attraverso le directive.
- Creare scope isolati quando si creano directiv riusabili.
- Usare direttive come attributi o elementi invece di commenti o classi: questo renderà il codice più leggibile.
- Usare
$scope.$on('$destroy', fn)
per pulire. Questo è molto utile specialmente quando si fa il wrapping di plugin e directive di terze parti. - Non dimenticare di usare
$sce
quando si ha a che fare con contenuti non affidabili
- Assegnare i nomi ai filter seguendo il lowerCamelCase.
- I filter dovrebbero essere più leggeri possibile. Vengono spesso invocati
durante il ciclo
$digest
, quindi un filtro poco performante potrebbe incidere sulle performance della vostra applicazione. - Usare ogni filter per un solo scopo mantenendoli coerenti. Manipolazioni più complesse possono essere ottenute concatenando più filter.
Questa sezione include informazioni sui componenti service di AngularJS. Questi
non dipendono dal tipo di definizione (es: come provider, .factory
,
.service
) a meno che questo non è esplicitamente menzionato.
-
Usare camelCase per assegnare nomi ai service:
-
UpperCamelCase (PascalCase) per service usati come costruttori. Es:
module.controller('MainCtrl', function ($scope, User) { $scope.user = new User('foo', 42); }); module.factory('User', function () { return function User(name, age) { this.name = name; this.age = age; }; });
-
lowerCamelCase per gli altri casi.
-
-
Incapsulare tutte le logiche di business in service.
-
Service che rappresentano domini dovrebbero essere definiti come
service
piuttosto chefactory
. In questo modo ci si può avvantagiare dell' ereditarietà "klassical" in modo più semplice:
function Human() {
//body
}
Human.prototype.talk = function () {
return "I'm talking";
};
function Developer() {
//body
}
Developer.prototype = Object.create(Human.prototype);
Developer.prototype.code = function () {
return "I'm coding";
};
myModule.service('Human', Human);
myModule.service('Developer', Developer);
- Per cache di session-level si può usare
$cacheFactory
. Questo dovrebbe essere usato come risultato cache di richieste o pesanti calcoli. - Se un dato service richiede una configurazione, definirlo come provider e
configurarlo nel callback di
config
:
angular.module('demo', [])
.config(function ($provide) {
$provide.provider('sample', function () {
var foo = 42;
return {
setFoo: function (f) {
foo = f;
},
$get: function () {
return {
foo: foo
};
}
};
});
});
var demo = angular.module('demo');
demo.config(function (sampleProvider) {
sampleProvider.setFoo(41);
});
- Usare
ng-bind
ong-cloak
invece di{{ }}
per evitare il flashing content. - Evitare di scrivere espressioni complesse nei template.
- Quando si ha bisogno di settare
src
ad un'immagine in modo dimamico, usareng-src
invece disrc
con{{ }}
. - Quando si ha bisogno di settare
href
ad un tag dimaicamente, usareng-href
invece dihref
con{{ }}
. - Invece di usare variabili di scope come stringa e usarli nell'attributo
style
racchiusi da{{ }}
, utilizzare la directiveng-style
con parametri oggetti e variabili dello scope come valori:
<script>
...
$scope.divStyle = {
width: 200,
position: 'relative'
};
...
</script>
<div ng-style="divStyle">my beautifully styled div which will work in IE</div>;
- Usare
resolve
per risolvere le dipendenze prima di mostrare le view.
In corso di stesura.
Finchè la sezione non è pronta, si può fare riferimento a questo link.
Dal momento che l'obiettivo di questa guida stilistica è di essere portata avanti dalla community, eventuali collaborazioni sono grandemente apprezzate. Ad esempio, si può contribuire estendendo la sezione di Testing o traducendo la guida nella propria lingua
mgechev | pascalockert | mainyaa | rubystream | lukaszklis |
cironunes | cavarzan | tornad | jmblog | bargaorobalo |
astalker | valgreens | bitdeli-chef | dchest | gsamokovarov |
ntaoo | hermankan | jesselpalmer | capaj | jordanyee |
nacyot | kirstein | mo-gr | cryptojuice | olov |
vorktanamobay | thomastuts | grapswiz | coderhaoxin | dreame4 |