Anwendungen von AngularJS auf Angular 2 migrieren [Tutorial]

AngularJS 1 ist ein gutes, stabiles Framework, um Web-Anwendungen zu erstellen. Durch die Entscheidung der Angular(JS)-Entwickler, die Nachfolgeversion des Frameworks auf komplett neue Füße zu stellen, sind viele Entwickler unsicher, ob sie ihre bestehende, gut funktionierende AngularJS-Anwendung updaten sollen oder nicht. Das Elster-Naturell vieler Entwickler trägt wahrscheinlich seinen Teil dazu bei. Zur Beruhigung dieser Leser kann gesagt werden: AngularJS 1 wird nicht morgen als deprecated gekennzeichnet werden; durch die große Verbreitung von AngularJS 1 wird dieses Framework noch eine ganze Weile eine wichtige Rolle in der Entwicklung von Web-Anwendungen spielen. Ein Upgrade kann dennoch sinnvoll sein, da Angular 2 viele sinnvolle neue Konzepte und Features einführt, von denen die Entwicklung einer wartbaren, sauber strukturierten Anwendung nur profitieren kann.

So kann Angular 2 etwa mit einer guten Unterstützung von Typescript punkten, die bei AngularJS 1 eher aufgesetzt und unpraktisch wirkte. Bei Angular 2 ist Typescript dagegen die bevorzugte Variante der Implementierung, was die Entwicklung von stabileren Anwendungen durch Typsicherheit und sinnvoller Modularisierung verspricht. Das durch Web Components und React bereits bekannte Konzept einer hierarchisch verschachtelten komponentenbasierten Entwicklung von Web Apps wird in Angular 2 ebenfalls umgesetzt – statt flacher Controller-Hierarchien mit angebundenen Templates können Komponenten verschachtelt definiert werden, was die Wiederverwendbarkeit und die strukturelle Ordnung erhöht.

Dieser Beitrag beschreibt den Versuch, eine bestehende AngularJS-Anwendung Schritt für Schritt auf Angular 2 zu upgraden. Dabei werden besonders die verschiedenen Fallstricke erklärt, die auf dem Weg dahin auftreten können. Da es in großen, produktiv genutzten Anwendungen oft der Fall ist, dass ein „Big Bang“ Upgrade, bei dem alle Komponenten der Anwendung auf einmal umgestellt werden, wegen paralleler Feature-Entwicklung nicht möglich ist, wurde ein inkrementeller Ansatz gewählt, bei dem AngularJS- und Angular-2-Komponenten innerhalb einer Web App miteinander interagieren.

Das Ziel ist es, die alte, auf ECMAScript 5 basierende AngularJS 1 App Schritt für Schritt auf eine mit Typescript entwickelte Angular 2 App zu upgraden. Dabei muss einmalig die Interoperabilität zwischen den beiden Systemen hergestellt werden – dann können neue Features als Angular 2 Components implementiert werden, während alte Direktiven und Controller der AngularJS-1-Anwendung Schritt für Schritt nachgezogen werden.

Sind die wichtigsten Teile der AngularJS-1-Anwendung umgezogen, kann auf eine Angular-2-Anwendung gewechselt werden, die die noch nicht umgezogenen Services, Controller und Direktiven in einem Kompatibilitätsmodus ausführen kann. Anschließend werden auch diese letzten Teile noch in Angular 2 überführt. Abschließend kann die Abhängigkeit zu AngularJS 1 komplett entfernt werden und die Migration ist vollständig.

Dependency Management

Während AngularJS 1 und davon abhängende Bibliotheken per Bower installiert werden, muss für Angular 2 npm herangezogen werden. Mit angular2-build gibt es zwar ein Bower Repository für diesen Zweck, mit diesem werden jedoch keine Typing Files ausgeliefert, die später für den Typescript-Kompiliervorgang benötigt werden.

Angular 2 ist eigentlich nicht über Bower verfügbar, weil die mit Bower ausgelieferten Bibliotheken in ECMAScript 5 implementiert sind (gewöhnliches JavaScript), das direkt im Browser ausgeführt werden kann – diese Anforderung besteht für npm nicht.

Da Angular 2 stark auf moderne Features von JavaScript setzt, die erst in ECMAScript 6 und ECMAScript 7 eingeführt wurden und die viele Browser noch nicht unterstützen, werden zusätzlich zu Angular 2 noch die Polyfills es6-shim und reflect-metadata benötigt, die Features wie Promises und Annotations nachrüsten.

Da Angular 2 modular aufgebaut ist, wird noch ein Module Loader benötigt, um die einzelnen Teile einer Anwendung zusammenzuführen und zur Ausführung zu bringen. Angular nutzt von Haus aus hierfür den universellen Module Loader System.js, der mit beliebigen Modul-Definitionen funktioniert.

Typescript-Kompilierung

Die Kompilierung von Angular-2-Komponenten funktioniert relativ problemlos mit dem Standard Typescript Compiler, in der Konfigurationsdatei muss nur SystemJS als Module-Loader erwähnt werden:

Damit korrekt gegen verwendete Angular-Imports kompiliert werden kann, muss Typescript die Typing-Dateien für Angular finden. Diese werden zusammen mit der angular2-Abhängigkeit über NPM ausgeliefert.

Bootstrapping der Hybridanwendung

Normalerweise werden AngularJS-1-Anwendungen über die ng-app-Direktive gebootstrapt, die einem Container-Element in der ursprünglichen index.html als Attribut mitgegeben werden muss. Damit AngularJS 1 und Angular 2 koexistieren können, muss die bestehende AngularJS-1-Anwendung nun aus Angular 2 heraus gestartet werden. Dafür wird der Upgrade-Adapter eingesetzt:

Das obige Code-Snippet startet die AngularJS-1-Anwendung myApp im Kompatibilitäts-Modus mit eventuellen Angular-2-Komponenten. Um geupgradete Komponenten wieder in die AngularJS-1-Anwendung einzufügen, müssen diese gedowngradet werden:

Die Direktive MyDirective kann man jetzt wie gehabt in die AngularJS-1-Anwendung einbinden. Es muss nur darauf geachtet werden, dass sich die Syntax für die Übergabe von Parametern geändert hat – die Attributnamen müssen in eckige Klammern gesetzt werden:

Achtung! Diese Methode funktioniert nur für Elementdirektiven (restrict: “E”).

Problematisch wird diese Vorgehensweise, wenn die Direktive, die in die Angular-2-Welt übertragen wurde, irgendwelche Abhängigkeiten zu Komponenten hat, die noch nicht von AngularJS 1 geupgradet wurden. Dazu zählen zum Beispiel Provider, die per Dependency Injection in der Direktive geladen wurden oder im Template der Direktive verwendete andere Direktiven oder Filter.

Um die Direktive wieder lauffähig zu machen, müssen diese Abhängigkeiten in Angular 2 verfügbar gemacht werden – dies kann man teilweise mit dem UpgradeAdapter erledigen.

  • Verfügbarmachen von Providern: Provider können mit einem einfachen Befehl für Angular-2-Komponenten sichtbar gemacht werden: upgradeNg1Provider(myProvider).
  • Verfügbarmachen von Direktiven: Verwendet die eigene Direktive andere Direktiven, z.B. aus Bibliotheken, kann das leicht zu Problemen führen. So lassen sich viele Direktiven der bekannten Bibliothek angular-material nicht in Angular 2 verfügbar machen. Die einzige Möglichkeit ist es, auf eine Angular-2-Bibliothek auszuweichen – auf lange Sicht muss diese Aufgabe sowieso erfüllt werden, da alle AngularJS-1-Abhängigkeiten schlussendlich entfernt werden sollen. Dieses Vorgehen funktioniert nur für Elementdirektiven (restrict: “E”), Attributdirektiven können nicht verfügbar gemacht werden und müssen neu implementiert werden.
  • Verfügbarmachen von Filtern: Filter wie in AngularJS 1 ersetzt Angular 2 durch sogenannte Pipes. Leider gibt es keinen Weg, AngularJS-1-Filter direkt als Angular-2-Pipes zu nutzen und später zu migrieren. Es ist erforderlich, diese zuerst selbstständig upzugraden. Das kann in der Folge zu der unschönen Situation führen, dass es zwei verschiedene Versionen von demselben Pipe/Filter gibt, die getrennt voneinander gepflegt werden müssen. Hier kann durch die Reihenfolge der Komponenten-Upgrades versucht werden, diese Situation zu vermeiden oder zu minimieren.

Upgrade von Controllern und Routes

Das größte Problem beim inkrementellen Upgrade auf Angular 2 ist der Rewrite von Controllern, die an bestimmte Routes gebunden sind, da es das Prinzip von Controllern in Angular 2 nicht mehr gibt. Um dieses Problem zu umgehen, gibt es die Bibliothek “new-router” für AngularJS 1, das das Routing-Konzept von Angular 2 in AngularJS 1 implementiert. Dadurch ist es möglich, einige Routes mit vorhandenen AngularJS 1-Controllern und andere mit neu implementierten Angular 2-Components zu befüllen. Auf diese Weise können alle Controller Schritt für Schritt in Components umgewandelt werden, bevor am Ende AngularJS 1 entfernt und auf den echten Angular 2 Router gewechselt wird.

Testen der Hybridanwendungen

Möchte man Angular 2 (TypeScript) Tests mit Karma und Jasmine durchführen benötigt man ein erweitertes Setup, da Karma-Tests nicht ohne Anpassung mit Module-Loadern wie SystemJS kompatibel sind. Neben bisherigen AngularJS-1-Tests müssen Angular-2-spezifische Tests hinzugefügt werden. Folgende Schritte sind dabei zu beachten:

  • Hinzufügen der Angular-2-Abhängigkeiten in der karma.conf.js
  • Hinzufügen einer karma-test-shim.js Datei, um Karma mit system.js verwenden zu können
  • Kompilieren der TypeScript-Tests

Hinzufügen der Angular-2-Abhängigkeiten

In der karma.conf.js müssen die Dateien (Abhängigkeiten) hinzugefügt werden, die im Browser von Karma geladen werden müssen und zur Ausführung von Angular 2 benötigt werden.

Außerdem müssen die selbstgeschriebenen Angular-2-Komponenten hinzugefügt werden. Diese werden wie oben beschrieben in die einzelne Bundle-Datei tsc.js geschrieben. Deshalb muss das kompilierte JavaScript File auch in karma.conf.js hinzugefügt werden.

Um die Angular-2-Tests in einer Continous Integration Pipeline in PhantomJS auszuführen, werden außerdem zusätzliche Polyfills benötigt, da verschiedene Teile von Angular sonst nicht korrekt funktionieren.

Hinzufügen einer karma-test-shim.js

Die einzelnen Unit tests werden nicht mehr über die karma.conf.js hinzugefügt, sondern mithilfe einer karma-test-shim.js geladen. Das ist ein kurzes Skript, das es ermöglicht, system.js mit Karma zu verwenden. Mithilfe der karma-test-shim.js werden alle Tests, die .spec im Namen enthalten, mithilfe von system.js importiert.

Kompilieren der Typescript-Tests

Um die Typescript-Tests in Javascript zu kompilieren, wird eine zusätzliche Typescript-Config benötigt. Diese kann direkt in einem Gulp-Task ausgeführt werden, der später Karma ansteuert.

Das hier beschriebene Setup von Karma als Testrunner und Jasmine als Framework wird vom Angular 2 Team selbst verwendet und für alle Angular-2-basierten Projekte empfohlen.

Der momentan noch recht aufwändige Aufbau lässt sich dadurch erklären, dass das Angular-2-Ökosystem noch sehr jung ist. Sobald die Community mehr Erfahrung in diesem Bereich gesammelt hat, werden sich hierfür wahrscheinlich elegantere Methoden entwickeln, die auch im Bezug auf Testrunner und Testframework mehr Freiheiten bieten.

Der erste Test

Ein beispielhafter Test kann wie folgt aussehen: Um compile errors zu verhindern müssen die TypeScript Typings von Jasmine importiert werden.

Weitere Resourcen

Testing Angular 2 apps – Part 2: Dependency Injection and Components

We’re hiring!

Tapetenwechsel gefällig? Wir sind auf der Suche nach begeisterten Frontend-Entwicklern, die unsere Projektteams im Umfeld von JavaScript, HTML und CSS unterstützen und auch vor innovativen Themen wie AngularJS und Progressive Web Apps nicht zurückschrecken. Jetzt Bewerben!

Weiterlesen

Mehr Informationen zu unseren Dienstleistungen rund um die Web-Entwicklung gibt es auf unserer Website. Unser Portfolio umfasst außerdem die Anwendungsentwicklung für Android & iOS mit speziellem Fokus auf Enterprise-Apps. Für direkten Kontakt schreibt an info@inovex.de oder ruft an unter +49 721 619 021-0.

comments powered by Disqus