AngularJS 1.x und 2.0 mit dem Component Router parallel betreiben

Know-how  –  0 Kommentare

Der neue Component Router soll einen Parallelbetrieb von AngularJS 1.x und 2.0 ermöglichen und bietet somit Unterstützung bei einer schrittweisen Migration. Der Einsatz von ECMAScript 2015 in AngularJS-1.x-Anwendungen hilft hierbei ebenfalls.

Dass Angular 2.0 einen Neustart darstellt, ist seit Anfang 2014 bekannt. Entwickler, deren Code mit Version 1.x arbeitet, stellen sich somit die Frage nach einem möglichen Migrationspfad. Eine Antwort darauf liefert der neue Router, den das Produktteam sowohl für AngularJS 1.x als auch für Angular 2.0 zur Verfügung stellen will. Dieser sogenannte Component Router kann beide Versionsstränge von AngularJS parallel betreiben, was eine schrittweise Migration ermöglicht.

Der Einsatz von ECMAScript 6 (ES6, offiziell nun ECMAScript 2015) oder TypeScript hilft zusätzlich, AngularJS-1.x-Code an den für Version 2.0 anzugleichen (siehe "Tipps und Tricks für AngularJS, Teil 2: ES2015" und "AngularJS mit EcmaScript 6 & Starter-Kit "). Dieser Beitrag betrachtet den Component Router vor dem Hintergrund einer in ECMAScript 6 verfassten AngularJS-1.x-Anwendung und soll vermitteln, dass sich der Weg von 1.x auf 2.0 zwar nicht einfach gestaltet, aber auch nicht so steinig ist, wie vielerorts vermutet. Den Quellcode der Beispielanwendung stellt der Autor auf GitHub zur Verfügung.

Komponenten und Konventionen

Wie bei Angular 2.0 steht auch beim Component Router das Konzept der Komponente im Mittelpunkt. Beim Aktivieren einer Route bringt der Router die mit ihr assoziierten Komponenten zur Ausführung. Während Komponenten in Angular 2.0 sogenannte Web Components zur Grundlage nehmen, wird der Component Router bei AngularJS-1.x-Projekten stattdessen eine Kombination aus einer View und einem dazu passenden Controller nutzen.

Zum Laden dieser Konstrukte greift er auf Konventionen zurück. Hinterlegen Entwickler beispielsweise als Ziel einer Route die Komponente Home, begibt sich der Router auf die Suche nach einem HomeController und einer View components/home/home.html. Zusätzlich würde er in dem Fall eine Instanz des Controllers über die Scope-Variable home bereitstellen. Auf die Weise erzwingt der Router eine einheitliche Anwendungsstruktur und verringert dabei die Länge des notwendigen Konfigurationscodes. Wer sich mit diesen Konventionen nicht anfreunden möchte, kann eigene bereitstellen.

Routen konfigurieren

Während sich die Quellcodedateien des Component Router im GitHub-Repository [GitHub-Router] von Angular befinden, kann der Entwickler die letzte freigegebene Version wie folgt via npm beziehen:

npm install angular-new-router

Als der vorliegende Text verfasst wurde, war das die Version 0.5.x. Da es sich dabei nicht um die finale Version handelt, können sich bis zu deren Veröffentlichung noch Änderungen ergeben.

Der neue Component Router sieht vor, dass ein anwendungsweit genutzter Controller die Routen konfiguriert. Der nachfolgende Quellcode demonstriert das anhand der Datei app.js:

// app.js
import angular from 'angular';
import newRouter from 'node_modules/angular-new-router/dist/router.es5';
import { HomeController } from 'home/home';
import { FlugBuchenController } from 'flug-buchen/flug-buchen';
[...]

var app = angular.module('app', ['ngNewRouter']);

class AppController {

constructor($router) {
$router.config([
{ path: '/', component: 'home' },
{ path: '/flugbuchen', component: 'flugBuchen' }
]);
}
}

app.controller('AppController', AppController);
app.controller('HomeController', HomeController);
app.controller('FlugBuchenController', FlugBuchenController);
[...]

app.constant("baseUrl", "http://www.angular.at");

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

Das in ES6 geschriebene Beispiel importiert zunächst AngularJS, den Component Router sowie die von einzelnen Routen verwendeten Controller. Anschließend definiert es ein neues Angular-Modul für die Anwendung. Es erhält als Abhängigkeit das Modul ngNewRouter, das den Component Router beherbergt. Der AppController bekommt eine Instanz des Component Router injiziert und bildet mit der Methode config einzelne Pfade auf Komponenten ab.

Wie oben beschrieben, erwartet der Router unter Nutzung der standardmäßig vorliegenden Konventionen für die Komponente home einen HomeController sowie für die Komponente flugBuchen einen FlugBuchenController. Damit diese bereit stehen, registriert die gezeigte Anwendung die entsprechenden Klassen mit der Methode controller beim angular-Modul. Die dazugehörigen Views sind aufgrund dieser Konventionen über die Dateien components/home/home.html und components/flug-buchen/flug-buchen.html bereitzustellen.

Erstere erhält über die Scope-Variable home Zugriff auf eine Instanz ihres Controllers. Bei letzterer nutzt der Router laut den vorherrschenden Konventionen die Scope-Variable flugBuchen.

ECMAScript-7-Dekoratoren

Um den Quellcode noch stärker an Angular 2.0 anzugleichen, besteht die Möglichkeit, die für ECMAScript 7 (ECMAScript 2016) geplanten Dekoratoren zu verwenden, die Angular 2.0 zum Hinterlegen von Metadaten im Quellcode nutzt. Auf die Weise könnte das Registrieren von Controllern analog zu Angular 2.0 deklarativ durch Angabe von @Component() erfolgen:

@Component()
class AppController {
[...]
}

Werkzeuge wie Babel oder auch TypeScript sind bereits in der Lage, das neue Sprachmittel nach ECMAScript 5 zu übersetzen. Nähere Informationen finden sich in einem Artikel zum Thema.

Legt der Entwickler die Controller im selben Ordner wie die Views ab, ergibt sich die nachfolgend dargestellte einheitliche Projektstruktur:

Projektstruktur

Bei den .map-Dateien handelt es sich um vom Transpiler erzeugte Source Maps. Sie geben Entwicklern die Möglichkeit, ECMAScript-6-Code zu debuggen, obwohl der Browser das daraus erstellte und ECMAScript 5 zugrunde legende Kompilat ausführt.

Nach dem Definieren einer Konstante führt das betrachtete Beispiel ein manuelles Bootstrapping von AngularJS durch. Es ersetzt die Verwendung von ng-app und ist in Fällen notwendig, in denen die Anwendung die einzelnen Skript-Dateien nicht über script-Verweise sondern über einen Modul-Loader lädt.