zurück zum Artikel

Tipps und Tricks für AngularJS, Teil 2: ES2015

Vildan Softic

Moderne JavaScript-Konstrukte dienen nicht nur der Übersichtlichkeit von Projekten, sondern können sie auch zukunftssicher machen. Damit sie aber von allen Browsern verstanden werden, sind einige Kniffe nötig.

Tipps und Tricks für AngularJS, Teil 2: ES2015

Moderne JavaScript-Konstrukte dienen nicht nur der Übersichtlichkeit von Projekten, sondern können sie auch zukunftssicher machen. Damit sie aber von allen Browsern verstanden werden, sind einige Kniffe nötig.

Die Möglichkeiten, die das mittlerweile ECMAScript 2015 betitelte ES6 zur Verfügung stellt, liefern gerade im Zusammenspiel mit AngularJS Mittel, um den Anwendungscode sauber zu organisieren und leserlicher zu gestalten. Unter den entsprechenden Neuerungen sind unter anderem Lambda-Ausdrücke, JavaScript-Klassendefinitionen und allen voran die Option, externe JavaScript-Dateien per Export zu referenzieren. Eine Übersicht der in ES2015 ergänzten Features lässt sich in einem separaten Artikel [1] nachlesen.

Da bisher jedoch noch recht wenige Browserversionen (meist experimentelle Builds) ECMAScript in der Version 2015 nativ unterstützen, stellt sich die Frage, wie man ihre Features derzeit verwenden kann. Dieser Artikel zeigt dazu ein mögliches Vorgehen, in dem der populäre Transpiler 6to5 [2] und Gulp [3] für die Aufgabenautomatisierung zum Einsatz kommen. Das fertige Beispiel lässt sich über das Github Repo Angular-ES6 [4] beziehen.

Um ES2015-Code schon heute nutzen zu können, ohne eine Reihe von Browsern auszuschließen, ist die einfachste Herangehensweise, ihn nach ES5 zu transpilieren. Ein Transpiler wandelt im Gegensatz zu einem klassischen Compiler eine Hochsprache in eine andere um, sodass sich beispielsweise Versionsunterschiede überwinden lassen (z.B. Python 2 zu Python 3). Compiler hingegen kümmern sich beispielsweise um die Umwandlung von höheren Sprachen in maschinennähere (z.B. C zu Maschinensprache).

In der Welt von JavaScript stehen dafür unter anderem Traceur [5] von Google, ESNext [6]und 6to5 [7] zur
Verfügung. Während Traceur noch ein Laufzeitskript für das Umwandeln und Interpretieren von ES2015-Code benötigt, analysiert 6to5 den Code vorab statisch und kompiliert ihn im Anschluss, um ein lesbares Ergebnis zu generieren. Ein Vergleich der Projekte [8] ist auf der Seite des Anbieters zu
finden.

Mittlerweile haben sich die Teams hinter dem Transpiler ESnext und 6to5 zusammengeschlossen, um gemeinsam weitere Features und kürzere Release-Zyklen anzubieten.

Für das gezeigte Beispiel liegen die ES2015-Dateien jeweils im Ordner src und deren Transpilat in dist.

Da eventuell auch externe Module und Bibliotheken in ES2015 beschrieben sind und bereits auf die zuvor erwähnte Import-Syntax zurückgreifen, ist es notwendig, auch sie korrekt beziehen und in das Projekt einbinden zu können. In letzter Zeit hat sich hierfür der JavaScript Package Manager JSPM [9] als eine hilfreiche Alternative zu Bower, Jam, Brocolli und den zahlreichen anderen hervorgetan. Im Vergleich zu den vorhergenannten sieht JSPM seine Aufgabe nicht nur im Beschaffen der Module, sondern kümmert sich auch um ihre Einbindung innerhalb eines Projekts. Dafür wird der universelle Modul-Loader SystemJS [10]verwendet, der neben ES2015-Modulen auch AMD, CommonJS und globale Skripte versteht. Darüber hinaus bietet JSPM eine Bundling-Option an, die ähnlich wie bei RequireJS mit dem r.js-Adapter [11], eine gebündelte Auslieferung von Dateien ermöglicht.

Die benötigten Quellen und Grundkonfiguration werden in einer Datei config.js definiert:

System.config({
"baseUrl": "dist",
"paths": {
"*": "*.js",
"github:*": "jspm_packages/github/*.js",
"npm:*": "jspm_packages/npm/*.js",
}
});

System.config({
"map": {
"angular": "github:angular/bower-angular@1.3.8",
"angular-mocks": "github:angular/bower-angular-mocks@1.3.8",
"bootstrap": "github:twbs/bootstrap@3.3.1",
"github:twbs/bootstrap@3.3.1": {
"css": "github:systemjs/plugin-css@0.1.0",
"jquery": "github:components/jquery@2.1.3"
}
}
});

Mit baseUrl gibt der Entwickler den Zielordner an, der die darzustellenden Projektdateien enthält. In der paths-Angabe lassen sich die Pfade einzelner wichtiger Ordner hinterlegen, die beispielsweise heruntergeladenen GitHub- beziehungsweise NPM-Pakete enthalten. Die Eigenschaft map definiert abschließend Aliase für die verwendeten Bibliotheken.

Um ein neues Paket hinzuzügen, reicht es, den folgenden Befehl auszuführen. JSPM kümmert sich danach automatisch um seinen Eintrag in config.js:

jspm install PAKETNAME

Wie schon in der Konfiguration zu erkennen, lassen sich dafür nicht nur Bower- und GitHub-Quellen sondern auch das NodeJS Repository verwenden.

Der Funktionsumfang von JSPM ist groß und wächst durch die aktive Community täglich. Für einen genauen Einblick ist der Wiki-Eintrag "Getting Started [12]" zu empfehlen.

Gulp ist ein Werkzeug , das ähnlich wie Grunt [13] wiederkehrende Aufgaben automatisiert. Sie lassen sich dann mit folgendem Befehl ausführen:

gulp watch

Die Konfiguration geschieht über die Datei Gulpfile.js, die im Vergleich zu Grunt jedoch gewöhnlichen JavaScript-Code enthält. Die für das Beispiel definierten Tasks [14] umfassen:

Für eine schnelle Einführung in Gulp ist der Artikel "An Introduction to Gulp.js [15]" zu empfehlen.

Nach der Vorstellung der im Beispiel verwendeten Werkzeuge ist es Zeit, den Einsatz von ES2015 mit AngularJS zu demonstrieren. Das Beispiel ist eine Flickr-Bildsuche, mit der man unter Angabe eines Stichworts Bilder abrufen kann. Nach einem Klick auf die Schaltfläche "Load Images" gibt ein darunterliegendes ngRepeat die mit JSONP extrahierten Bilder aus. Zusätzlich wird neben der Beschriftung "Number of hits" am rechten Bildschirmrand die Anzahl der geladenen Bilder angezeigt.

<html>
<head>
<link rel="stylesheet" type="text/css" href="jspm_packages/github
/twbs/bootstrap@3.3.1/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
</head>
<body>
<div class="container" ng-controller="MainCtrl as ctrl">
<h1>Flickr Photo Album</h1>
<div class="form-inline form-group">
<label for="txtSearchTag">Search tag:</label>
<input type="text" id="txtSearchTag" ng-model="ctrl.searchTag"
class="form-control" required="required">
<button class="form-control"
ng-click="ctrl.service.loadImages(ctrl.searchTag)">Load
images</button>
<span class="pull-right">Number of hits:
{{ctrl.service.numberOfHits()}}</span>
</div>
<flickr-gallery data="ctrl.service.images" />
</div>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
System.import('./dist/app').catch(console.error.bind(console));
</script>
</body>
</html>

Dieser Quelltext der Datei index.html enthält das Grundgerüst der Applikation und bettet die benötigten externen Quellen ein. Der Kopfbereich head referenziert die benötigten CSS-Stylesheets. Im body folgen dann die Definition eines üblichen AngularJS-View-Gerüsts, das den verwendeten MainCtrl mit der Controller-As-Syntax aus AngularJS 1.3 einbindet. Darin enthalten sind die Steuerelemente und die ngRepeat-Direktive, die eine Direktive flickr-gallery wiederholt. Ansonsten sind keine weiteren JS-Dateien eingebunden, sondern lediglich der Verweis auf system.js und der erste Aufruf von System.import, der auf die Startdatei app.js aus dem Ordner dist verweist.

Diese Einstiegsdatei des Projektes sieht wie folgt aus:

import angular from 'angular';
import {FlickrService} from './services/flickrService';
import {MainCtrl} from './controllers/mainctrl';
import {FlickrGallery} from './directives/flickrGallery';
import {TitleHeaderStyler} from './filters/titleHeaderStyler';

var app = angular.module('AngularES6', []);
app.constant('baseURL', 'dist/');

app.service('FlickrService', FlickrService);
app.controller('MainCtrl', MainCtrl);
app.directive('flickrGallery', FlickrGallery);
app.filter('titleHeaderStyler', TitleHeaderStyler);

angular.element(document).ready(function() {
angular.bootstrap(document, ['AngularES6']);
});

export {app};

Mit den ES2015-Import-Anweisungen lassen sich externe Quellen entweder mit definiertem Alias, zum Beispiel dem Angular-Modul, oder einzelne Dateien mit einem relativen Verweis laden. Enthält der gewünschte Inhalt nur einen Default-Export, können die geschweiften Klammern wie beim Angular-Modul entfallen. Nachdem nun alle Beispielkonstrukte importiert sind, muss man sie wie üblich im Angular-Modul AngularES6 registrieren. Zum Abschluss findet ein manuelles Bootstrapping der Applikation statt. Alternativ lässt sich das automatische Bootstrapping nutzen, wofür das Attributs ng-app mit dem Modulnamen im HTML-Markup zu setzen ist.

Eine Besonderheit ist hierbei das Verwenden der Service-Methode von Angular. Der Grund dafür ist, dass sie eine Konstruktorfunktion entgegennimmt, die einer Klasse in ES2015 entspricht. Der FlickrService exportiert sie und verwendet eine constructor-Funktion, an die die Abhängigkeiten zu übergeben sind, in diesem Fall der $http-Service. Hervorzuheben ist auch das in Backticks dargestellte Quasi-Literal zur Definition der URL.

Damit lassen sich endlich mehrzeilige Strings in JavaScript definieren. Zudem kann man einen einfachen String auch – ähnlich wie in PHP – durch den Template-String ${WERT} mit Variablen kombinieren:

export class FlickrService {
constructor($http) {
this.$http = $http;
this.images = [];
}

numberOfHits() {
return this.images.length;
}

loadImages(searchTag) {
var URL = `http://api.flickr.com/services/feeds
/photos_public.gne?tags=${searchTag}&tagmode=any&format=json&
jsoncallback=JSON_CALLBACK`;

return this.$http
.jsonp(URL)
.then(response => {
this.images = response.data.items;
});
}
}

Ähnlich wie Services lassen sich auch Controller mit Klassen definieren:

export class MainCtrl {
constructor(FlickrService) {
this.images = [];
this.searchTag = "AngularJS";
this.service = FlickrService;
}
}

Für die Definition einer Direktive ist jedoch auf eine Funktion zurückzugreifen. Statt einer Klasse wird nun mit der ES2015-Arrow-Function eine anonyme Funktion zurückgegeben. Sie wird zuvor in der lokalen Variable FlickrGallery abgelegt, die in der weiter oben gezeigten Import-Anweisung referenziert ist:

export let FlickrGallery = (baseURL) => {
return {
restrict: 'E',
scope: {
data: '=data'
},
templateUrl: baseURL + 'templates/flickrGallery.html'
};
};

Das gleiche Vorgehen ist auch im Falle eines eigenen Filters zu wählen. Erneut weist der Entwickler die anonyme Funktion einer lokalen Variable zu, die sodann in der app.js Import-Anweisung Verwendung findet:

export let TitleHeaderStyler = () => {
return (input) => {
return input.toUpperCase();
}
}
Mehr Infos

Allein die Möglichkeit, endlich bestehenden Code auf mehrere Dateien aufzuteilen und sich nicht manuell um die Referenzierung der JavaScript-Dateien kümmern zu müssen, zeigt, wie hilfreich der Einsatz von ECMAScript 2015 sein kann. Dank Projekten wie 6to5 und Traceur lässt sich der neue Standard bereits nutzen, ohne Browser außen vor zu lassen, die noch nicht damit umgehen können.

Vildan Softic
ist als selbstständiger Berater und Softwareentwickler tätig. Der Fokus liegt dabei auf Single Page Applications mit diversen Frameworks sowie Desktop-Entwicklung mit .NET. In seinem aktuellen Buch "AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript" behandelt er die vielen Seiten von Googles populärem SPA-Framework.
(jul [25])


URL dieses Artikels:
https://www.heise.de/-2555723

Links in diesem Artikel:
[1] https://www.heise.de/hintergrund/Neue-Sprachfeatures-fuer-JavaScript-im-ECMAScript-6-Entwurf-Teil-1-2398267.html
[2] https://6to5.org/
[3] http://gulpjs.com/
[4] https://github.com/zewa666/angular_es6
[5] https://github.com/google/traceur-compiler
[6] https://esnext.github.io/esnext/
[7] https://6to5.org/
[8] https://6to5.org/docs/compare/
[9] http://jspm.io/
[10] https://github.com/systemjs/systemjs
[11] http://requirejs.org/docs/optimization.html
[12] https://github.com/jspm/jspm-cli/wiki/Getting-Started
[13] https://www.heise.de/blog/Grunt-lagen-Teil-1-1816780.html
[14] https://github.com/zewa666/angular_es6/blob/master/gulpfile.js
[15] http://www.sitepoint.com/introduction-gulp-js/
[16] https://www.heise.de/hintergrund/Tipps-und-Tricks-fuer-AngularJS-Teil-1-Internationalisierung-2516976.html
[17] https://www.heise.de/hintergrund/Tipps-und-Tricks-fuer-AngularJS-Teil-2-ES2015-2555723.html
[18] https://www.heise.de/hintergrund/Tipps-und-Tricks-fuer-AngularJS-Teil-3-OAuth-2-0-2620374.html
[19] https://www.heise.de/ratgeber/Tipps-und-Tricks-fuer-AngularJS-Teil-4-Animationen-in-AngularJS-1-4-2774580.html
[20] https://www.heise.de/ratgeber/Tipps-und-Tricks-fuer-AngularJS-Teil-5-Transformationen-und-Interceptors-2821088.html
[21] https://www.heise.de/hintergrund/Backend-lose-Entwicklung-mit-AngularJS-und-ngMockE2E-3086974.html
[22] https://www.heise.de/ratgeber/Tipps-und-Tricks-mit-AngularJS-Teil-7-GUIs-mit-Angular-2-und-Redux-Implementierung-ngrx-store-I-3192046.html
[23] https://www.heise.de/ratgeber/test-3194264.html
[24] https://www.heise.de/ratgeber/AngularJS-1-x-und-2-0-mit-dem-Component-Router-parallel-betreiben-2679282.html
[25] mailto:jul@heise.de