Webanwendungs-Entwicklung: So gelingt der Sprung von AngularJS zu Angular 10

AngularJS für die Frontend-Entwicklung fällt aus dem Support, der Umstieg auf Angular 10 lohnt sich also. Wir zeigen, was bei der Migration zu beachten ist.

In Pocket speichern vorlesen Druckansicht 8 Kommentare lesen

(Bild: Sepp photography/Shutterstock.com)

Lesezeit: 15 Min.
Von
  • Lukas Adler
Inhaltsverzeichnis

In aktuellen Webanwendungen sind Frontend-Frameworks für die Umsetzung unternehmenskritischer Funktionen nicht mehr wegzudenken. Eines davon ist AngularJS, ein 2010 erschienenes, robustes und über viele Jahre gewachsenes Frontend-Framework. AngularJS bietet gegenüber der Entwicklung von Webanwendungen mit reinem JavaScript einige Vorteile wie wiederverwendbare Komponenten und Direktiven, Two-Way Bindings und Dependency Injection. Durch diese und weitere Vorteile erlangte AngularJS eine große Popularität, die ihren Höhepunkt im Jahre 2016 erreichte, als der von Google entwickelte Nachfolger Angular 2 erschien.

Der Long-Term Support (LTS) für die letzte Version von AngularJS 1.8 begann am 01.07.2018. Die AngularJS-Entwickler verlängerten den für drei Jahre vorgesehenen Support-Zeitraum aufgrund der COVID-19-Pandemie nochmals um sechs Monate. Das bedeutet, dass AngularJS zukünftig keine offiziellen Fixes mehr erhalten wird, was zu Inkompatibilitäten mit neuen Browser- oder Library-Versionen führen kann. Auch das Beheben möglicher Schwachstellen durch offizielle Patches soll entfallen. Somit stellt sich die Frage, wie die Migration des Frameworks zur neueren Angular-Version zu bewerkstelligen ist. Dieser Artikel zeigt die Migration von AngularJS zu Angular 10 in einem großen monolithischen ContentManagementSystem (CMS) und geht auf mögliche Fallstricke ein.

Im vorliegenden Praxisbeispiel bestand die Herausforderung darin, zwei verschachtelte AngularJS-Anwendungen auf Angular-Anwendungen zu aktualisieren. Die erste Anwendung war das CMS. Es bot neben anderen Funktionen die Möglichkeit, die Oberfläche von Hotspots für verschiedene Partner frei zu konfigurieren. Die zweite Anwendung, nachfolgend als Portal bezeichnet, stellte die eigentliche Benutzeroberfläche der Hotspots dar. Der Kern des CMS bestand aus einem Editor, in dem sich die Benutzeroberfläche des Portals mithilfe von Overlays, Dialogen und What-You-See-Is-What-You-Get-Editoren (WYSIWYG) bearbeiten ließ. Das stark auf den Kunden zugeschnittene Produkt hatte seinen Fokus auf einem intuitiven und leicht zu bedienenden UI-Design. Der Bearbeitungsmodus durch Overlays erforderte es, die Anwendungen ineinander zu verschachteln. Dazu lud AngularJS die Bundles (Menge von JavaScript-Dateien) des gebauten Portals im Editor des CMS. Dieses Einbetten des Portals in den Editor lässt sich als App-in-App-Struktur bezeichnen.

Zur Risikoeinschätzung fand zunächst eine Studie statt, um die Möglichkeit einer Angular-Migration der monolithischen Anwendung zu evaluieren. Als Basis der Analyse diente eine Tabelle mit den Spalten "Anwendungsbereich", "Abhängigkeiten", "vermutete Probleme" und "potenzielle Lösungsansätze". Die Analyse identifizierte 59 zu prüfende Anwendungsbereiche. Dabei soll der Fokus im Folgenden auf den größten Komplexitätstreibern und deren Lösungsansätzen liegen.

Aufgrund der Größe der beiden Anwendungen erstreckte sich die Migration von AngularJS zu Angular über einen längeren Zeitraum. Für die inkrementelle Aktualisierung der einzelnen Bausteine kam für den Übergangszeitraum eine hybride App zum Einsatz. Dazu stellt Angular das upgrade/static-Paket zur Verfügung, das das Betreiben einer AngularJS-Anwendung in einer Angular-Anwendung erlaubt. Für über Jahre gewachsene monolithische Anwendungen bietet das den großen Vorteil, dass es nicht nötig ist, sie in einem Zug auf Angular 10 zu aktualisieren ("Big Bang"-Ansatz). Für das inkrementelle Upgrade gibt es verschiedene Hilfsfunktionen, um aktualisierte Angular-Komponenten und -Services über eine Downgrade-Methode in AngularJS zu verwenden (s. Abb. 1).

Hilfsfunktionen für ein inkrementelles Upgrade auf Angular 10 (Abb. 1)

(Bild: Angular)

In einem ersten Schritt sind die für Angular notwendigen Pakete zu installieren, wie hier auszugsweise gezeigt. Das upgrade/static-Paket kommt an dieser Stelle zur Geltung, da es alle Funktionalitäten für den Betrieb einer hybriden Anwendung enthält.

"@angular/upgrade": "^10.2.1",
"@angular/core": "^10.2.1",
"@angular/common": "^10.2.1",
...

Danach ist das Importieren von UpgradeModule aus dem upgrade/static-Paket erforderlich (Auszug):

@NgModule({
  imports: [UpgradeModule, …

AngularJS setzt das Bootstrapping der Anwendung im Template über die ng-app-Direktive um. Angular ersetzt das durch den nachfolgend gezeigten Code. Zuerst erfolgt das Setzen der AngularJS-Version, danach der Anstoß für das Bootstrapping des Root-Moduls:

setAngularJSGlobal(angular);
platformBrowserDynamic().bootstrapModule(AppModule);

Da das CMS noch keine Single-Page-Anwendung (SPA) ist, prüft es für jede Seite einzeln, ob die AngularJS- oder die Angular-Anwendung zu laden ist. Im Development-Modus prüft es, ob die Root-Komponente von Angular im Template gesetzt ist und ob ein Eintrag im Local Storage vorhanden ist. Wenn beides zutrifft, lädt das CMS die Angular-Anwendung; ansonsten lädt es die AngularJS-Anwendung. Über den Eintrag im Local Storage lässt sich steuern, welche Anwendung zu laden ist:

if (hasAngularRootComponent && (isAngular)) {
      	app.bootstrap(AppComponent); return;
}
this.downgradeResourcesToAngJs();
this.upgrade.bootstrap(angularRoot, [cmsApp], { strictDi: true });

In beiden AngularJS-Anwendungen lädt der TypeScript Loader die Typescript-Dateien, Webpack und der raw-loader die dazugehörigen Templates der Komponenten. Der raw-loader lädt Dateien in einen String. Das importierte HTML-Template in AngularJS sieht aus wie folgt:

import template from './cms.component.html';

Mit dem TypeScript Loader und dem raw-loader funktioniert das Laden von Templates auch in Angular 10, hier ein Decorator mit importiertem Template:

@Component({
  selector: 'cms-root',  template: template })
Der Komponenten-Decorator von Angular verwendet aber üblicherweise eine [code]templateUrl[/code]:
@Component({
  selector: cms-root, templateUrl: './cms.component.html'})

Der im AngularJS-Portal verwendete TypeScript Loader versucht, die templateUrls über HTTP abzufragen. Das resultiert in einem HTTP-404-Fehler, da auf diesem Weg die Templates nicht aufzufinden sind, wenn sie nicht im korrekten Verzeichnis für statische Inhalte liegen. Stattdessen soll sich der relative Pfad auf das Dateisystem beziehen. Dieses Problem lässt sich durch den Einsatz des angular2-template-loader lösen, der die templateUrl durch ein Inline Template ersetzt.

Damit die Templates über einen relativen Pfad zu laden sind, verwendet die hybride App das Angular Compiler Webpack Plugin (ngtools/webpack), um das Laden der Templates vom Dateisystem über die URL zu ermöglichen. Der wichtigste Vorteil von ngtools/webpack ist allerdings die Ahead-of-Time-Kompilierung (AOT). Zuletzt ruft das Angular Compiler Webpack Plugin noch den Angular compatibility compiler (ngcc) auf. Er sorgt für die Ivy-kompatible Kompilierung aller Libraries. Falls ngtools/webpack nicht alle Anwendungen Ivy-kompatibel kompiliert hat, kann das auch als Postinstall-Schritt erfolgen:

"postinstall": "ngcc -l debug --async false",

Das Importieren von Templates über den raw-loader in Kombination mit dem Angular Compiler Webpack Plugin ist nicht mehr möglich, da das Template vor der Kompilierung feststehen muss, und führt zu folgender Fehlermeldung: "template must be a string Value could not be determined statically." Das Laden der Templates über die templateUrl ist ohnehin der bevorzugte Weg.

Dieser Ansatz zeigt somit, wie sich eine hybride Anwendung realisieren lässt, in der eine AngularJS-Anwendung in einer Angular-Anwendung läuft. Des Weiteren ermöglicht er das Laden von Inline Templates (AngularJS-Anwendung) und templateURLs (Angular-Anwendung).