Blog-Post title image
This blog post has been published originally at: angular-buch.com.

Last update: 10.02.2020

Keywords

Am 6. Februar 2020 wurde bei Google in Kalifornien der "rote Knopf" gedrückt: Das lang erwartete neue Release ist da – die neue Major-Version Angular 9.0! Wir werden Ihnen in diesem Artikel die wichtigsten Neuerungen vorstellen.

Durch eine Reihe von Bugs und offene Features hatte sich das Release um einige Wochen verzögert – ursprünglich angestrebt war das Release im November. Der wohl wichtigste Punkt ist die Umstellung auf den neuen Renderer Ivy, der einige Features und vor allem Verbesserungen in der Performance mit sich bringt. Es gibt auch wieder kleinere Breaking Changes, doch das Update auf die neue Version ist undramatisch und geht leicht von der Hand.

Die offizielle Ankündigung zum neuen Release mit allen Features finden Sie im Angular Blog.

Inhalt

Update auf Angular 9

Das Update zur neuen Angular-Version ist in wenigen Schritten getan. Falls Ihr Projekt noch nicht in der letzten Version von Angular 8 vorliegt, sollten Sie zunächst das folgende Update erledigen:

ng update @angular/cli@8 @angular/core@8

Anschließend kann das Update auf Angular 9 erfolgen:

ng update @angular/cli @angular/core

Die Angular CLI führt automatisch alle nötigen Anpassungen am Code der Anwendung durch, sofern notwendig. Hier zeigt sich bereits die erste Neuerung: Beim ng update werden ab sofort ausführliche Informationen zu neuen Features ausgegeben, die Ihnen beim Update helfen. Außerdem verwendet die Angular CLI zur Durchführung des Updates unter der Haube jetzt immer die Version, auf die Sie updaten wollen.

Auf update.angular.io können Sie übrigens wie üblich alle Migrationsschritte im Detail nachvollziehen und die Migration vorbereiten.

Der neue Ivy-Renderer

Die wohl größte Neuerung in Angular 9.0 ist der neue Renderer und Compiler Ivy – also der Baustein, der die Templates mit Angular-Ausdrücken in JavaScript-Anweisungen umsetzt, die im Browser den DOM generieren. Der neue Ivy-Renderer löst die vorherige View Engine vollständig ab. Ivy konnte bereits mit Angular 8 als Opt-In genutzt werden, ist ab sofort aber standardmäßig aktiv.

Ivy soll vollständig abwärtskompatibel sein. Für die meisten Nutzer ändert sich also nichts, in wenigen Ausnahmefällen könnte es zu Problemen mit der Kompatibilität mit alten Anwendungen kommen.

Das Projekt Ivy hat das Angular-Team nun fast zwei Jahre beschäftigt – doch das Ergebnis kann sich sehen lassen. Ivy verspricht vor allem kleinere Bundles, was den Download beschleunigt und damit die generelle Performance der Anwendung deutlich erhöht. Ebenso bringt Ivy deutlich bessere Performance beim Kompilieren, verbessertes Tree Shaking, Template Checks und aufschlussreichere Fehlermeldungen mit sich. Ivy wurde sehr lange und intensiv getestet, um in den meisten Projekten eine nahtlose Umstellung zu ermöglichen. Sollten Sie bei der Migration zu Angular 9 dennoch unerwartet Probleme haben, so besteht noch immer die Möglichkeit, Ivy durch ein Opt-Out wieder zu deaktivieren:

// tsconfig.json
{
  "angularCompilerOptions": {
    "enableIvy": false
  }
}

Nachfolgend wollen wir noch etwas konkreter auf ein paar wichtige Features und Verbesserungen von Ivy eingehen.

Bundle Sizes

Das Entfernen von ungenutztem Code ("Tree Shaking") wurde mit dem Ivy-Compiler weiter verbessert. Von den Verbesserungen profitieren vor allem kleine und große Anwendungen.

Abbildung: Bundle Size Vergleich

Bei kleinen Anwendungen konnte die Paketgröße um etwa 30 %, bei großen Anwendungen um 25-40 % und bei mittleren Anwendungen nur minimal reduziert werden. (Quelle)

Da die Anwendung insgesamt kleiner ist, kann sie schneller heruntergeladen und ausgeführt werden. Dadurch startet die Anwendung auch deutlich schneller.

AOT per Default

Mit Ivy wird standardmäßig die Ahead-of-Time Compilation (AOT) eingesetzt – auch bei der Entwicklung. Das bedeutet, dass die Templates bereits zur Buildzeit in JavaScript umgesetzt werden und nicht erst zur Laufzeit im Browser.

Bisher wurde beim Ausführen von ng serve (Development Server) und auch bei der Ausführung der Tests die Just-in-Time Compilation (JIT) genutzt – die Anwendung wird also zur Laufzeit im Browser kompiliert. Das lag vor allem daran, dass JIT mit dem alten Renderer deutlich schneller arbeitet als AOT, wenn häufige Rebuilds zur Entwicklungszeit durchgeführt werden mussten. Für den Produktiv-Build wurde auch bisher schon die AOT-Kompilierung verwendet.

Durch die zwei verschiedenen Compiler-Modi konnte es vereinzelt zu unerwünschten Nebeneffekten kommen: Bei der Entwicklung lief die Anwendung reibungslos und alle Tests waren grün. Im Produktivmodus mit AOT tauchten dann plötzlich Fehler auf, die vorher nicht erkennbar waren.

Mit Ivy hat sich die Performance beim Kompilieren massiv verbessert, so dass der AOT-Modus nun standardmäßig immer aktiv sein kann. Somit kann man sichergehen, dass bei der Entwicklung und im Produktivbetrieb stets derselbe Modus eingesetzt wird und Fehler bereits frühzeitig erkannt werden können.

Change Detection

Wer sich einen Überblick über den Prozess der Change Detection mit Ivy machen will, sollte einen Blick auf die Visualierung von Alexey Zuev werfen.

Testing

Mit Ivy wird nicht nur die Anwendung performanter, sondern auch die Ausführung der Tests. Bis einschließlich Angular 8 wurden vor jedem Testschritt alle Komponenten neu kompiliert. Ab Angular 9 werden die Komponenten und Module bei der Verwendung von TestBed gecachet. Somit können die Tests erheblich schneller ausgeführt werden.

Server-Side Rendering und Pre-Rendering

Mit Version 9 wurde das Tooling für Server-Side Rendering mit Angular Universal verbessert.

Angular Universal bringt nun eigene Builder mit, die den Buildprozess erledigen. Es ist nicht mehr notwendig, die Webpack-Config für den Serverprozess oder das Pre-Rendering selbst zu pflegen.

Mithilfe von ng add können wir alles Nötige einrichten, um Angular Universal zu verwenden:

ng add @nguniversal/express-engine

Es wird automatisch die Konfiguration für den Universal Builder in die angular.json eingefügt. Die Datei server.ts enthält den Code für den Node.js-Server, der die Anwendung später ausliefert. Neu mit Angular 9 ist, dass für die Serverseite nur noch ein einziges Bundle erstellt wird, das die Angular-Anwendung und den Node.js-Server zusammen beinhaltet.

Für Pre-Rendering vereinfacht sich der Workflow enorm. Während wir bisher immer ein eigenes Skript erstellen mussten, um statische HTML-Seiten aus der Anwendung zu generieren, übernimmt das Angular-Tooling all das ab sofort automatisch. In der angular.json befindet sich dazu der folgende neue Abschnitt:

"prerender": {
  "builder": "@nguniversal/builders:prerender",
  "options": {
    "routes": [
      "/",
      "/books"
    ]
    // ...
  },
  // ...
}

Hier müssen wir lediglich die Routen eintragen, für die das Pre-Rendering ausgeführt werden soll. Der folgende Befehl startet dann den Build-Prozess:

ng run book-rating:prerender
npm run prerender # Alternativ: Kurzform als NPM-Skript

Die notwendigen Schritte erledigt die Angular CLI bzw. der Universal Builder nun für uns. Damit verringert sich die Fehleranfälligkeit, die es bisher mit selbst konfigurierten Skripten gab.

TestBed.inject<T>: Abhängigkeiten im Test anfordern

Bisher wurden Abhängigkeiten in Tests mittels Testbed.get<any>() angefordert. Mit Angular 9 wurde diese Methode als deprecated markiert – stattdessen sollte nun TestBed.inject<T> genutzt werden. Der Unterschied liegt hier in der Typsicherheit: Mit TestBed.inject() ist der Rückgabewert mittels Typinferenz korrekt typisiert, und wir können auf die Propertys der Klasse zugreifen. Das alte TestBed.get() lieferte hingegen stets any zurück.

// book-store.service.spec.ts
it('infers dependency types', () => {
  // `service` ist vom Typ `BookStoreService`
  const service = TestBed.inject(BookStoreService);
});

Grundsätzlich können beide Methoden synonym verwendet werden. Technisch handelt es sich dennoch um einen Breaking Change, deshalb war es nötig, die Änderung über eine neue Methode anzubieten.

i18n mit @angular/localize

Ein neues Paket mit dem Namen @angular/localize wurde mit Angular 9 eingeführt. Dieses Paket ist ab sofort die Grundlage für die Internationalisierung (i18n) in Angular.

Bei einem bestehenden Projekt mit Internationalisierung wird der Update-Prozess einige Änderungen an der angular.json durchführen. Beim Start der Applikation werden Sie anschließend folgende Nachricht in der Konsole sehen:

ERROR Error: Uncaught (in promise): Error: It looks like your application or one of its dependencies is using i18n. Angular 9 introduced a global $localize() function that needs to be loaded. Please run ng add @angular/localize from the Angular CLI.

Wie in der Nachricht bereits vorgeschlagen müssen wir folgenden Befehl ausführen:

ng add @angular/localize

Die Datei polyfills.ts wird um einen neuen Import ergänzt, der die Funktion $localize() verfügbar macht. Schon ist das Update prinzipiell durchgeführt. Die Syntax zur Übersetzung von Templates wurde nicht verändert. Weiterhin markieren wir die zu übersetzenden Stellen im HTML durch das i18n-Attribut:

<h1 i18n="@@HelloWorld">Hello World!!</h1>

Eine Übersetzung von Strings im TypeScript-Code war bislang nicht möglich. Dieses dringend benötigte Feature ist nun endlich verfügbar:

const test = $localize`@@HelloWorld`;

Wir sehen hier den Einsatz der neuen global verfügbaren Funktion $localize(). Diese Methode muss nicht importiert werden und kann als "Tagged Template" verwendet werden.

In dem obigen Beispiel fehlt noch die Standard-Übersetzung – im Template-Beispiel lautete diese Hello World!!. Das entsprechende Äquivalent können wir mit zwei zusätzlichen Doppelpunkten ausdrücken:

const test = $localize`:@@HelloWorld:Hello World!!`;

Eine weitere stark nachgefragte Funktionalität sind Übersetzungen zur Laufzeit. Damit kann Angular leider immer noch nicht (ganz) aufwarten. Angular unterstützt nun aber die Möglichkeit, zum Start der Applikation (also vor dem "Boostrapping") die notwendigen Übersetzungen bereitzustellen. Dadurch muss man die Applikation nicht mehr langwierig in diverse Sprachen kompilieren. Hierfür gibt es die neue Funktion loadTranslations():

// main.ts
import { loadTranslations } from '@angular/localize';

loadTranslations({
  HelloWorld: 'Hallo Welt!!'
});

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

Wir können auch einen Schritt weitergehen und die Übersetzungen aus einer JSON-Datei nachladen. Wichtig ist dabei nur, dass loadTranslations() vor bootstrapModule() ausgeführt werden muss. Hierfür stellt Angular derzeit keinen Helfer bereit. Diese Lücke füllt das Projekt locl vom ehemaligen Angular-Teammitglied Olivier Combe. Folgendes Beispiel demonstriert das Nachladen von Übersetzungen vor dem "Bootstrapping":

// main.ts
import { loadTranslations } from '@angular/localize';
import { getTranslations, ParsedTranslationBundle } from '@locl/core';

const messages = '/assets/i18n/messages.de.json';
getTranslations(messages).then((data: ParsedTranslationBundle) => {
  loadTranslations(data.translations);
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
});

Mehr zu der Methode getTranslations() erfahren Sie auf der GitHub-Seite des Projekts.

@ViewChild() und @ContentChild()

Mit Angular 8 gab es einen Breaking Change bei den Dekoratoren @ViewChild() und @ContentChild(): Es wurde das Flag static eingeführt, mit dem eine solche Query als statisch oder dynamisch markiert werden muss. Die Änderung war notwendig, weil sich das Standardverhalten der beiden Dekoratoren mit Angular 9 ändern sollte. In unserem Artikel zum Update auf Angular 8, haben wir die Thematik im Detail beschrieben.

Mit Angular 9 ist die Änderung final umgesetzt: Alle Querys sind nun grundsätzlich dynamisch, falls nicht anders angegeben. Es ist also nicht länger notwendig, das static-Flag für @ViewChild() und @ContentChild() explizit auf false zu setzen.

// Dynamische Query ab Angular 9:
@ViewChild('foo') foo: ElementRef;
@ContentChild('bar') bar: ElementRef;

// Statische Query ab Angular 8:
@ViewChild('foo', { static: true }) foo: ElementRef;
@ContentChild('bar', { static: true }) bar: ElementRef;

// Dynamische Query in Angular 8:
// Das Ergebnis ist im LifeCycle-Hook `ngAfterViewInit()` verfügbar
// `{ static: false }` musste explizit gesetzt werden
@ViewChild('foo', { static: false }) foo: ElementRef;
@ContentChild('bar', { static: false }) bar: ElementRef;

Weitere Neuigkeiten

Wir haben in diesem Artikel nur die wichtigsten Änderungen und Neuigkeiten erwähnt. Das neue Major-Release bringt dazu eine Vielzahl von Bugfixes, Optimierungen unter der Haube und kleinere Features, die für die meisten Entwicklerinnen und Entwickler zunächst nicht relevant sind.

Eine detaillierte Liste aller Änderungen finden Sie im offiziellen Changelog von Angular und der Angular CLI zum Release 9.0.

Verbesserte Typprüfung in Templates

Angular 9 bringt zwei neue Optionen zur Typprüfung mit:

  • fullTemplateTypeCheck: Wenn das Flag aktiviert ist, wird nicht nur der TypeScript-Code auf Typen geprüft, sondern auch die Expressions in den Templates (z. B. die Direktiven ngIf und ngFor). Diese Option ist in einem neuen Angular-Projekt standardmäßig aktiviert.
  • strictTemplates: Wird dieses Flag gesetzt, werden zusätzliche Typprüfungen für Templates aktiv.

Wir können die Optionen in der Datei tsconfig.json im Abschnitt angularCompilerOptions aktivieren:

{
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictTemplates": true
  }
}

Im Strict Mode wird beispielsweise geprüft, ob der übergebene Typ eines Property Bindings auch zu dem dazugehörigen @Input() passt. Mehr Informationen dazu finden Sie in der Angular-Dokumentation.

Schematics für Interceptoren

Neu hinzugekommen ist auch ein Generator zur Erstellung von HTTP-Interceptoren. Bisher musste man die Interceptor-Klasse per Hand erstellen, ab sofort unterstützt die Angular CLI uns dabei mit folgendem Befehl:

ng generate interceptor

providedIn für Services: any und platform

Für Services wird ab Angular 6.0.0 standardmäßig die Option providedIn: 'root' verwendet (wir haben dazu im Update-Artikel zu Angular 6 berichtet). Mit Angular 9 kommen neben root zwei neue Optionen für die Sichtbarkeit eines Providers hinzu: any und platform.

  • root: Die Anwendung erhält eine einzige Instanz des Services.
  • any: Jedes Modul der Anwendung erhält eine eigene Instanz des Services.
  • platform: Alle Anwendungen auf der Seite teilen sich dieselbe Instanz. Das ist vor allem im Kontext von Angular Elements interessant, wenn mehrere Anwendungen auf einer Seite "gebootstrappt" (gestartet) werden.

Optional Chaining mit TypeScript

Die von Angular verwendete Version von TypeScript wurde auf 3.7 aktualisiert. Damit ist auch ein neues interessantes Sprachfeature im Code verwendbar: Optional Chaining.

Bei der Arbeit mit verschachtelten Objekten musste man bisher jeden Schritt im Objektpfad einzeln auf Existenz prüfen, um Fehler zu vermeiden. Wollen wir beispielsweise die Thumbnail-URL eines Buchs ermitteln, müssen wir so vorgehen, wenn nicht sicher ist, ob das Thumbnail existiert:

const book = {
  title: '',
  thumbnail: { url: '', title: '' },
};

const url = book.thumbnail && book.thumbnail.url;

Mit Optional Chaining vereinfacht sich das Vorgehen. Wir verwenden den ?-Operator, um die Evaluierung des Ausdrucks abzubrechen, falls ein Teilstück des Objekts nicht existiert:

const url = book.thumbnail?.url;

Nullish Coalescing mit TypeScript

Ein weiteres neues Feature von TypeScript ist Nullish Coalescing. Damit kann in einem Ausdruck ein Fallback-Wert definiert werden, der eingesetzt wird, wenn der geprüfte Wert ungültig ist.

Für diese Semantik konnte bisher der ||-Operator verwendet werden. Ist der Wert von foo falsy (also null, undefined, 0, NaN, false oder leerer String), wird stattdessen der Wert default eingesetzt:

const value = foo || 'default';

Mit dem neuen Nullish Coalescing gelten 0, false, NaN oder leerer String als gültige Werte. Der Rückfall mit dem ??-Operator greift im Gegensatz zu || also ausschließlich bei den Werten null oder undefined.

const foo = 0;

// Prüfung auf falsy values (null, undefined, '', 0, false, NaN)
const value = foo || 'default';
// value = 'default'

// Zuweisung eines Standardwerts ohne Nullish Coalescing ('', 0, false und NaN sind erlaubt)
const value = foo !== null && foo !== undefined ? foo : 'default';
// value = 0

// Zuweisung eines Standardwerts mit Nullish Coalescing ('', 0, false und NaN sind erlaubt)
const value = foo ?? 'default';
// value = 0

Wir wünschen Ihnen viel Spaß mit Angular 9! Haben Sie Fragen zur neuen Version, zum Update oder zu Angular? Schreiben Sie uns!

Viel Spaß wünschen
Johannes, Danny und Ferdinand

Titelbild: Yosemite National Park, California, 2019