Eine Sammlung von Best Practices für Applikationen mit AngularJS. Der Vortrag stellt Strukturen und Konventionen vor, mit denen sich auch umfangreiche Applikationen wartbar und erweiterbar halten lassen.
14. Styleguide
Der Quellcode soll überall gleich aussehen.
Dem Quellcode soll man nicht ansehen, wann er von wem mit
welcher Laune geschrieben wurde.
Ein guter Start ist: https://github.com/johnpapa/angular-
styleguide
17. Entwicklungsumgebung
Autocompletion: Variablen und Konstrukte vervollständigen.
Hilfestellung bieten.
Highlighting: Visuelle Abgrenzung zwischen verschiedenen
Konstrukten für erhöhte Lesbarkeit.
Linting: Anti-Pattern-Erkennung, um Fehler zu vermeiden.
Angular Plugins für WebStorm, Eclipse,…
24. Yeoman
npm install -g yo
npm install -g generator-angular
yo angular myApp
yo angular:controller account
25. Yeoman
Problem:
Aber unsere Applikation ist etwas ganz besonderes und die
bestehenden Generatoren passen für uns nicht. Wir müssen
jede Struktur nochmal anfassen und umbauen.
Lösung:
Eigene Generatoren erstellen.
Guide: http://yeoman.io/authoring/
27. Struktur
Wie baut man eine Applikation auf, damit sie wartbar und
erweiterbar bleibt?
Wo findet man bestimmte Komponenten?
Wie werden die Verzeichnisse strukturiert?
Wie heißen die Dateien?
31. Ein Verzeichnis für shared
content
Gemeinsam genutzte Komponenten liegen in einer eigenen
Hierarchie. Abhängigkeiten zwischen Modulen werden
vermieden.
33. Eine Komponente pro Datei
Controller, Services, Direktiven, … erhalten jeweils ihre eigene
Datei.
Single Responsibility - eine Datei ist nur für einen Zweck da.
Zuordnung von Zuständigkeiten ist einfach. Das Auffinden
von Quellcode ist schneller.
34. Eine Komponente pro Datei
angular.module('user.controller.login', [])
.controller('user.loginController', LoginController);
function LoginController() {
}
36. Einheitliche sprechende
Benennung
Die Namen von Dateien und deren Speicherort folgt einem
konsistenten Schema.
Das Lokalisieren von Komponenten wird einfacher.
/user/login.controller.js
38. Konfiguration auslagern
Die Konfiguration eines Moduls liegt in einer eigenen Datei
innerhalb des Moduls.
Die Konfiguration erfolgt an einer zentralen Stelle und nicht
über die Komponenten verteilt.
43. Routing
Navigation zwischen verschiedenen States in der
Applikation.
States arbeiten mit Views, die benannt sein können. Optional
können Controller hinzugefügt werden.
Implementierungen:
- ngRoute
- angular-ui-router
50. DRY
Sobald eine Routine zum zweiten Mal implementiert wird,
sollte sie in einen eigenständigen Service ausgelagert
werden.
Diese Services können abstrahiert und durch
unterschiedliche Konfiguration an mehreren Stellen
wiederverwendet werden.
53. NIH
Bevor man etwas selbst implementiert, sollte man zuerst
prüfen, ob es für diese Problemstellung nicht schon eine
Lösung gibt.
Beispiele:
- REST-Kommunikation: ngResource, restangular
- Websocket: angular-websocket, ng-websocket
- localstorage: angular-local-storage
- Routing: ngRoute, ui-router
- Translation: ngTranslate
57. Dokumentation
Angular selbst verwendet jsdoc bzw. ngdoc und Dgeni zum
Parsen.
Weitere Infos:
https://github.com/angular/angular.js/wiki/Writing-AngularJS-
Documentation
58. Dokumentation
/**
* @name trim
* @private
*
* @description
* trim polyfill
*
* @returns {string} The string stripped of whitespace from both ends
*/
var trim = function() {
return this.toString().replace(/^s+|s+$/g, '');
};
59.
60. Typescript
Zur Dokumentation von Schnittstellen kann zusätzlich
Typescript eingesetzt werden.
Möglichkeit zur Beschreibung von Signaturen, Klassen und
Interfaces.
64. Struktur vs. Implementierung
function ListCtrl() {
var vm = this;
vm.onEdit = onEdit;
vm.onDelete = onDelete;
function onEdit(id) {…}
function onDelete(id) {…}
}
function ItemService() {
return {
fetch: fetch,
save: save
};
function fetch() {...}
function save() {...}
}
Controller:
Factory:
66. init-Funktion
Sämtliche Logik, die in einem Controller zur Initialisierung
direkt ausgeführt wird, wird in einer Funktion gekapselt.
So sieht man auf einen Blick, was passiert, wenn dieser
Controller erstellt wird.
69. Logik in Services
Controller sollten nicht zu viel Logik beinhalten, da sie
dadurch unleserlich werden. Umfangreichere Logik in
Services auslagern.
71. Ein Controller pro View
Mehrere Views sollten sich nicht einen Controller teilen. Dies
macht es meistens erforderlich, Logikweichen einzubauen,
die die Lesbarkeit beeinträchtigen.
73. Promises
Promises sind ein Mittel, um asynchronen Programmfluss zu
steuern. Sie erhöhen die Lesbarkeit des Quellcodes und
helfen bei der Fehlerbehandlung.
76. Tests
Mindestens zwei Ebenen von Tests: Unittests und End2End-
Tests.
Manuelle Tests können allerdings nicht entfallen, da immer
noch irgendwo Logikfehler versteckt sein können.
78. Unittests
Karma: bietet die Infrastruktur. Steuerung der
angeschlossenen Browser. Verteilung der Tests und
Einsammeln der Daten.
Jasmine: Formulieren der Tests.
Die Tests liegen bei den Dateien, die sie testen sollen. Tests
werden schneller gefunden und besser gepflegt, sind
gegenwärtiger.
79. describe('simple controller', function() {
var $rootScope, $scope, createController;
beforeEach(module('simpleCtrl'));
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('PasswordCtrl', {
'$scope': $scope
});
};
}));
it('should have a name property with a value', function() {
var controller = createController();
expect($scope.name).toBe('Hans-Peter');
});
it('should execute a function', function() {
var controller = createController();
expect($scope.myFunc()).toBe('hello');
})
});
81. End2End
Werden mit Protractor erstellt. Bauen auf Selenium
WebDriver auf. Testen die komplette Applikation inklusive
Server.
Längere Laufzeit als Unittests. Testen Integration einzelner
Komponenten in die Applikation.
Werden in einer parallelen Verzeichnishierarchie gepflegt.
82. End2End
describe('project list', function() {
it('should check the first item in the list', function(done) {
browser.get('/');
var name = element(by.repeater('project in projects')
.row(0)
.column('name'));
expect(name.getText()).toEqual('Projekt A');
done();
});
});
86. Module Loader
Keine Script-Tags mehr schreiben. Abhängigkeiten zwischen
Dateien werden aufgelöst. Können im Build zu einer Datei
zusammengefasst werden.
Beispiele:
- require.js
- Browserify
- SystemJS
- …
89. Statische Codeanalyse
Statische Prüfung des Quellcodes. Auffinden von
Antipatterns oder Betrachtung bestimmter Metriken wie z.B.
Komplexität.
Tools:
- linter (jslint, jshint, eslint)
- Plato
97. Services
Services sind Singletons und können an verschiedenen
Stellen verwendet werden.
Mit Services lassen sich Nachrichtenbusse umsetzen, die zur
Kommunikation zwischen den Komponenten verwendet
werden können.
98. angular.module('myApp', [])
.factory('MsgBus', MsgBus);
function MsgBus() {
var callbacks = {};
return {
on: on,
trigger: trigger
};
function on(event, callback) {
if (callbacks[event]) {
callbacks[event] = [callback];
return this;
}
callbacks[event].push(callback);
}
function trigger(event, data) {
if (callbacks[event]) {
callbacks[event].forEach(function (callback) {
callback(data);
});
}
}
}
99. Direktiven
Direktiven sind Marker im HTML, die den Funktionsumfang
von HTML erweitern.
Mithilfe der Scope-Eigenschaft der Direktive kann zwischen
dem außenliegenden Controller und der Direktive
kommuniziert werden.
100. Direktiven
<my-directive name="{{ name }}" color="color" reverse="reverseName()">
angular.module('app', [])
.directive('myDirective', myDirective);
function myDirective() {
return {
scope: {
name: '@', // one way (read only)
color: '=', // two way
reverse: '&' // function binding
}
}
}
102. Kommunikation zum Server
Services, die die Kommunikation kapseln.
$http, restangular, ngResource, angular-websocket
Abstrahieren die Schnittstelle zum Server. Können zum
Testen durch Stubs ersetzt werden.
105. Performance
Angular ist ein Framework, das bedeutet Overhead.
Two Way Data-Binding mit Dirty Checking ist eines der
größten Performance-Probleme.
Mögliche Lösung: angular-vs-repeat, ngGrid