Endlich Efeu

Angular 12 ist da: Ivy für alle

Karsten Sitterberg

© Shutterstock / Runa0410

Nachdem am 14. April der Zeitpunkt für das Feature Freeze erfolgte, ist am 13. Mai Angular in Version 12 erschienen. Das Angular Framework hält damit den Rhythmus regelmäßiger Releases im Abstand von ca. 6 Monaten ein. Wir schauen uns an, was es Neues gibt.

Wie bei allen Major-Versionen gibt es Änderungen, die nicht abwärtskompatibel sind. So wird beispielsweise der Internet Explorer 11 zukünftig nicht mehr offiziell unterstützt. Diese Aufräumarbeiten sind wichtig, um Angular performant, schlank und zukunftsfähig zu halten.

Meine persönliche Empfehlung an jeden Angular-Entwickler lautet daher auch: Sich kontinuierlich mit den Neuerungen von Angular zu beschäftigen und Anwendungen stets auf dem neuesten Stand zu halten. Dank Schematics und der Angular-Update-Seite https://update.angular.io/ ist das in der Praxis auch erstaunlich wenig Aufwand. Das gilt vor allem, wenn es automatisierte Tests gibt, die es nach einem Update ermöglichen, schnell zu prüfen, dass es keine dadurch verursachten Probleme gibt.

Die größte Umstellung betrifft den alten Angular-Renderer ViewEngine: Seit Angular 9 wurde dieser schrittweise durch den neuen, performanteren Renderer Ivy ersetzt. Doch die vollständige Umstellung muss unter Berücksichtigung von Kompatibilität mit Libraries und Komponenten der Community und Drittanbietern erfolgen. Hier muss Angular mit Fingerspitzengefühl und entsprechend vorsichtig vorgehen.

Wie das gelingen kann und natürlich auch alle anderen Neuerungen von Angular und TypeScript werden im folgenden im Detail vorgestellt. Viel Spaß beim Lesen und Umsetzen!

TypeScript

In neuen Angular-Versionen wird regelmäßig auch die TypeScript-Version aktualisiert. Mit Angular 11.1.0 wurde die TypeScript-Version zum Beispiel auf 4.1 angehoben, in Angular 12 findet sich jetzt TypeScript 4.2.

Seit dem Update auf Angular 11 wurden somit 2 neue TypeScript-Versionen released, die wir uns daher im Folgenden beide näher anschauen wollen.

Wir beginnen mit den Template Literal Types. Damit ist es nun möglich, neue Typen mit einer Syntax zu erzeugen, die an die String-Interpolation angelehnt ist. Diese neue Syntax kann genutzt werden, um neue Typen aus bestehenden Typen abzuleiten. Dies können wir uns an folgendem Beispiel illustrieren. Als Ausgangstypen nutzen wir die String Literals VerticalAlignment und HorizontalAlignment.

type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";

Mit Hilfe der neuen Syntax kann man diese Typen nun zusammenfassen:

type Alignment = `${VerticalAlignment}-${HorizontalAlignment}`;
// Ergibt folgenden Typen:
//   | "top-left"    | "top-center"    | "top-right"
//   | "middle-left" | "middle-center" | "middle-right"
//   | "bottom-left" | "bottom-center" | "bottom-right"

Der Typ umfasst dabei alle Permutationen der möglichen Ausprägungen. Im Ergebnis wird dieser Typ von TypeScript dann wie jeder andere Typ behandelt, sodass auch Code-Completion und statische Code-Analyse möglich ist:

declare function setAlignment(value: Alignment): void;

setAlignment("top-left");   // Funktioniert!
setAlignment("top-middel"); // Error!
// Argument of type '"top-middel"' is not assignable to parameter of type '"top-left" | "top-center" | "top-right" | "middle-left" | "middle-center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right"'.

Die Template Literal Types ermöglichen weitere, mächtige Typkonstrukte. Hier gilt der Grundsatz „With Great Power comes Great Responsibility“: Solche Typkonstrukte sollten also eher vorsichtig verwendet werden. Eines dieser Konstrukte ist das Key Remapping in Mapped Types, also die Umbenennung bzw. Änderung von Keys von bestehenden Typen. Beispielsweise kann man sich einen neuen Typ Getter definieren, der eine bestehende Datenstruktur analysiert und passend zu deren Properties eine Datenstruktur mit entsprechenden Getter-Methoden anlegt. Im Folgenden wird etwa aus dem Interface Person ein neuer Objekt-Typ ReadonlyPerson erzeugt, der Getter basierend auf dem Property-Namen des Interfaces definiert. Aus dem Property name: string wird also eine Getter-Funktion getGame(), die einen String zurückgibt.

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};


interface Person {
    name: string;
    age: number;
}

type ReadonlyPerson = Getters<Person>;
//   Entspricht folgendem Typen:
//   type ReadonlyPerson = {
//       getName: () => string;
//       getAge: () => number;
//   }

Um den geschriebenen TypeScript-Code zu dokumentieren, wird in der Regel eine JSDoc-Kompatible Syntax verwendet. In dieser Syntax wird das Tag @see verwendet, um Querverweise auf andere Dokumente zu ermöglichen. Dieses Tag wird nun von verschiedenen Editoren besser unterstützt. So ist nun ein Sprung auch zu Sub-Definitionen möglich. Im folgenden Beispiel ist es sowohl möglich, zur Datei prefix als auch direkt zur Klasse C zu springen:

// Datei: prefix.ts
export class C {}

// Datei: main.ts
import * as prefix from "./prefix";
/**
* @see prefix.C
*/
function related() {}

Eine weitere Änderung betrifft Rest-Elemente in Tupel-Typen: Generell enthalten Tupel eine vorgegebene Menge von Elementen mit definierten Typen. Dabei konnten bisher auch Rest-Element-Typen verwendet werden, jedoch nur am Ende des Tupels. Dies geht nun auch an anderen Positionen, wie dem Anfang. Ein typischer Anwendungsfall ist, wenn man sich speziell für das Ende oder den Anfang einer Menge interessiert. In dem Fall können Rest-Elemente auch am Anfang oder in der Mitte definiert werden. Die einzige Bedingung ist jedoch, dass nicht mehrere Rest-Elemente aufeinander folgen können.

// Ein gewöhnliches Tupel mit ein oder zwei Strings
let c: [string, string?] = ["Hallo"];
c = ["Hallo", "Welt"];

// Ein Tupel mit einer Zahl am Ende und einem Rest-Element am Anfang,
// mit anderen Worten: beliebig viele Strings können vor der Zahl kommen.
let demo: [...string[], number];
demo = [3270];
demo = ["Hallo", 3270];
demo = ["Hallo", "Welt", "!", 3270];

// let StealersWheel: [...string[], number, ...string[]];
// Fehler: Ein Rest-Element darf nicht auf ein anderes folgen
// let StringsAndMaybeBoolean: [...string[], boolean?];
// Fehler: Optionale Elemente dürfen nicht nacht Rest-Elementen kommen

Damit verwandt ist die Möglichkeit, einen Wert bei der Zerlegung von Mengen als „nicht genutzt“ zu deklarieren. Dazu wird der Unterstrich als Variablenpräfix verwendet:

let [_first, second] = getValues();

Damit kann der TypeScript-Compiler darauf hingewiesen werden, dass die Variable nicht verwendet werden soll. Damit gibt er auch in dem Fall keinen Fehler aus, wenn die Compiler-Option noUnusedLocals eingeschaltet ist.

Neu ist ebenfalls eine Diagnosefunktion für den Build. Dazu kann im TypeScript Compiler tsc durch den Schalter --explainFiles eine erweiterte Ausgabe aktiviert werden, aus der ersichtlich wird, warum welche Datei im Build herangezogen wurde.

Breaking Changes in TypeScript

Bei der Prüfung auf nicht aufgerufene Funktionen kann der TypeScript Compiler nun auch Funktionen berücksichtigen, die innerhalb von logischen Ausdrücke wie && oder || verwendet werden. Im folgenden Beispiel kann so mit Hilfe der Compiler-Option --strictNullChecks ein Fehler ausgegeben werden, der darauf hinweist, dass die Funktion nicht aufgerufen wird, sondern lediglich referenziert wird.

function hasElementContent(element: Element): boolean {
    // ...
    return true;

}

function getVisibleElements(elements: Element[]): Element[] {
    return elements.filter(e => hasElementContent && e.clientHeight);

    //                          ~~~~~~~~~~~~~~~~~
    // This condition will always return true since the function is always defined.
    // Did you mean to call it instead.

}

Bisher war es erlaubt, TypeScript-Definitionsdateien (Erweiterung .d.ts), die lediglich ergänzende Typinformationen zu JavaScript-Quelldateien bereitstellen, per Import direkt zu referenzieren. Da eine solche Datei aber lediglich Typinformationen und keine eigentliche Implementierung enthält, führt Import einer Datei mit der Extension .d.ts mit TypeScript 4.2 nun zu einem Fehler.

Weiterhin führt die Kombination aus async und abstract an Methoden zu einem Fehler. Der Hintergrund ist, dass lediglich der Rückgabewert in der Signatur für den Aufrufer relevant ist. In derartigen Fällen kann das async einfach entfernt werden.

Beim Erzeugen von Promises sollte ab TypeScript 4.1 darauf geachtet werden, dass Promises keinen Default-Typ-Parameter mehr haben. Damit geht einher, dass von nun an innerhalb des resolve()-Callback immer ein Wert mit angegeben werden muss, außer der generische Typparameter ist void:

new Promise<number>((resolve) => {
    doSomethingAsync((value) => {
        doSomething();
        resolve(value);
        // `value` ist nicht mehr optional und
        // muss dem generischen Typen entsprechen, hier `number`
    });
});

Eine wichtige Neuerung ist mit TypeScript 4.1 für alle, die häufig den Spread-Operator verwenden, gekommen. Der Spread-Operator wird häufig eingesetzt, um z.B. neue Objekte zu erzeugen, wie etwa in den Reducern des @ngrx-Store üblich. Da der Spread-Operator Werte wie undefined oder null ignoriert, konnte es passieren, das bei der Verwendung des Spread-Operators komplexe zusammengesetzte Typen entstehen, wie in folgendem Beispiel dargestellt.

function copyAnimal(animal?: Animal) {
    return {
        ...animal,
        eyes: 2,
    };
}

// Rückgabetyp vorher:
// { eyes: number } | { eyes: number; name: string; age: number; }

Das Tracking dieser Typen vor allem bei tief verschachtelten Spreads ist sehr aufwändig. Aus Gründen der Einfachheit und (Compiler-)Performance wurden diese Typen daher nun so umgebaut, dass die Properties, die zu dem optionalen Objekt gehören, als optional markiert werden. Der neue zusammengesetzte Typ vereinfacht sich dadurch deutlich:

// Rückgabetyp ist nun:
// { eyes: number; name?: string; age?: number; }
// -> Einfacher/Performanter für das Typsystem

Angular CLI 12

Das Angular CLI bietet neben nützlichen Kommandos zur Erzeugung von Angular-Modulen, Komponenten und weiteren Bestandteilen auch eine Abstraktion über den aktuell mit Webpack implementierten Build. Durch die Abstraktion können in der Regel Modernisierungen am Build ohne Auswirkungen auf die einzelne Angular Projekte und Anwendungen umgesetzt werden.

Die erste Neuerung betrifft die unterstützte NodeJS-Version: Mit Angular CLI 12 wird nunmehr NodeJS in Version 14 benötigt, dem aktuellen LTS (Long Term Support) von Node.

Unter der Haube wird nun Webpack 5 eingesetzt, womit sich einige Vorteile ergeben:

Durch einen persistenten Cache in Webpack 5 soll der Build vor allem bei wiederholter Ausführung deutlich schneller werden. Die Bundles werden zudem im Ergebnis durch verbesserte Tree-Shaking-Algorithmen kleiner und die Codegenerierung wurde optimiert. Das Caching der Build-Ergebnisse im Browser wurde durch verbesserte Standardeinstellungen und geschickte Aufteilung der im Browser dauerhaft cache-baren Anteile verbessert.

Mit Webpack 5 ist auch die Grundlage geschaffen, um mit der Webpack Module Federation Architekturen auf Basis dynamischer Microfrontends zu entwerfen. An dieser Stelle ist ein Hinweis angemessen: Nur weil dies nun technisch möglich ist, sollte eine solche Architektur nicht leichtfertig eingeführt werden, ohne sich der damit verbundenen Konsequenzen und nicht immer direkt offensichtlichen Implikationen bewusst zu sein. Aus vielen Architekturworkshops kann ich festhalten, dass sich im speziellen Kontext des jeweiligen Kunden oft eine andere Architektur als vorteilhafter herauskristallisiert hat.

Mit Angular CLI 12 ist die Unterstützung für Microsoft Internet Explorer 11 als deprecated markiert worden. Ab Angular 13 soll der Internet Explorer 11 dann offiziell nicht mehr unterstützt werden.

Für Entwickler hilfreich ist die bereits mit Angular CLI 11.2 eingeführte detailliertere Logausgabe von ng add. Damit werden die tatsächlich durchgeführten Aktionen, wie zum Beispiel die ermittelte Version einer Abhängigkeit, leichter nachvollziehbar.

Das Angular-Team legt Wert darauf, dass mit Angular CLI gute, wartbare Anwendungen entstehen können. Um dies zu unterstützen, wird der strikte Modus des TypeScript-Compilers von nun an als Standard gesetzt, wenn Angular-Projekte neu generiert werden. Möchte man dieses Feature dennoch ausschalten, ist beim Erzeugen von neuen Projekten nun die Kommandozeilenoption --no-strict notwendig.

Für neue Projekte wird nicht mehr TSLint und Codelyzer bereitgestellt. Diese Werkzeuge, die zur kontinuierlichen Qualitätssicherung beitragen, können stattdessen durch ESLint ersetzt werden. ESLint muss jedoch manuell eingebunden werden. Bei all den Neuerungen ist dies ein echter Wermutstropfen, verstand Angular sich bisher stets als „Batteries-included“-Framework. Offiziell empfohlen wird nun die Verwendung des Paketes @angular-eslint.

Mit Angular 12 wurde auch der Protractor-Support deprecated. Neue Projekte werden folglich ohne E2E-Tests generiert. Stattdessen wird in enger Abstimmung mit der Community an Integrationen für Cypress, TestCafe und WebdriverIO gearbeitet.

Um in Zukunft die Komplexität der Konfiguration innerhalb der angular.json zu reduzieren, wurden die Default-Werte einiger Konfigurations-Optionen geändert. Der Default-Wert für die Build-Option optimization wurde zum Beispiel von false auf true geändert. Um die Builds mit den gewohnten Einstellungen laufen lassen zu können, wurde eine automatische Migration erstellt, die die Optionen passend einstellt.

Es wurde auch eine optionale Migration mit dem Namen production-by-default hinzugefügt, mit der der Production-Modus zum Default für die Anwendung wird. Sie kann im Anschluss an das Update mit folgendem Befehl ausgeführt werden:

ng update @angular/cli --migrate-only production-by-default

Bei neu generierten Anwendungen wird nun auch eine neue Konfiguration development für den Task build innerhalb der angular.json generiert.

Die neuen Voreinstellungen machen dabei viel Sinn: Für ng serve wird nun das development-Setup verwendet, bei ng build entsprechend die production-Einstellungen. Natürlich lassen sich diese Vorgaben auch bei Bedarf weiterhin überschreiben, dazu wird dann die zu verwendende Konfiguration wie gewohnt mit angegeben: ng build --configuration development

Um die Konfiguration insgesamt übersichtlicher zu gestalten wird in der angular.json-Konfiguration eine defaultConfiguration für jeden Task als Option eingeführt.

Die bisher oft verwendete Option --prod ist in diesem Zuge als deprecated markiert worden und wird in zukünftigen Angular CLI Versionen möglicherweise nicht mehr unterstützt. Als Ersatz steht der bereits erwähnte Schalter --configuration production zur Verfügung.

Bei der Übersetzung von TypeScript wurden auch im Hinblick auf den zukünftigen Wegfall der Unterstützung von Internet Explorer 11 die ECMAScript Level angepasst: So wird für E2E und Server nun es2019 und für Browser es2017 generiert.

Angular CLI Breaking Changes

Es gibt natürlich auch einige inkompatible Änderungen im Angular CLI, jedoch haben diese keine so wesentlichen Auswirkungen, dass diese ein typisches Update deutlich erschweren würden.

So wird von ng-packagr mindestens in Version 12.0.0-next benötigt, Karma in Version 6.0.0 und NodeJS in mindestens Version 12.13. Entsprechend wird die Verwendung von NodeJS 14 als aktuelle LTS empfohlen und ng update stellt Karma automatisch auf Version 6.3 um. ZoneJS wird ebenfalls aktualisiert und auf mindestens 0.11.4 gehoben.

Aufbauend auf den oben genannten Verbesserungen bei ng add wird in Zukunft eine Kommandozeilen-Abfrage an den Nutzer gestellt, bevor ein neues Paket installiert wird oder ein die ng-add-Migration des Paketes ausgeführt wird. Dies wurde eingeführt, um dem Nutzer die Möglichkeit zu geben, den Vorgang abzubrechen, falls er sich vertippt hat, oder der Befehl eine unerwünschte Paket/Versions-Kombination installieren würde. Die Abfrage kann auch unterbunden werden, indem auf der Kommandozeile die Option --skip-confirmation mitgegeben wird.

Zur Verbesserung der Performance ist die Workspace-Option inlineCritical als aktiv vorbelegt. Damit werden CSS-Regeln, die benötigt werden, um den First-Contentful-Paint zu generieren, extrahiert und als Inline-CSS ausgeliefert. Damit wird die subjektive Geschwindigkeit der gebauten Anwendung verbessert, da der Nutzer nicht so lange warten muss, bis die Anwendung erstmals sichtbar wird. In der Vergangenheit war dieser Schalter zwar verfügbar, aber deaktiviert.

Im Kontext von nachgeladenen Modulen (Lazy Loading) ist die Unterstützung entfernt worden, Module durch eine Zeichenkette so zu referenzieren, dass sie dynamisch nachgeladen werden. In der Vergangenheit war dies beispielsweise per Routing durch folgende Syntax möglich:

{
    //...
    loadChildren: './path/to/lazy.module#LazyModule'
}

Diese Syntax war bereits seit Angular 8 deprecated und wird mit Angular 12 nun vollständig entfernt. Daher müssen nun dynamische Imports verwendet werden:

{
    //...
    loadChildren: () => import('./path/to/lazy.module')
                            .then(mod => mod.LazyModule')

}

Durch den Wegfall der String-Syntax sind auch die folgenden Konfigurationsoptionen entfernt worden: discoverLazyRoutes, additionalLazyModules, additionalLazyModuleResources und contextElementDependencyConstructor. Eine entsprechende Migration bringt Angular CLI mit.

Im Rahmen der schrittweisen Umstellung von der alten View-Engine auf den neuen Ivy-Compiler wird das Webpack-Plug-in entfernt, mit dem Angular-Anwendungen bisher noch per View-Engine kompiliert werden konnten. Somit können Angular-Anwendungen ab Version 12 nur noch mit Ivy kompiliert werden. Das führt auch zu einigen sonstigen Erleichterungen im Angular-Umfeld. Beispielsweise das entryComponent-Property wurde nur im Kontext der View-Engine verwendet, um eine Komponente als Einstiegspunkt für ein Modul zu kennzeichnen. In Ivy ist dieses Property nicht mehr notwendig, daher wurde es nun auch aus der Component-Schematic entfernt. Auch die Plug-ins, um mit der View-Engine eine App-Shell zu generieren oder I18N-Informationen aus der App zu extrahieren wurden nun entfernt. Auch die zum i18n-Extraktor der View-Engine gehörenden Kommandozeilen-Properties wurden nun entfernt.

Angular CLI Docker Image

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

Folgende Images stehen zur Verfügung:

Details zur Verwendung finden sich auch in einem Artikel auf JAXenter.

Angular Framework

Ein in der Community viel gewünschtes Feature ist die Konfigurierbarkeit von Angular-HTTP-Interceptoren. Mit Angular 12 wurde diese Möglichkeit nun durch die Einführung eines HttpContext-Objektes geschaffen. Angular fügt dieses Objekt nun automatisch jedem HTTP-Request-Objekt hinzu, der Kontext ist also fest einem Request zugeordnet. Das gilt auch, wenn der Request durch mehrere Ebenen von Interceptoren gereicht wird. Der HttpContext kann dabei beliebige Daten in Form einer Map speichern. Dabei sind die Keys der Map spezielle, generisch typisierte HttpContextToken, sodass Daten typsicher in den HttpContext abgelegt und typsicher wieder daraus gelesen werden können.

In folgendem Beispiel wird das HttpContextToken DemoToken erzeugt, mit dem Datenstrukturen vom Typ {foo: string} im HTTP-Context abgelegt werden können. Das Token bekommt den Default-Wert {foo: 'bar'}. Im DemoService wird dann ein neuer HTTP-Context aufgebaut, dabei wird das DemoContext-Token verwendet, um dem Kontext das Objekt {foo: 'peng'} hinzuzufügen. Der so aufgebaute Kontext wird dann dem HTTP-Request hinzugefügt.

Im Interceptor wiederum wird über den Request auf den Kontext zugegriffen. Aus dem Kontext werden dann die zum Token gehörenden Daten mittels context.get() ausgelesen. Dabei ist zu beachten, dass auf jeden Fall ein Objekt zurückgegeben wird: Falls im Kontext ein Objekt gespeichert wurde, ist dieses vorhanden, falls im Kontext kein Objekt gespeichert wurde, wird immer der Default-Wert zurückgegeben.

// demo.context.ts
export const DemoContext = new HttpContextToken<{foo: string}>(() => ({foo: 'bar'}))


// demo.service.ts
demoRequest() : Observable<unknown> {
    const context = new HttpContext().set(DemoContext, {foo: 'peng'})
    return this.http.get<unknown>('/path/to/demo', {context})
}


// demo.interceptor.ts
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const context = request.context.get(DemoContext);
    if(context.foo === DemoContext.defaultValue().foo){
        // Kein Context provided - Dann wird Default genutzt
        // {foo:'bar'}
    } else {
        // Sonst wird der normale Kontext genutzt
        // {foo:'peng'}
    }
    return next.handle(request);
}

Das besondere am HTTP-Context ist, dass dieser ein rein Client-seitiges Konstrukt ist, also nicht zum Backend mitgeschickt wird. Sollen Informationen zum Backend geschickt werden, können wir dafür zum Beispiel das HttpParams-Objekt verwenden. Dieses wurde nun um eine Funktion appendAll() erweitert, mit der dem Objekt mehrere Parameter auf einmal hinzugefügt werden können:

const params = new HttpParams()
    .appendAll({foo: ['bar', 'baz'], myparam: 'karsten'});
// Resultat: 'foo=bar&foo=baz&myparam=karsten'

Wird ein HTTP-Request abgeschickt, so beinhaltet die daraufhin zurückkommende HTTP-Response in der Regel einen Status-Code. Angular 12 liefert nun ein HttpStatusCode-Enum mit, in dem die meisten HTTP-Status-Codes definiert sind. Es kann genutzt werden, um die Codes besser zuordnen zu können und so die Code-Lesbarkeit zu verbessern.

Für den Router wird mit Angular 12 eine offizielle API eingeführt, um eigene Implementierungen für das RouterOutlet hinzufügen und nutzen zu können. Mit dem RouterOutlet kann innerhalb einer Komponente bestimmt werden, wo genau die zu einer (Unter-)Route gehörende Komponente gerendert werden soll. Interessant ist dieses Feature für Erweiterungen des Angular-Frameworks, zum Beispiel das Ionic-Framework nutzt eine eigene Implementierung des RouterOutlet.

In einigen Sprachen, etwa dem Finnischen, gibt es eine Unterscheidung zwischen einem Wochentag im Kontext und einem alleinstehenden Wochentag in einer Datumsangabe. Diese Unterscheidung war in Angular, z.B. in der DatePipe, bisher so nicht korrekt möglich. Mit Angular 12 wurde nun das Kürzel c (bzw. c/cc/ccc/cccc/ccccc/cccccc) zur Verwendung als Datumsformat in der DatePipe hinzugefügt und das finnische Datumsformat korrigiert.

Falls zum Start-Zeitpunkt der App bestimmte Initialisierungen vorgenommen werden müssen, etwa Laden von Nutzerdaten oder anderen App-Konfigurationsdaten, kann dafür auch bisher schon das InjectionToken APP_INITIALIZER verwendet werden. Wenn der APP_INITIALIZER ein Promise zurück gibt, so wird der App-Start verzögert, bis dieses Promise resolved. Bisher war es nicht möglich, hierfür ein Observable zu verwenden. Mit Angular 12 ist dies nun ermöglicht worden:

export function initAppConfig(httpClient: HttpClient): () => Observable<any> {
    return () => httpClient.get('/api/config')
        .pipe(
            map(data => data.config)
        )
};

// ...
{

    provide: APP_INITIALIZER,
    useFactory: initAppConfig,
    deps: [HttpClient],
    multi: true
}

//...

Das Angular-Team arbeitet „unter der Haube“ immer auch an weiteren (Performance-)Optimierungen des Frameworks. Dafür wurden nun Performance-Tracing-Messpunkte unter anderem in den Language Service eingebaut. Außerdem wurde Angular so angepasst, dass es in Zukunft Kompilation durch den Closure Compiler, den Google für seine Anwendungen nutzt, besser unterstützt.

Um für eine bessere Code-Qualität in allen Angular-Projekten zu sorgen, wurde dem Language-Service eine Warnung hinzugefügt, die den Entwickler warnt, wenn keine der Compiler-Optionen strictTemplates oder fullTemplateTypeCheck gesetzt ist.

Angular Breaking Changes

Analog zu Angular CLI benötigt natürlich auch Angular in Zukunft aktuellere Versionen einiger Bibliotheken. Dazu gehört, dass mindestens Zone.js Version 0.11.4 und TypeScript 4.2 zum Einsatz kommen müssen, außerdem wird Node 10 nicht mehr unterstützt.

Wie bereits in Bezug auf Angular CLI erwähnt ist der Internet Explorer 11 mit Angular 12 deprecated worden. Man darf daher nicht mehr davon ausgehen, dass mit Angular 13 eine Unterstützung für den IE 11 vorhanden ist. Gerade in speziellen Umgebungen findet sich jedoch noch der IE 11, so dass die Community entsprechendes Feedback zur Entfernung von IE 11 Support gegeben hat. Damit ist der Wegfall mit Angular 13 noch nicht sicher.

Innerhalb einer Komponente kann der Decorator @ViewChildren() verwendet werden, um sich aus dem TypeScript-Code heraus Referenzen auf Elemente im Template zu beschaffen. Mit @ContentChildren()-Decorator geht das gleiche für in die Komponente hereingereichte Content-Elemente. Beiden Decorators ist gemein, dass sie eine sogenannte QueryList mit den passenden Elementen erzeugen. Diese QueryList stellt unter dem Property changes ein Observable zur Verfügung, das jedes mal gefeuert hat, wenn die QueryList neu berechnet wurde. So konnte es dazu kommen, dass auch dann Change-Events erzeugt wurden, wenn sich eigentlich gar nichts an der QueryList geändert hat. Um dieses Verhalten zu korrigieren und zu optimieren wurde bei beiden Decorators die Option emitDistinctChangesOnlyDefaultValue hinzugefügt, die aber aus Gründen der Abwärtskompatibilität zunächst standardmäßig auf false gestellt war. Mit Angular 12 wird diese Option nun standardmäßig auf “true” gestellt und gleichzeitig deprecated.

export class QueryCompWithStrictChangeEmitParent {
    @ViewChildren('myChild', {
        // Diese Option ist der neue Default
        emitDistinctChangesOnly: true,
    })
    myChildren!: QueryList<any>;

}

Sollen bei einem HTTP-Request HTTP-Query-Parameter mitgeschickt werden, so erreicht man dies beim Angular-HttpClient in der Regel, indem man dem Request ein HttpParams-Objekt mitgibt. Der Wert eines Parameters konnte bisher nur vom Typ string sein. Mit Angular 12 ist es nun auch möglich, boolean oder number als Typen zu nutzen. Dies ist dann ein Breaking Change, wenn in eigenem Code die HttpParams-Klasse erweitert wurde und muss auch nur dann entsprechend angepasst werden.

Wenn in Formularen die Attribute min und max auf <input type="number"> verwendet wurden, wurde dies bisher vom Angular-Forms-Module ignoriert, auch wenn eine formControl-, formControlName– oder ngModel-Direktive auf dem Element lag. Mit Angular 12 wird nun in diesem Fall die entsprechende Min/Max-Validierungslogik getriggert und der Validierungsstatus der Control entsprechend aktualisiert.

Eine weitere Änderung, die die Angular Forms betrifft, ist der Parameter emitEvent, der zu einigen FormGroup– und FormArray-Methoden hinzugefügt wurde. Dieser Breaking Change wirkt sich nur dann aus, wenn innerhalb der eigenen Anwendung die Klassen von FormGroup oder FormArray erweitert und eine der folgenden Methoden überschrieben wurde:

  • addControl
  • removeControl
  • setControl
  • push
  • insert
  • removeAt
  • setControl
  • clear

Falls eine oder mehrere dieser Methoden überschrieben wurden, muss mit Angular 12 die Signatur dieser Methoden entsprechend angepasst werden.

Angular Material und CDK

Angular Material stellt eine Implementierung des von Google geschaffenen Material Designs als Angular-Komponenten bereit. Angular CDK stellt Funktionalität bereit, die zur Entwicklung von Komponenten-Frameworks auf Basis von Angular hilfreich sind. Mit Angular-Material bzw. -CDK 12 gibt es neben einer Menge kleinerer, inkrementeller Verbesserungen, etwa an einigen Stellen optimierte Print Style-Sheets, vor allem die neue Theming-API als wichtigen Change.

Die Theming API von Material und CSS für Sass wurde überarbeitet, sodass nun die neue SCSS-@use-Syntax mit Material und CDK verwendet werden kann. Daraus ergibt sich, dass nun lediglich ein Einstiegspunkt zu @angular/material und @angular/cdk verwendet wird. Auch wird nun das Sass-Modul-System verwendet. In dem Zuge wurden auch Funktionen, Mixins und Variablen mit sprechenderen Namen versehen. Ein umfangreicher Guide zum neuen Theming-API wurde von Material-Entwicklern unter https://github.com/angular/components/blob/master/guides/theming.md zur Verfügung gestellt. Beim Update auf Material 12 wird über eine automatische Migration das Update auf das neue Theming-API durchgeführt.

Wichtig ist, dass als Abhängigkeit nicht mehr das nicht mehr gepflegte node-sass sondern sass in Projekten verwendet wird.

Der einzige Breaking Change bezieht sich auf die Accordion-Komponente des CDK (die in der Dokumentation auf material.angular.io noch nicht auftaucht). Bei der AccordionItem-Komponente wurden die Properties disabled und expanded vom any-Typen in strikte boolean-Properties verwandelt.

Ein Blick auf die Zukunft von Angular

Eine offizielle Integration von weiteren e2e-Testwerkzeugen, wie Cypress oder Testcafe, wird sicherlich vielen gefallen. Inzwischen sind diese Werkzeuge in einigen Bereichen Protractor sogar voraus, so dass der Wegfall von Protractor leicht zu verschmerzen sein wird. Zumindest, wenn die Integration der anderen Werkzeuge auf vergleichbarem Niveau erfolgt.

Konkret für Angular CLI 13 ist geplant, dass es nicht mehr möglich ist, die alte ViewEngine als Renderer zu verwenden. Damit ist dann die Transition zu Ivy – auch für Bibliotheken – vollständig abgeschlossen. Entsprechend werden Builds, die aktuell durch ngcc Aufrufe bei Libraries ohne Ivy verlangsamt werden, auch kontinuierlich schneller.

Interessant ist vor allem, was um Angular herum passiert: Ein reichhaltiges Ökosystem bringt Bibliotheken, Werkzeuge und Komponenten, auf die Entwickler zurückgreifen können. Sowohl in Bezug auf Performance als auch optimale Paketierung von Angular Anwendungen finden Entwicklungen statt, von denen direkt profitiert werden kann, ohne dass es umfangreicher Investitionen in das jeweilige Upgrade bedarf. Auch ist das Risiko, dass eine Anwendung durch ein Upgrade auf eine neue Major Version unerwartetes Verhalten oder gar Fehler aufweist praktisch nicht existent. Angular stellt somit wieder einmal mehr unter Beweis, dass es als Framework hervorragend geeignet ist, um für langlebige und wichtige Anwendungen als Basis eingesetzt zu werden.

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: