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
- GitHub'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
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 |