Das Ziel dieses Style Guides ist, eine Sammlung von Best Practices und Gestaltungsrichtlinien für AngularJS-Anwendungen aufzuzeigen. Sie wurden aus den folgenden Quellen zusammengestellt:
- AngularJS-Quelltext
- Quelltexte oder Artikel, die ich gelesen habe
- Meine eigene Erfahrung
Hinweis 1: Hierbei handelt es sich noch um einen Entwurf des Style Guides, dessen vorrangiges Ziel es ist, gemeinschaftlich von der Community entwickelt zu werden. Die gesamte Community wird es daher begrüßen, wenn Lücken gefüllt werden. Hinweis 2: Bevor du den Richtlinien in einer der Übersetzungen des englischsprachigen Dokuments folgst, vergewissere dich, dass diese aktuell sind. Die jüngste Version des AngularJS Style Guide ist im Dokument README.md.
Du wirst in diesem Style Guide keine allgemeinen Richtlinien für die JavaScript-Entwicklung finden. Solche finden sich unter:
- Googles JavaScript-Style-Guide
- Mozillas JavaScript-Style-Guide
- GitHubs JavaScript-Style-Guide
- Douglas Crockfords JavaScript-Style-Guide
- Airbnb JavaScript-Style-Guide
Für die AngularJS-Entwicklung ist Googles JavaScript-Style-Guide empfehlenswert.
Im GitHub-Wiki von AngularJS gibt es einen ähnlichen Abschnitt von ProLoser, den du dir hier ansehen kannst.
- Allgemein
- Module
- Controller
- Direktiven
- Filter
- Services
- Templates
- Routing
- Testen
- Mitmachen
- Mitwirkende
Da eine große AngularJS-Anwendung viele Komponenten hat, sollten diese mit Hilfe einer Verzeichnishierarchie strukturiert werden. Es gibt zwei Basis-Herangehensweisen:
- Auf einer oberen Ebene eine Aufteilung nach Art der Komponenten und auf einer tieferen Ebene eine Aufteilung nach Funktionalität.
Die Verzeichnisstruktur wird in diesem Fall folgendermaßen aussehen:
.
├── app
│ ├── app.js
│ ├── controllers
│ │ ├── page1
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ └── page2
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── page1
│ │ │ └── directive1.js
│ │ └── page2
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ ├── page1
│ │ └── page2
│ └── services
│ ├── CommonService.js
│ ├── cache
│ │ ├── Cache1.js
│ │ └── Cache2.js
│ └── models
│ ├── Model1.js
│ └── Model2.js
├── partials
├── lib
└── test
- Auf einer oberen Ebene eine Aufteilung nach Funktionalität und auf einer tieferen Ebene eine Aufteilung nach Art der Komponenten.
Hier ist das entsprechende Layout:
.
├── app
│ ├── app.js
│ ├── common
│ │ ├── controllers
│ │ ├── directives
│ │ ├── filters
│ │ └── services
│ ├── page1
│ │ ├── controllers
│ │ │ ├── FirstCtrl.js
│ │ │ └── SecondCtrl.js
│ │ ├── directives
│ │ │ └── directive1.js
│ │ ├── filters
│ │ │ ├── filter1.js
│ │ │ └── filter2.js
│ │ └── services
│ │ ├── service1.js
│ │ └── service2.js
│ └── page2
│ ├── controllers
│ │ └── ThirdCtrl.js
│ ├── directives
│ │ ├── directive2.js
│ │ └── directive3.js
│ ├── filters
│ │ └── filter3.js
│ └── services
│ └── service3.js
├── partials
├── lib
└── test
- Wenn eine Direktive erstellt wird, kann es sinnvoll sein, alle der Direktive zugehörigen Dateien (d. h. Templates, CSS/SASS-Dateien, JavaScript) in das selbe Verzeichnis zu legen. Wenn du dich für diesen Stil entscheidest, setze ihn konsequent im gesamten Projekt um.
app
└── directives
├── directive1
│ ├── directive1.html
│ ├── directive1.js
│ └── directive1.sass
└── directive2
├── directive2.html
├── directive2.js
└── directive2.sass
Dieser Ansatz kann mit beiden der oben genannten Verzeichnisstrukturen kombiniert werden.
- Eine weitere kleine Variation der beiden Verzeichnisstrukturen wird in ng-boilerplate eingesetzt. In dieser liegen die Unit Tests zu einer Komponente direkt im Verzeichnis der jeweiligen Komponente. Werden Änderungen an einer Komponente vorgenommen, ist es auf diese Weise einfacher, ihre Tests zu finden. Gleichzeitig dienen die Tests als Dokumentation und zeigen Anwendungsfälle auf.
services
├── cache
│ ├── cache1.js
│ └── cache1.spec.js
└── models
├── model1.js
└── model1.spec.js
- Die Datei
app.js
enthält die Routendefinitionen, die Konfiguration und/oder das manuelle Bootstrapping (falls benötigt). - Jede JavaScript-Datei sollte nur eine einzige Komponente enthalten. Die Datei sollte nach dem Namen der Komponente benannt sein.
- Verwende Angular-Projektstrukturvorlagen wie Yeoman oder ng-boilerplate.
Ich bevorzuge die erste Struktur, weil bei ihr die üblichen Komponenten einfacher gefunden werden können.
Konventionen über die Benennung der Komponenten stehen in jedem Abschnitt über die jeweilige Komponente.
Auch die HTML-Markup ist wichtig und sollte in einem Team so geschrieben werden, als sei sie von derselben Person.
TLDR; Scripts sollten am Ende einer Seite eingefügt werden.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Meine App</title>
</head>
<body>
<div ng-app="myApp">
<div ng-view></div>
</div>
<script src="angular.js"></script>
<script src="app.js"></script>
</body>
</html>
Um den Code nicht unnötig zu verkomplizieren, füge AngularJS-spezifische Direktiven hinter Standard-Attributen ein. Dadurch ist es einfacher, sich den Code anzusehen und durch das Framework erweitertes HTML zu erkennen (was die Wartbarkeit verbessert).
<form class="frm" ng-submit="login.authenticate()">
<div>
<input class="ipt" type="text" placeholder="name" require ng-model="user.name">
</div>
</form>
Andere HTML-Attribute sollten den Empfehlungen des Code Guides folgen.
- Watche nur auf die vitalsten Variablen (Beispiel: Wenn du eine Echtzeitkommunikation einsetzt, sollte nicht bei jeder eingehenden Nachricht ein
$digest
-Loop ausgelöst werden). - Für Inhalte, die nur einmal initialisiert und anschließend nicht mehr geändert werden, sollten Einmal-Watcher wie
bindonce
verwendet werden. - Vereinfache Berechnungen in
$watch
so weit wie möglich. Komplexe und langsame Berechnungen in einem einzigen$watch
verlangsamen die gesamte Applikation (der$digest
-Loop wird in einem einzelnen Thread ausgeführt, weil JavaScript single-threaded ist). - Falls in der Callback-Funktion von
$timeout
keine gewatchten Variablen geändert werden, setze den dritten Parameter der$timeout
-Funktion auffalse
, um nicht automatisch einen$digest
-Zyklus durch den Aufruf des Callbacks auszulösen.
##Namensgebung Die Namensgebung für alle Elemente sind in folgender Tabelle wieder zu finden:
Element | Style | Beispiel | Verwendung bei |
---|---|---|---|
Module | lowerCamelCase | angularApp | |
Controller | funktionalität + 'Ctrl' | adminCtrl | |
Direktiven | lowerCamelCase | userInfo | |
Filter | lowerCamelCase | userFiler | |
Services | UpperCamelCase | User | Konstruktor |
Services | lowerCamelCase | dataFactory | sonstige |
- Verwende:
$timeout
stattsetTimeout
$interval
stattsetInterval
$window
stattwindow
$document
stattdocument
$http
statt$.ajax
Dadurch werden deine Tests einfacher und in manchen Fällen wird einem unerwarteten Verhalten vorgebeugt (zum Beispiel wenn du ein $scope.$apply()
in setTimeout
vergessen hast).
-
Automatisiere deinen Workflow mit Tools wie:
-
Verwende Promises (
$q
) statt Callbacks. Dadurch sieht dein Code eleganter und sauberer aus und du wirst nicht in der Callback-Hölle landen. -
Verwende, wenn möglich,
$resource
statt$http
. Das höhere Abstraktionslevel schützt dich vor Redundanz. -
Verwende einen Angular Pre-Minifier (wie ngmin oder ng-annotate), um Probleme nach einer Minification zu vermeiden.
-
Verwende keine Globalen. Löse alle Abhängigkeiten durch Dependency Injection auf.
-
Mülle deinen
$scope
nicht zu. Füge ihm nur Funktionen und Variablen hinzu, die in den Templates verwendet werden. -
Bevorzuge Controller gegenüber
ngInit
.ngInit
eignet sich nur, um Aliase für spezielle Eigenschaften vonngRepeat
zu erstellen. Hiervon abgesehen solltest du immer Controller stattngInit
verwenden, um Werte in einem Scope zu initialisieren. -
Verwende kein
$
als Präfix für die Namen von Variablen, Eigenschaften oder Methoden. Dieser Präfix ist für AngularJS reserviert.
- Module sollten in lowerCamelCase benannt werden. Um deutlich zu machen, dass das Modul
b
ein Untermodul vona
ist, kannst du sie durch Namespaces verschachteln, z. B.:a.b
.
Es gibt zwei verbreitete Wege, nach denen Module strukturiert werden können:
- Nach Funktionalität
- Nach Typ der Komponente
Derzeit gibt es keinen großen Unterschied, aber die erste Variante sieht sauberer aus. Außerdem wird - wenn lazy-loading für die Module implementiert ist (momentan nicht auf der AngularJS-Roadmap) - die Performance der App verbessert.
- Du solltest das DOM nicht aus deinen Controllern heraus manipulieren, dadurch wird das Testen der Controller erschwert und du verstößt gegen das Prinzip der Separation of Concerns. Verwende stattdessen Direktiven.
- Controller sollen nach ihrer Funktion (zum Beispiel shopping cart, homepage, admin panel) und dem Suffix
Ctrl
benannt werden. Controller werden in UpperCamelCase benannt (HomePageCtrl
,ShoppingCartCtrl
,AdminPanelCtrl
usw.). - Controller sollten nicht als Globale definiert werden (AngularJS erlaubt das zwar, es ist jedoch schlechte Praxis den globalen Namensraum zu verschmutzen).
- Verwende für Controller-Definitionen die Array-Syntax:
module.controller('MyCtrl', ['dependency1', 'dependency2', ..., 'dependencyn', function(dependency1, dependency2, ..., dependencyn) {
// body
}]);
Durch die Verwendung dieses Definitionstyps werden Probleme bei der Minification vermieden. Die Array-Definition kann aus der Standardnotation automatisch generiert werden, indem Werkzeuge wie ng-annotate (und der Grunt-Task grunt-ng-annotate) verwendet werden.
- Verwende die Originalnamen der im Controller verwendeten Abhängigkeiten. Dies hilft dir dabei, lesbareren Code zu schreiben:
module.controller('MyCtrl', ['$scope', function(s) {
// body
}]);
ist schlechter lesbar als:
module.controller('MyCtrl', ['$scope', function($scope) {
// body
}]);
Das gilt insbesondere für Dateien, die so viel Code enthalten, dass gescrollt werden muss. Dadurch vergisst du möglicherweise, welche Variable zu welcher Abhängigkeit gehört.
- Halte Controller so schlank wie möglich und lagere mehrfach verwendete Funktionen in Services aus.
- Kommuniziere zwischen verschiedenen Controllern, indem du Method Invocation nutzt (das ist möglich, wenn ein Kindcontroller mit seinem Elterncontroller kommunizieren möchte) oder die
$emit
-,$broadcast
- und$on
-Methoden verwendest. Über$emit
und$broadcast
gesendete Nachrichten sollten auf ein Minimum reduziert werden. - Erstelle eine Liste aller Nachrichten, die über
$emit
und$broadcast
verschickt werden. Pflege diese Liste, um Kollisionen und Bugs zu vermeiden. - Wenn du Daten formatieren musst, kapsle die Formatierungslogik in einem Filter und gebe diesen als Abhängigkeit an:
module.filter('myFormat', function() {
return function() {
// body
};
});
module.controller('MyCtrl', ['$scope', 'myFormatFilter', function($scope, myFormatFilter) {
// body
}]);
- Benenne deine Direktiven in lowerCamelCase.
- Verwende
scope
statt$scope
in deiner Link-Funktion. In den Compile- und Post-/Pre-Link-Funktionen hast du bereits Argumente angegeben, die verwendet werden sobald die Funktion aufgerufen wird. Diese kannst du nicht über eine Dependency Injection ändern. Dieser Stil wird auch im AngularJS-Sourcecode verwendet. - Verwende eigene Präfixe für deine Direktiven, um Namenskollisionen mit Bibliotheken von Drittanbietern zu vermeiden.
- Die Präfixe
ng
undui
solltest du nicht verwenden, da diese für AngularJS und AngularUI reserviert sind. - DOM-Manipulationen dürfen ausschließlich über Direktiven vorgenommen werden.
- Verwende einen Isolated Scope, wenn du wiederverwendbare Komponenten entwickelst.
- Binde Direktiven über Attribute oder Elemente ein statt über Kommentare oder Klassen. Das macht deinen Code lesbarer.
- Verwende zum Aufräumen
$scope.$on('$destroy', fn)
. Dies ist besonders nützlich wenn du Wrapper-Direktiven für Drittanbieter-Plug-ins entwickelst. - Vergiss nicht,
$sce
zu verwenden, wenn du mit Inhalten arbeitest, die nicht vertrauenswürdig sind.
- Benenne deine Filter in lowerCamelCase.
- Halte deine Filter so schlank wie möglich. Durch die
$digest
-Schleife werden sie häufig aufgerufen, so dass langsame Filter die gesamte Anwendung verlangsamen. - Mache nur eine einzige Sache in deinen Filtern, halte sie kohärent. Komplexere Manipulationen können erzielt werden, indem mehrere Filter gepiped werden.
Dieser Abschnitt enthält Informationen über AngularJS' Service-Komponente. Er bezieht sich nicht auf eine spezielle Definitionsweise (d. h. als Provider, Factory oder Service), falls nicht ausdrücklich genannt.
- Benenne deine Services in camelCase.
- UpperCamelCase (PascalCase), um Services zu benennen, die als Konstruktoren verwendet werden, d. h.:
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 für alle anderen Services.
- Kapsle die gesamte Anwendungslogik in Services.
- Services, die eine bestimmte Domäne abbilden, sollten bevorzugt als
service
statt alsfactory
geschrieben werden. Auf diese Weise können die Vorteile der klassischen Vererbung einfacher genutzt werden:
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);
- Für einen sitzungsbezogenen Cache kannst du
$cacheFactory
verwenden. Diesen solltest du nutzen, um die Ergebnisse von Anfragen oder aufwändigen Berechnungen zwischenzuspeichern. - Falls ein Service konfiguriert werden muss, definiere ihn als Provider und konfiguriere ihn im
config
-Callback, wie hier:
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);
});
- Verwende
ng-bind
oderng-cloak
statt einfachen{{ }}
, um flackernde Inhalte zu vermeiden. - Vermeide es, komplexe Ausdrücke in ein Template zu schreiben.
- Wenn das
src
-Attribut eines Bilds dynamisch gesetzt werden soll, verwendeng-src
stattsrc
mit einem{{ }}
-Template. - Wenn du das
href
-Attribut eines Ankers dynamisch setzen musst, verwendeng-href
statthref
mit einem{{ }}
-Template. - Statt in Scopevariablen Strings anzugeben und diese mit
{{ }}
in einstyle
-Attribut zu schreiben, benutze dieng-style
-Direktive, der als Parameter objektartige Strings und Scopevariablen übergeben werden können:
$scope.divStyle = {
width: 200,
position: 'relative'
};
<div ng-style="divStyle">Mein wunderschön gestyltes div, das auch im IE funktioniert</div>;
- Verwende
resolve
, um Abhängigkeiten aufzulösen bevor die View angezeigt wird.
#Testen
TBD
Du kannst diese Anleitung verwenden, solange dieser Abschnitt noch nicht fertig ist.
Da dieser Style Guide gemeinschaftlich durch die Community erstellt werden soll, sind Beiträge willkommen. Zum Beispiel kannst du etwas beitragen, indem du den Abschnitt über Tests erweiterst oder den Style Guide in deine Sprache übersetzt.
#Mitwirkende
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 |