Neues in der Major-Version

Angular 11: Entwickler im Fokus

Karsten Sitterberg

© Shutterstock / Ink Drop

Angular hat eine neue Major-Version erhalten, die vor allem die Entwickler von Angular-Projekten adressiert. Enthalten sind einige kleine Neuigkeiten, die aber große Wirkung haben. Auch Breaking Changes gibt es zu vermelden, allerdings hilft hier das CLI weiter.

Am 11.11. erschien Angular in Version 11. Die rund fünf Monate Arbeit seit dem letzten Major Release am 24. Juni und dem Minor Release 10.1 am 8. September hatten vor allem das Feedback der weltweiten Angular-Community im Fokus. So wurde unter dem Schlagwort „Operation Byelog“ intensiv an den auf GitHub gemeldeten Issues gearbeitet, um diese zu kategorisieren und gezielt angehen zu können. Dies Ziel wurde erreicht! Natürlich ist die Abarbeitung der Issues noch nicht abgeschlossen – ein Zeichen eines aktiven Open-Source-Projekts ist es ja gerade, dass die Community sich in Form von Issues einbringt. In Zukunft soll die initiale Bearbeitung neuer Pull Requests und Issues innerhalb eines Zeitfensters von maximal zwei Wochen erfolgen. Allein seit Mitte Oktober wurden bis zum Release von Angular 11 über 340 Issues als erledigt markiert (Abb. 1).

Abb. 1: Erledigte Issues

Auch das Thema Transparenz, ein häufig geäußerter Kritikpunkt, wird aktiv angegangen: So ist nun unter [1] die Roadmap des Angular-Projekts zu finden. Dies spiegelt die aktuelle Einschätzung und Planung zu den wichtigsten Themen aus Sicht der Angular Core-Entwickler wieder. Angular ist dank des reichhaltigen Ökosystems inzwischen mehr als nur das Framework im Zentrum. Umso wichtiger, dass auch externe Entwickler und Contributors die Möglichkeit bekommen, ihre Planung entsprechend auszurichten.

Das Major Release Angular 11 bringt natürlich auch technische Neuerungen rund um die Plattform, das Angular CLI, Material Components und TypeScript mit. Alle Bereiche werden wir uns im Folgenden im Detail ansehen.

TypeScript

TypeScript entwickelt sich kontinuierlich weiter und Angular hält Schritt: Mit Angular 11 wird der Support für TypeScript 3.9 eingestellt, TypeScript 4.0 ist nun nötig. Das Update ist vor allem auch deshalb wichtig, da mit TypeScript 4.1 bald eine Optimierung ausgerollt werden soll, die den Angular-Compiler ngcc bis zu vier Mal schneller machen soll.

Darüber hinaus bietet TypeScript 4.0 einige weitere interessante Neuerungen. So wurde die Typ-Inferenz bei TypeScript-Klassen verbessert: Auch wenn der Typ der Properties area und sideLength nicht explizit angegeben ist, kann TypeScript diese anhand der Wertzuweisungen im Konstruktor erkennen. So bekommt das Property sideLength den Typ number (Listing 1). Falls nicht alle Codepfade innerhalb des Konstruktors sicher zu einer Wertzuweisung führen, wird das Property als potentiell undefined markiert. Daher hat das Property area im Beispiel den Typen number | undefined.

Listing 1
class Square {

  // Bisher waren beide Properties vom Typ 'any'
  sideLength; // Typ 'number'
  area; // Typ 'number | undefined'

  constructor(sideLength: number) {

    this.sideLength = sideLength;
    if (Math.random()) {
      this.area = sideLength**2;
    }

  }
}

Mit TypeScript 4.0 wurden auch neue (Zuweisungs-)Operatoren hinzugefügt. Wie aus vielen anderen Sprachen bekannt, gab es auch in TypeScript bisher schon Zuweisungs-Operatoren wie a += b; (entspricht a=a+b;) oder a -= b; (entspricht a=a-b;). Nun werden solche Operatoren entsprechend des ECMAScript-Standards auch für die Verknüpfungen &&, || und ?? hinzugefügt. Es gibt nun also die folgenden Zuweisungs-Operatoren:

  • a &&= b (entspricht a && a = b),
  • a ||= b (entspricht a || a = b, kann auch geschrieben werden als if(!a){ a = b; }) und
  • a ??= b (entspricht a ?? a = b).

Wichtig ist bei allen diesen Operatoren, dass die Zuweisung nur geschieht, wenn sie notwendig ist. Bei a||=b etwa geschieht die Zuweisung nur dann, wenn der Wert von a falsy (also etwa 0, null oder undefined) ist.

Eine kleine Verbesserung wurde für den catch(error)-Block in try-catch-Statements eingeführt: Bisher war eine Typdeklaration des übergebenen error-Parameters nicht möglich, da er immer den Typen any hatte. Somit hatte TypeScript keine Möglichkeit, Checks auf Zugriffe durchzuführen. Nun ist es auch möglich, unknown als Typen anzugeben. Dies ist zwar kein definitiver Typ, allerdings erlaubt TypeScript Zugriffe auf unkown-Variablen nur dann, wenn diese durch einen Type-Guard abgesichert werden. So kann an dieser Stelle Typsicherheit dazugewonnen werden, wie in Listing 2 zu sehen ist.

Listing 2
try {
  // ...
} catch (e: unknown) {

  console.log(e.toUpperCase()); // Error: Object is of type 'unknown'.

  if (typeof e === "string") {
    // Type-Narrowing: 'e' ist vom Typ 'string'.
    console.log(e.toUpperCase());
  }
}

In zukünftigen TypeScript-Versionen soll eventuell eine Compiler-Option hinzugefügt werden, mit dem der unknown-Typ an dieser Stelle dann erzwungen würde.

Weiterhin wurden mit der neuen TypeScript-Version einige Verbesserungen in Sachen Build-Geschwindigkeit und Editor-Unterstützung durch den Language-Service erreicht. Mit der neuen Major-Version gab es auch einige Breaking Changes. So wurde document.origin entfernt, da dies ohnehin nur in alten IE und Safari-Versionen funktionierte. Stattdessen sollte self.origin verwendet werden. Zudem wird nun ein Fehler geworfen, wenn versucht wird, ein Objekt-Property mit einem set- bzw. get-Accessor zur überschreiben. Dies gilt auch im umgekehrten Fall (Überschreiben eines Setters/Getters mit einem reinen Property).

Der delete-Operator konnte bisher auf alle Properties angewendet werden. In Zukunft kann delete nur noch angewendet werden, wenn das zu entfernende Property als optional gekennzeichnet ist.

Update auf Angular 11

Wer das offizielle Angular CLI verwendet, ist beim Thema Update fein raus: Selbst, wenn man nicht kontinuierlich auf die aktuelle Version migriert hat, wird man durch das Werkzeug bei Updates gut unterstützt. Bei einem kleinen Projekt ist das Update innerhalb weniger Minuten erledigt. Bei komplexen Projekten, die umfangreichen Gebrauch von den Angular APIs machen, ist typischerweise ein Aufwand von einer bis wenigen Stunden realistisch. Das Vorgehen ist gewohnt komfortabel und einfach. Das Angular CLI wird mit dem update-Task aufgerufen:

ng update @angular/core @angular/cli

Anschließend sollten alle Tests ausgeführt werden, um potenzielle Regressionen aufzuzeigen. Ich kann nur empfehlen, dass wirklich jeder in eine sinnvolle Testabdeckung investiert.

Wer sich über die Details eines Updates informieren möchte, findet dazu eine interaktive Anwendung unter [2]. Anhand der Ausgabe kann auch eine manuelle Migration auf Angular 11 unterstützt werden.

CLI

Nach dem Update auf Angular CLI 11 ist die erste und offensichtlichste Neuerung die aktualisierte Log-Ausgabe der Befehle ng serve und ng build. Damit soll eine bessere Übersicht über die erzeugten Bundles und deren Größe geschaffen werden. Dabei werden die Grund-Bundles der Anwendung separat von den Lazy-Loading Bundles aufgeführt. Letztere sind nun nach Größe sortiert. In Abbildung 2 und 3 [Anm. d. Red.: für eine große Darstellung bitte auf die Abbildungen klicken] sind die alte und die neue Ausgabe von ng serve –prod beispielhaft gegenübergestellt.

Abb. 2: Alte Ausgabe von ng serve –prod

Abb. 3: Neue, geordnete und übersichtliche Ausgabe von ng serve –prod

Werden innerhalb des Projektes Google-Fonts eingebunden, so werden diese nun als Teil des CLI-Prod-Builds in die index.html als separater Link eingebettet, wenn das Property „optimization“: true in der angular.json gesetzt ist. Diese Optimierung erlaubt die Fonts parallel zum Anwendungsstart zu laden, und nicht erst als Teil der Anwendungs-Bundles. Damit reduziert sich praktischerweise auch der Umfang des Anwendungs-Bundles.

Wenn eine Anwendung entwickelt wird, kann es vorkommen, dass das Setup der Anwendung zur Entwicklungszeit ein anderes ist als das Setup der Anwendung zur Produktions-Laufzeit. Etwa wenn Backend und/oder Frontend zur Produktions-Laufzeit in einer Umgebung laufen, die spezielle Sicherheitsfeatures erfordert. Solche Sicherheitsfeatures sind zum Beispiel eine CSP oder die Types moderner Browser. Beide Features werden vom Browser aber nur aktiviert, wenn der Server spezielle Header mitschickt. Da im Angular CLI Dev-Server aber keine Custom Header gesetzt werden konnten, konnten diese Features bisher nicht leicht zur Entwicklungszeit simuliert werden. Mit Angular-CLI 11 gibt es nun die Möglichkeit, Custom Header in der angular.json als headers-Property innerhalb der architect.build.options zu hinterlegen. Das headers-Property ist dabei eine Key-Value-Map, in der der Key der Name des Headers ist und Value der entsprechende Wert. So lässt sich beispielsweise ein CSP-Header auf folgende Weise spezifizieren:

"headers": {
    "Content-Security-Policy": "default-src 'self'"
}

Die Möglichkeit, solche Header zu setzen, wurde vor allem in Hinblick auf die sogenannten „Trusted Types“ eingebaut. Trusted Types sind ein neues Sicherheits-Feature in Browsern und sollen Cross-Site-Scripting-Attacken noch effektiver verhindern. Auch Angular 11 nutzt dies, wenn möglich.

Ein Feature, welches speziell Library-Autoren die Arbeit erleichtern soll, sind die sogenannten Declaration-Maps. Diese sollen es Editoren und IDEs ermöglichen, bei Nutzung von “Go to Definition” direkt in den (lokalen) Library-Quellcode zu springen und nicht in die gebauten Library-Dateien innerhalb des /dist-Ordners.

Falls mit der neuen Angular CLI Version zwei Angular-Anwendungen parallel gestartet werden, beschwert sich Angular nun nicht mehr wie früher über einen bereits belegten Port. Stattdessen wird der Entwickler nun direkt gefragt, ob die Anwendung auf einem anderen freien Port gestartet werden soll. Der freie Port wird dabei zufällig aus der Menge der freien Ports gewählt.

Wird mit dem Angular CLI ein neues Projekt angelegt, so wurde vom CLI schon bisher immer abgefragt, ob z. B. ein Style-Preprocessor wie SCSS verwendet und Angular-Routing vorkonfiguriert werden soll. Mit Version 11 kommt nun ein neuer sogenannter Prompt hinzu, der fragt, ob die App im strikten Modus angelegt werden soll. Dadurch werden zum einen die strikten Type-Checks von TypeScript („strict“:true in der tsconfig) aktiviert, etwa die Strict-Null-Checks oder die Strict-Property-Initialization. Zum anderen führt der Angular-Compiler diese strengen Checks auch im Template durch.

Das Angular CLI unterstützt sogenannte Schematics. Diese Schematics sind hilfreiche Tools, um beispielsweise neue Komponenten oder ganze Libraries per einfachem Kommandozeilen-Befehl automatisch generieren zu lassen. Mit Angular 11 wurden diese Schematics so erweitert, dass per ng generate resolver ein neuer Router-Resolve erzeugt wird.

Außerdem setzt es zur Testausführung auf den Karma-Testrunner. Um die für den Testing-Workflow wichtigen Code-Coverage-Reports generieren zu können, nutzt das Angular CLI bisher den karma-coverage-istanbul-reporter. Mit dem Angular CLI 11 werden neue Projekte nun allerdings mit dem Paket besser unterstützten Paket karma-coverage generiert. Bestehende Projekten werden zwar (noch) nicht migriert, aber zumindest wird eine Deprecation-Nachricht bei Nutzung des alten Reporters ausgegeben.

Hot Module Replacement (HMR)

Während der Entwicklung sind kurze Zyklen zwischen Quellcode-Anpassungen und Ergebnisbewertung wünschenswert. Schon jetzt kann das Angular CLI den Browser informieren, wenn Änderungen stattgefunden haben. Der Browser lädt dann autonom die Anwendung neu. Das spart einige Klicks oder Shortcuts, die Anwendung befindet sich danach jedoch wieder im Initialzustand.

Um dies zu verbessern bietet webpack das Feature „Hot Module Replacement“ [3] an. Dabei können bestimmte Änderungen, wie zum Beispiel im globalen CSS Styling direkt live angewendet werden. Das spart natürlich viel Zeit und machen die Entwicklung angenehmer.

Nun bestehen die meisten Änderungen jedoch nicht aus Style-Anpassungen. Mit HMR ist das Ziel, dass der Anwendungszustand auch bei Änderungen der Komponenten weitestgehend erhalten bleibt und kein voller Reload erfolgt. Dazu gehört standardmäßig erst einmal die Scroll-Position im Fenster und Formulareingaben.

Da Angular CLI unter der Haube auf webpack setzt, war auch in der Vergangenheit die Nutzung von HMR möglich. Dazu waren jedoch Anpassungen an der Konfiguration und dem Anwendungscode erforderlich. Mit Angular 11 ist dies nun nicht mehr erforderlich, um eine grundsätzliche HMR Unterstützung zu erhalten – beim Start ist lediglich der Schalter –hrm zu setzen. Damit sieht das vollständige Kommando beispielsweise so aus:

ng serve --hmr

Da sich dadurch das Verhalten der Anwendung verändert, wird auch ein entsprechender Hinweis ausgegeben:

NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.

Aktuell bleibt der Anwendungszustand nicht in allen Konstellationen erhalten. HMR ist in jedem Fall ein vielversprechendes Feature und birgt großes Potential, sobald der volle Funktionsumfang im Kontext von Angular umgesetzt ist.

Abschied von TSLint

Um Code-Qualität besser analysierbar zu machen, gibt es sogenannte Linting-Tools, die den Quellcode etwa auf Einhaltung von Best Practises überprüfen. Ein solcher Linting-Vorgang kann im Angular CLI mit ng lint gestartet werden. Der bisher von Angular-CLI genutzte Linter war TSLint im Zusammenspiel mit dem Codelyzer-Plug-in. Mittlerweile wurde TSLint allerdings von den Projektverantwortlichen als deprecated markiert, die Migration auf ESLint wird offiziell empfohlen. Daher geht nun auch das Angular CLI diesen Schritt: TSLint wird als deprecated markiert und das Angular-Team arbeitet zusammen mit der Community an einer möglichst automatischen Migration der CLI-Projekte zu ESLint.

Build-Zeit und Bundle Size

Jedes Update des Angular CLI begleitet der Wunsch nach einem schnelleren Build mit kleineren Artefakten im Ergebnis. Dabei besteht zwischen den beiden Anforderungen ein Spannungsfeld: Um kleinere Artefakte zu erzeugen wird in der Regel der Buildaufwand steigen, um intensiveres Tree-Shaking und andere Optimierungen durchzuführen. Das wirkt sich negativ auf die Buildzeit aus.

Die Release Notes von Angular sprechen von deutlichen Verbesserungen durch NGCC (Faktor 2-4) und die aktuellere TypeScript Version. NGCC wird verwendet um NPM-Pakete, die aus kompatibilitätsgründen standardmäßig noch im alten View-Engine-Format publiziert werden, in das neue Ivy-Format zu übersetzen. Dieser Konvertierungsaufwand fällt typischerweise nur einmal an, da die Ergebnisse gecacht werden. Daher profitieren von diesen Verbesserungen in erster Linie CI Server, die so konfiguriert sind, dass sie jeden Build komplett mit einem leeren Projekt starten.

Im Folgenden wurde anhand von zwei aktuellen Kundenprojekten das Verhalten als production-Build gemessen. Für eins der Projekte konnte auch die Preview von webpack 5 getestet werden, da dort keine Webworker verwendet wurden. Mehr zu webpack 5 folgt im nächsten Abschnitt. Betrachten wir zunächst die Ergebnisse in Listing 3 bis Listing 7.

Listing 3: Build Angular 10 Projekt “Remote Training”
chunk {} runtime.43e2b8a007f700481.js (runtime) 2.53 kB [entry] [rendered]
chunk {1} 1.84010c5af42650386d40.js () 26 kB  [rendered]
chunk {2} 2.e9a861b71b633965b3b8.js () 16 kB  [rendered]
chunk {3} main.7fda6eefa7e683cc3ca1.js (main) 825 kB [initial] [rendered]
chunk {4} polyfills.8456b03b9e12a8e8.js (polyfills) 36 kB [initial] [rendered]
chunk {5} styles.3c95f0a9d119125db70f.css (styles) 68.1 kB [initial] [rendered]
chunk {6} 6.f3ee68e8823eec12d047.js () 881 kB  [rendered]
chunk {7} 7.b97f54a1e17c0375e958.js () 136 kB  [rendered]
chunk {8} 8.6fd325ff961e99e0808e.js () 4.78 kB  [rendered]
chunk {9} 9.26554fc7d61c8c809b39.js () 45.4 kB  [rendered]
chunk {10} 10.d6534ccfa9246c4adb20.js () 4.73 kB  [rendered]
chunk {11} 11.81cfc9377af48e428fbc.js () 2.21 kB  [rendered]
chunk {12} 12.bbb91bc2f292c14a7367.js () 11.8 kB  [rendered]
chunk {13} 13.3a5fb0fd7914837b0c8c.js () 1.16 kB  [rendered]
chunk {14} 14.a3b89a1c7ac7c0a5b08d.js () 2.79 kB  [rendered]
chunk {15} 15.b61a35f235d989c99a73.js () 93 bytes  [rendered]
chunk {16} 16.013f95e86cc15a0c515b.js () 3.22 kB  [rendered]
chunk {scripts} scripts.eaa39cabe4928.js (scripts) 1.57 MB [entry] [rendered]
Date: 2020-11-15T21:05:09.170Z - Hash: 77566713ce72558de04b - Time: 26732ms
Listing 4: Build Angular 11 Projekt “Remote Training”
Initial Chunk Files               | Names     	|  	Size
scripts.e97c506d3885f99c38a9.js   | scripts   	|   1.57 MB
main.d09be28430557c5a3a88.js      | main      	| 783.82 kB
styles.57d88b82d19b1de143f6.css   | styles    	|  68.61 kB
polyfills.b798e2cd31320f43ed2f.js | polyfills 	|  36.00 kB
runtime.397a60f455e8ebb34f70.js   | runtime   	|   2.42 kB

                                  | Initial Total |   2.44 MB

Lazy Chunk Files              	| Names     	|  	Size
5.044d6812ea6aa19f7c91.js     	| -         	| 135.79 kB
7.1e8a11aa598b7c849312.js     	| -         	|  46.65 kB
1.ca17a65df4afeb9bbdf4.js     	| -         	|  25.86 kB
10.8f6df508978a29dc7637.js    	| -         	|  11.82 kB
6.a9f19928649b8564995b.js     	| -         	|   4.77 kB
8.82788de94f5ae3bd26db.js     	| -         	|   4.73 kB
12.1d361f8ef7f203f1de3f.js    	| -         	|   2.79 kB
9.766b01f7f382aa133fba.js     	| -         	|   2.20 kB
11.eed7e9f8396eb884650a.js    	| -         	|   1.17 kB

Build at: 2020-11-15T21:23:35.729Z - Hash: f7c7ef39f09c24e - Time: 25253ms 

 

Listing 5: Build Angular 10 Projekt “Mobile Marketing”
chunk {} runtime.acf1f05ab39abc62fff7.js (runtime) 2.28 kB [entry] [rendered]
chunk {1} main.f5398d0a43ef90b15fa7.js (main) 443 kB [initial] [rendered]
chunk {2} polyfills.fa70ca150ae09dfd08a8.js (polyfills) 36.1 kB [initial] [rendered]
chunk {3} styles.7284b12cc0babc86e5d1.css (styles) 7.77 kB [initial] [rendered]
chunk {4} 4.ee4f58db44f19937f96b.js () 2.22 kB  [rendered]
chunk {5} 5.820f8ca212f4b8c30487.js () 105 kB  [rendered]
chunk {6} 6.4a76565d61df08843019.js () 1.6 kB  [rendered]
Date: 2020-11-19T20:08:37.614Z - Hash: 2fee8d19f9de74a4fd32 - Time: 16348ms

 

Listing 6: Build Angular 11 Projekt “Mobile Marketing”
Initial Chunk Files               | Names     	|  	Size
main.8107a14698d0bd82e78d.js      | main      	| 438.35 kB
polyfills.63615701763a13869c01.js | polyfills 	|  36.00 kB
styles.7284b12cc0babc86e5d1.css   | styles    	|   7.77 kB
runtime.682d939701041a13a436.js   | runtime   	|   2.27 kB

                                  | Initial Total | 484.39 kB

Lazy Chunk Files              	| Names     	|  	Size
5.6038ec46ed64c4afe42c.js     	| -         	|  99.23 kB
4.528f682123022a5b6f59.js     	| -         	|   2.22 kB
6.9578413d7188f2995dbc.js     	| -         	|   1.60 kB

Build at: 2020-11-19T20:17:06.945Z - Hash: 3b898d74c9d72fee6b0b - Time: 15629ms

 

Listing 7: Build Angular 11 (Webpack 5) Projekt “Mobile Marketing”
Initial Chunk Files               | Names     	|  	Size
main.1dd1e520974bbbcca267.js      | main      	| 480.49 kB
polyfills.6ccfce096debe4f0a331.js | polyfills 	|  38.79 kB
styles.ddd5330196eb11be102d.css   | styles    	|   7.77 kB
runtime.6d8d1027a071439d0621.js   | runtime   	|   3.44 kB

                              	| Initial Total | 530.47 kB

Lazy Chunk Files              	| Names     	|  	Size
552.7934c1452c58d7952579.js   	| -         	| 105.43 kB
297.7c3d900aa923a5dcb53c.js   	| -         	|   2.30 kB
437.4428e7dbed4aefe1459a.js   	| -         	|   1.67 kB

Build at: 2020-11-19T20:21:58.205Z - Hash: 2906ecca18ba441c0f17 - Time: 23270ms

Zu erkennen ist, dass sich die Buildzeit nicht deutlich zwischen Angular 10 und 11 verändert. Kleinere Schwankungen bewegen sich im Rahmen der Messgenauigkeit. Auch die Bundlegrößen ändern sich nicht signifikant. Im Gegensatz dazu zeigt die Preview von webpack 5 deutlich den noch frühen Entwicklungsstand. Hier verändern sich sowohl die Buildzeit als auch die Bundlegrößen negativ.

Vorschau auf webpack 5

Angular soll zukünftig auf webpack 5 umgestellt werden. Schon im Angular CLI 11 wird es die Möglichkeit geben, die neue Version zu verwenden. Dies dient dazu, erste Erfahrungen zu sammeln und auch mögliche Bugs frühzeitig melden zu können. Und in der Tat ist die aktuelle Umsetzung als Vorschau zu bezeichnen: Es wird zwingend Yarn als Paketmanager benötigt und noch sind nicht alle Angular-Features kompatibel. So funktionieren zum Beispiel Webworker noch nicht mit webpack 5. webpack 5 selbst, auch erst seit einigen Wochen freigegeben, bringt viele, zum Teil inkompatible, Änderungen mit sich.

Auf der anderen Seite verspricht webpack aber auch immense Verbesserungen:

Durch einen persistenten Cache der Builds soll die Geschwindigkeit vor allem bei wiederholten Builds deutlich verbessert werden. Die Bundles werden durch verbesserte Tree-Shaking-Algorithmen kleiner und die Codegenerierung wurde optimiert. Das Caching der Build-Ergebnisse wurde durch verbesserte Standardeinstellungen und geschickte Aufteilung der im Browser dauerhaft cachebaren Anteile verbessert.

Die Preview von webpack 5 kann im Angular CLI aktiviert werden, indem die folgende Einstellung in der package.json vorgenommen wird:

"resolutions": {
    "webpack": "5.4.0"
}

Module Federation

Ein besonders vielversprechendes Feature, dass durch webpack 5 erschlossen wird, ist die Module Federation. Damit wird es möglich, gemeinsamen Code zwischen voneinander unabhängigen Anwendungen zu teilen. Bisher musste dazu eine gemeinsame Library extrahiert werden, die sich dann in den Build-Ergebnissen beider Anwendungen wiederfand. Entsprechend wurde der gemeinsame Code auch mehrfach geladen.

Mit webpack 5 soll es möglich werden, dass sich Module aus unterschiedlichen Builds und sogar unterschiedlichen Anwendungen wie Teile aus einem großen zusammenhängenden Graphen auffassen lassen. Damit können dann auch Abhängigkeiten aus anderen Quellen bezogen werden. Redundantes Laden derselben Abhängigkeit für verschiedene Anwendungen kann vermieden werden. Aufgrund der Komplexität kann das Thema im Rahmen dieses Beitrags lediglich angerissen werden und verdient in jedem Fall eine eigenständige Betrachtung.

Angular CLI Docker Image

Passend zum aktuellen Angular CLI stehen auch die von trion.de entwickelten Docker-Images für den Build mit Angular 11 zur Verfügung. Die auf Docker Hub publizierten Images sind auf minimale Größe optimiert und erlauben neben dem eigentlichen Build mit dem Angular CLI auch die Ausführung von Karma Unit-Tests und Protractor e2e-Tests auf einem CI-Server.

Folgende Images stehen zur Verfügung:

  • trion/ng-cli [4] als minimales Build-Image, auch als ARM64-Image
  • trion/ng-cli-karma [5] für Build und Angular Unit-Tests
  • trion/ng-cli-e2e [6] bringt Java für Protractor mit

Details zur Verwendung finden sich auch im Artikel auf JAXenter unter [7].

Angular Framework

In Angular 11 hat sich viel unter der Haube getan, um die Abarbeitung der Roadmap-Themen vorzubereiten. So wurde dieses Release neben den bereits oben angesprochenen Build-Verbesserungen genutzt, um viele Deprecations der vergangenen Versionen durch Entfernung der entsprechenden Features aufzulösen.

Einige nicht-breaking Features betreffen etwa den Compiler, der jetzt besser mit nicht-validem Template-Code umgehen kann, etwa bei unvollständigen HTML-Tags. Für die Angular-PWA-Unterstützung gibt es das @angular/service-worker-Paket. Der Service Worker wird typischerweise genutzt, um die Offline-Fähigkeit einer App zu erreichen. Dabei nimmt der Service-Worker die Aufgaben des Caches wahr. Wenn bestimmte Assets einer Seite vom Browser aus dem Cache entfernt werden, aber aufgrund z. B. einer Server-Aktualisierung auch nicht mehr vom Server zu laden sind, konnte es in der Vergangenheit zu Darstellungsfehlern auf einer Seite kommen, oder die Seite konnte nicht mehr komplett geladen werden. Ein solcher Fehler lässt sich typischerweise nur durch Reload der Seite beheben. Um dies an die App zu kommunizieren, wurde der UnrecoverableStateError als Fehlerzustand eingeführt.

Um Performance und Bundle-Größen zu optimieren, wurden im Router einige Codestellen angepasst, sodass bestimmte Fehlermeldungen nur noch zur Entwicklungszeit im Code vorhanden sind. Etwa der Fehler, der ausgegeben wird, wenn zweimal Routermodule.forRoot() aufgerufen wird, ist vor allem für den Entwickler interessant, nicht aber in Produktion. Mit Angular 11 können diese Fehlermeldungen nun im Prod-Build durch Tree-Shaking entfernt werden.

Breaking Changes

Eine wichtiger Breaking Change ist natürlich die Umstellung von TypeScript 3.9 auf TypeScript 4.0. Nach der Deprecation in Angular 10 ist ein anderer, nicht unwesentlicher Breaking Change das Ende der offiziellen Unterstützung von IE 9, IE 10 und IE Mobile durch Angular. Da diese Browser auch durch ihren Browserhersteller nicht mehr unterstützt werden, sollte das aber in der Regel kein Problem sein. Das Ende des Supports stellt allerdings Ressourcen frei, die nun für andere, wichtige Angular-Themen genutzt werden können.

Mit dem Paket @angular/platform-webworker hat das Angular-Team versucht, den Rendering-Prozess zwecks Performance-Verbesserung in einen separaten Prozess, den sogenannten Webworker auszulagern. Da sich damit jedoch nicht die erhofften Verbesserungen erzielen ließen, wurde das Projekt mit Angular 8 deprecated und nun entfernt. Dies bedeutet aber ausdrücklich nicht, dass man Webworker nicht mehr mit Angular nutzen kann. Tatsächlich bietet das Angular CLI sogar ein spezifisches Schematic an, um eigene Webworker zu generieren: ng generate webWorker <name>. Diese Worker können dann rechenintensive Aufgaben übernehmen, die ansonsten den JavaScript-Rendering-Thread blockieren würden.

Eine wichtige Rendering-Plattform, die Angular neben dem Browser unterstützt, ist das serverseitige Rendering mit @angular/platform-server. Dieses Paket kann auf unterschiedliche Arten genutzt werden. Beispielsweise lässt sich einstellen, dass der Rendering-Prozess mit absoluten oder relativen URLs durchgeführt wird. Wird die absolute Variante verwendet (useAbsoluteUrl) verwendet, ist es nun notwendig, auch die baseUrl anzugeben, mit der die absoluten URLs aufgebaut werden sollen. Dies war vorher nicht der Fall und konnte zu undefiniertem, schwer zu debuggendem Verhalten auch in der Produktion führen. Im Folgenden wird noch auf einige wichtige Änderungen thematisch sortiert eingegangen. Die wesentlichen Breaking Changes werden durch die ng update-Migrations automatisch behandelt.

Pipes und Internationalization

Pipes werden in Angular genutzt, um Transformationen wie Datums- oder Zahlenformatierungen im Template performant und auf Change Detection optimiert durchzuführen. Mit Angular 11 wurde die Typisierung einiger Pipes verbessert und insgesamt konsistenter gestaltet. So wird in allen Number-Pipes und der Date-Pipe jetzt explizit in der Signatur angegeben, welche Übergabetypen die Pipe akzeptiert. Vorher wurde bei falschen Übergabetypen in der Regel nur eine RuntimeException geworfen.

Die Date-Pipe hat auch noch einen Breaking Change im Detail erfahren: Wurden der Pipe bisher Date-Time-Strings übergeben, die Bruchteile einer Millisekunde enthielten, wurden diese bisher auf die nächste Millisekunde gerundet. Dies ist notwendig, da das Standard-Date-Objekt des Browsers nicht mit Bruchteilen einer Millisekunde umgehen kann. Das Runden zur nächsten Millisekunde konnte aber ungewollte Effekte an Tagesgrenzen haben (23:59:59,9999 Uhr wurde zu 0:00:00 am nächsten Tag). Von nun an werden Sub-Millisekunden daher standardgemäß einfach abgerundet.

Die Uppercase/Lowercase Pipes erlauben nun keine falsy Werte wie 0, false, NaN mehr, sondern werfen stattdessen eine Exception. Die Werte null und undefined werden zu null. Mit diesem Change wurden Signatur und Verhalten der Pipes aneinander angepasst.

Ebenso wurde die Signatur der async-Pipe angepasst: auch wenn der Pipe undefined übergeben wird, gibt sie null zurück. Bisher war in der Signatur angegeben, dass die Pipe in diesem Fall undefined zurückgab. Dies widersprach aber dem tatsächlichen Verhalten.

Die slice-Pipe wiederum hatte bisher dieses Verhalten. Sie wurde nun so abgeändert, dass sie, wie auch die anderen Pipes, null zurückgibt, wenn ihr undefined übergeben wird.

Die Signatur der KeyValue-Pipe schließlich wurde so angepasst, dass sie nun das eigentliche Verhalten widerspiegelt: Wurde die Pipe z. B. genutzt, um ein Objekt in ein KeyValue-Array zu verwandeln, so wurde der Key dabei schon immer in einen String verwandelt, auch wenn der Key im ursprünglichen Objekt eine Number war. Dies spiegelt sich nun auch in der Signatur der Pipe wieder. Gut zu wissen ist, dass dies nur für normale Objekte gilt. Wird die KeyValue-Pipe etwa mit einer Map verwendet, so bleiben die number erhalten.

Die meisten Angular-Pipes bieten die Möglichkeit, die Ausgabeformate zu lokalisieren, so etwa die number-, date- und currency-Pipe. Dazu werden Locale-Daten benötigt, die typischerweise je Land unterschiedlich sind. Um Inkonsistenzen zu vermeiden, wurde diese Locale-Data-API nun so angepasst, dass sie die Daten nicht mehr in Form von veränderlichen (mutable) Arrays, sondern in Form von Readonly-Arrays zurück gibt. Ist eine Änderung der Locale-Daten nötig, so müssen die Arrays mittels slice() kopiert und dann in der Anwendung selbst verwaltet werden.

Router

Der Router ist in Angular dafür zuständig, die Nutzer durch die App zu navigieren diese Navigationsvorgänge durch URL-Wechel sichtbar zu machen. Durch Eingabe der URL kann mit dem Router in dedizierte Anwendungsteile gesprungen werden. Im Router wurde vor allem fehlerhaftes Verhalten aufgeräumt, das bereits seit einigen Versionen als Deprecated markiert wurde. So wurde das Property preserveQueryParams, das bei einem Routing-Vorgang dafür gesorgt hat, dass die Queryparameter in der URL erhalten blieben komplett entfernt. Stattdessen soll das eigentlich dafür gedachte Property queryParamsHandling genuzt werden, das zu diesem Zweck auf den Wert preserve eingestellt werden muss.

Bei der Konfiguration des Router mittels .forRoot() kann das Verhalten des Routers auf unterschiedliche Arten konfiguriert werden. So kann mit dem Property initialNavigation eingestellt werden, ob der Router beim Start der Anwendung eine initiale Navigation durchführen soll. Die möglichen Werte dieses Properties wurden mit Angular 11 eingeschränkt, sodass jetzt noch die Werte enabledNonBlocking, enabledBlocking oder disabled möglich sind. Der Unterschied zwischen dem ersten und zweiten Wert ist, dass enabledBlocking zuerst die initiale Navigation ausführt und erst danach den App-Start zu Ende bringt (wird für Server-Side-Rendering benötigt). Bei enabledNonBlocking hingegen findet der initiale Routing-Vorgang statt, nachdem die Root-Komponente geladen wurde und blockiert somit nicht den App-Start. Die alten Optionen (true, false, legacy_enabled, legacy_disabled) wurden entfernt. Der Default ist jetzt enabledNonBlocking und entspricht dem bisher gewohnten Verhalten.

Eine weitere Einstellmöglichkeit, die angepasst wurde, ist die relativeLinkResolution. Mit der Einstellmöglichkeit corrected wurde ein Fehlverhalten des Routers gefixt, dass bei Navigation mit relativen Links innerhalb von Komponenten mit leerem Pfad (path:“) , aber Kind-Routen aufgetreten ist. Mit Angular 11 wird nun corrected zum default. Das alte (fehlerhafte) Verhalten ist noch unter der Einstellmöglichkeit legacy vorhanden.

Eine wichtige Änderung ist auch für alle passiert, die ihre eigene RouteReuseStrategy entwickelt haben. Die RouteReuseStrategy ermöglicht das Wiederverwenden von Komponenten zwischen verschiedenen Navigationsvorgängen. Wenn also von Route /a nach Route /b und dann wieder nach /a navigiert wird, kann durch eine geeignete RouteReuseStrategy erreicht werden, dass die Komponente A bei der letzten Navigation nicht neu erzeugt werden muss, sondern wiederverwendet wird (also in der Zwischenzeit nicht mit Aufruf von ngOnDestoy de-initialisiert wurde). Das Standard-Verhalten ist, dass Komponenten zwischen den Routing-Vorgängen zerstört und bei erneuter Navigation auf die Seite neu erzeugt werden. Die Änderung in Angular 11 bezieht sich nun auf die shouldReuseRoute()-Methode der RouteReuseStrategy: Bisher wurden der Methode bei Kind-Routen die momentane und die zukünftige Route vertauscht übergeben. Dies wurde nun angepasst und sollte bei einem Update auf Angular 11 berücksichtigt werden.

Forms

Die eingeschränkte Typsicherheit bei Verwendung der Angular Forms ist schon länger ein Anliegen der Community. Mit Angular 11 wird in einem ersten Schritt die Typsicherheit in Bezug auf die Validatoren verbessert: Bisher wurden die Validatoren und AsyncValidatoren in den @angular/forms-Direktiven als Typ any[] erzeugt. Jetzt werden die passenden Typen verwendet (ValidatorFn[], bzw. AsyncValidatorFn[]).

Innerhalb eines verschachtelten Formulars ist es möglich, sich per AbstractFormControl.parent eine Referenz auf die umgebende FormGroup zu besorgen. Wenn keine umgebende FormGroup vorhanden ist, konnte dieser Wert schon immer auch null sein. Dies spiegelt sich nun auch im Typen von AbstractFormControl.parent wieder.

Außerdem wurde noch das Verhalten von FormControl/FormGroup/FormArray mit asynchronen Validatoren korrigiert: Wurden diese schon bei der Initialisierung der Forms mit angegeben, hat das statusChanges-Observable nicht gefeuert, sobald die asynchrone Validierung das erste Mal abgeschlossen wurde. Dies wurde nun umgestellt, sodass das Status-Change Observable jetzt auch diesen initialen Status-Change emittiert.

Core

Angular hat sein Komponentenmodell schon von Beginn an eng an die Komponenten aus der Web-Components-Spezifikation angelehnt. Dies drückt sich auch in der Möglichkeit aus, Style-Encapsulation von Angular-Komponenten zu ermöglichen. In Angular ist das Standard-Verhalten dabei eine Emulation des Shadow-DOM über HTML-Attribute, um Kompatibilität mit Browsern zu gewährleisten, die das Shadow-DOM nicht unterstützen. Es gab bisher jedoch auch zwei Möglichkeiten, Komponenten so einzustellen, dass das native Shadow-DOM genutzt wird. Einmal die ViewEncapsulation.Native, dies hat die Anbindung einer alten Spezifikation des Shadow-DOM-Standards ermöglicht. Diese Option war schon länger deprecated und wurde nun entfernt. Stattdessen gibt es nun nur noch ViewEncapsulation.ShadowDom, mit der die neuere Version der Spezifikation für Angular-Komponenten aktiviert werden kann.

Wenn Webanwendungen entwickelt werden, sollte – wie bei allen anderen Anwendungen auch – ein Thema nicht vernachlässigt werden: Testing. Tests erhöhen die Resistenz des Codes gegenüber Bugs, vor allem wenn Änderungen der wie jetzt Updates am Code durchgeführt werden. Da gerade Unit-Tests auch eine Form der Wiederverwendung des Codes darstellen, verbessern Tests darüber hinaus auch die Wartbarkeit des Codes. Aufgrund der asynchronen Eigenschaften des Browsers kommt es mitunter auch vor, dass man dies auch in den Unit-Tests berücksichtigen muss. Um dennoch einen korrekten Testablauf zu gewährleisten zu können, stellt Angular Tools für solche asynchronen Tests zur Verfügung. Zum Beispiel stellt Angular die async()-Funktion zur Verfügung, die sicherstellt, dass der nächste Testfall erst gestartet wird, wenn alle asynchronen Tasks im aktuellen Testfall abgeschlossen sind. Sowohl in IDEs als auch bei den Entwicklern hat der Name der Funktion aber häufig zu Verwechslungen mit dem ECMAScript async-Keyword geführt. Daher wurde die Funktion in der letzten Angular-Version umbenannt in waitForAsync() und die Verwendung als async() deprecated. Mit Angular 11 wurde der Name async() nun entfernt. Mit einer Migration werden alle noch vorhandenen async()-Aufrufe in waitForAsync()-Aufrufe umgewandelt.

Angular Material und CDK

Auch bei der Komponentenbibliothek Angular Material hat sich einiges getan. So existiert nun für jede Material-Komponente ein Test-Harness. Das verbessert die Qualität zwar nicht unmittelbar, erleichtert jedoch Tests durch die höhere Abstraktion.

Davon abgesehen stehen auch viele kleinere Features parat, die zum Beispiel die Theming-Fähigkeiten der Material-Komponenten mat-step, mat-vertical-stepper und mat-horizontal-stepper durch einen color-Input verbessern. Die MatSelect- und MatAutoComplete-Komponenten bieten nun die Möglichkeit, das generierte Overlay mit Hilfe der Eigenschaft overlayPanelClass um eigene CSS-Klassen anzureichern.

Im CDK wurden unter anderem die Fähigkeiten der Test-Harnesse ausgebaut. So unterstützen diese mit der parallel()-Funktion nun die Möglichkeit, mehrere Aktionen parallel gegen eine zu testende Komponente abzufeuern. Auch erlaubt die Funktion manualChangeDetection() eine präzisere Kontrolle des Testablaufs im Zusammenspiel mit Test-Harnesses.

Um die vielfältigen Einsatzmöglichkeiten des CDK noch besser zu unterstützen, wurde auch an diesen Komponenten Erweiterungen vorgenommen. Etwa kann das (Schliess-)Verhalten von ConnectedOverlay-Direktiven bei Nutzerinteraktionen konfiguriert werden (per disableClose-Input). Für Tabellen steht nun das Input-Propery fixedLayout zur Verfügung, das aktiviert werden kann und dafür sorgt, dass die Tabellenspalten die gleiche, feste Breite bekommen.

Eine hilfreiche Erweiterung im CDK sind die Coercion-Utilities, die auch im Anwendungscode hilfreich sein könnten. Diese nehmen einen bestimmten Wert entgegen und wandeln ihn in den geforderten Typen um. So gibt es etwa die Funktion coerceBooleanProperty(), die einen beliebigen Wert entgegennimmt und ihn in einen booleschen Wert umwandelt. Da die Coercion-Utilities zum Parsen von Komponenten-Inputs gedacht sind, wird z. B. der String false in den booleschen Wert false umgewandelt. Mit Angular 11 ist nun das Utility coerceStringArray() hinzugekommen, das einen Wert, oder auch ein Array von Werten, in ein Array von Strings verwandelt.

Die Zukunft von Angular

Angular ist eine inzwischen erwachsene Plattform. Das Versprechen, Entwickler bei der Umstellung von AngularJS zu unterstützen, wurde gehalten. Upgrades aktueller Versionen sind Dank des Angular CLI für Projekte jeder Größe gut zu bewerkstelligen. Die sehr gute Performance und das durchgängige, durchdachte Konzept machen Angular weiterhin zu dem Framework der Wahl für mittlere und große Anwendungen. Frameworks wie etwa Vue oder React liefern jedoch ein attraktives Modell für kleinste Anwendungen bzw. einzelne Komponenten. Dieser Bereich kann von Angular aktuell nicht optimal bedient werden.

Das wirkt sich nicht nur auf den Einsatzzweck aus, sondern auch auf die Lernkurve: Ist ein Hello-World bei React oder Vue schnell erstellt, gilt es bei Angular vielen Begriffen und Konzepte zu erlernen. Geht es darum, Entwicklerherzen zu gewinnen, spielen langfristige Wartbarkeit und Testbarkeit eine geringere Rolle, als das erhebende Gefühl von schnellem positiven Feedback.

Genau diesem Thema will sich das Angular-Team nunmehr auch widmen. Zukünftig soll möglich sein, Angular auch ohne das Modulsystem der NgModules einzusetzen. Dazu soll der Verzicht auf Zone.js optional möglich sein. Wichtig, wenn Angular Elements als reiner Lieferant von Komponenten statt einer zusammenhängenden Anwendung eingesetzt werden sollen.

Aus eigener Projekterfahrung mit Angular, Vue und React kann ich sagen, dass Angular sich auch für kleinere Anwendungen gut eignet. Da Anwendungen oftmals dazu tendieren, zu wachsen, lohnt sich langfristig die Investition in ein sauberes Projektsetup. Hier habe ich bei Angular dank des Opinionated-Ansatz, Dependency-Injection und sehr guter Testbarkeit deutlich bessere Erfahrungen gemacht, als mit Vue oder React. Wenn die Ziele der Roadmap erreicht werden, dann wird Angular nochmals flexibler einsetzbar.

Das nächste Angular Release als Minor Increment ist für den 13. Januar 2021 geplant. Am 10. Februar 2021 folgt dann Angular 12, sollte alles wie geplant laufen. Man darf gespannt sein, was bis dahin mit den Themen Webpack 5 und auch rund um Angular Elements passiert.

Links & Literatur
[1] https://angular.io/guide/roadmap
[2] https://update.angular.io/
[3] https://webpack.js.org/guides/hot-module-replacement/
[4] https://hub.docker.com/r/trion/ng-cli/
[5] https://hub.docker.com/r/trion/ng-cli-karma
[6] https://hub.docker.com/r/trion/ng-cli-e2e
[7] https://jaxenter.com/build-and-test-angular-apps-using-docker-132371.html

Geschrieben von
Karsten Sitterberg
Karsten Sitterberg
Karsten Sitterberg ist als freiberuflicher Entwickler, Trainer und Berater für Webtechnologien und Java tätig. Seine Schwerpunkte liegen im Bereich HTTP APIs, TypeScript und Angular. Karsten ist Physiker (MSc) und Oracle zertifizierter Java Developer. Regelmäßig berichtet er in Vorträgen und Artikeln über aktuelle Trends und Hintergründe zu Themen die für Entwickler und Architekten gleichermaßen relevant sind. In Münster hat er die Frontend Freunde als Meetup-Serie und die Java User Group mitgegründet.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: