Angular Buch Team
 

Angular 14 ist da!

02.06.2022

Noch bevor die Sommer- und Urlaubszeit beginnt, wartet Angular mit tollen Neuigkeiten auf: Am 2. Juni 2022 erschien die neue Major-Version Angular 14! Während die letzten Hauptreleases vor allem interne Verbesserungen für das Tooling mitbrachten, hat Angular 14 einige spannende neue Features mit an Bord.

In diesem Blogpost fassen wir wie immer die wichtigsten Neuigkeiten zusammen. Im englischsprachigen Angular-Blog finden Sie außerdem die offizielle Mitteilung des Angular-Teams. Außerdem empfehlen wir Ihnen einen Blick in die Changelogs von Angular und der Angular CLI.

Projekt updaten

Um ein existierendes Projekt zu aktualisieren, nutzen Sie bitte den Angular Update Guide. Der Befehl ng update liefert außerdem Infos zu möglichen Updates direkt im Projekt.

# Projekt auf Angular 14 aktualisieren
ng update @angular/core@14 @angular/cli@14

Dadurch werden nicht nur die Pakete aktualisiert, sondern auch notwendige Migrationen im Code durchgeführt. Prüfen Sie danach am Besten mithilfe der Differenzansicht von Git die Änderungen.

Standalone Components

Damit eine Komponente genutzt werden kann, musste sie bisher immer in einem NgModule deklariert werden. Insbesondere bei wiederverwendbaren Komponenten führte das leicht zu unübersichtlichem Code. Das Konzept der NgModules mit allen Details (insbesondere Imports, Exports, Providers) erschwert außerdem den Einstieg in das Framework Angular.

Mit Angular 14 wurde dieses lang diskutierte Thema angegangen: Angular unterstützt nun Standalone Components.

Komponenten, Pipes und Direktiven müssen damit nicht mehr in einem Modul deklariert werden, sondern können alleinstehend verwendet werden. Damit eine Komponente genutzt werden kann, wird sie direkt am Ort der Verwendung importiert. Im folgenden Codebeispiel möchte die AppComponent die andere Komponente DashboardComponent in ihrem Template nutzen:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [DashboardComponent]
  // ...
})
export class AppComponent {}

Dadurch vereinfacht sich die Struktur der Anwendung, denn die Gruppierung in NgModules entfällt. NgModules und Standalone Components sind kompatibel, können also auch in Kombination genutzt werden.

Das neue Feature ist zunächst als Developer Preview verfügbar. Das bedeutet, dass die Schnittstelle vor dem finalen Release noch verändert werden kann.

Wir behandeln das Thema ausführlich in einem separaten Blogpost:
Standalone Components – neu ab Angular 14

Strikt typisierte Formulare

Reactive Forms sind ein mächtiger Ansatz, um komplexe Formulare mit Angular zu entwickeln. Die bisherige Umsetzung hatte allerdings Schwächen, denn die Modelle waren stets mit any typisiert:

const myForm = new FormGroup({ /* ... */ })
const value = myForm.value; // any ❌

Mit Angular 14 ändert sich dieses Verhalten: Die Formularmodelle für Reactive Forms sind jetzt strikt typisiert! Das Design für die neue Typisierung war nicht trivial, deshalb wurde der Prozess von einem öffentlichen RFC begleitet.

Wir behandeln das Thema ausführlich in einem separaten Blogpost:
Typisierte Reactive Forms – neu ab Angular 14

Beim Update auf Angular 14 werden bestehende Formulare automatisch auf eine untypisierte Variante migriert, z. B. wird FormControl ersetzt durch UntypedFormControl. Alte Formulare können so zunächst unverändert bleiben, und die Migration zum neuen Ansatz kann schrittweise durchgeführt werden.

Seitentitel setzen mit dem Router

Um den Titel der Seite mit Angular zu setzen, existiert schon seit einiger Zeit der Service Title. Der Nachteil an dieser Strategie ist, dass man das tatsächliche Setzen des Titels selbst in der Anwendung implementieren muss. Dazu muss entweder jede geroutete Komponente die passende Funktionalität mitbringen, oder wir müssen in einem Service selbst eine zentrale Logik dafür platzieren.

Seit Angular 14 bringt der Router eine passende Funktionalität mit, um den Titel der Seite automatisch zu setzen. Dazu können wir in den Routen das Property title definieren, und der Seitentitel wird beim Aktivieren der Route automatisch angepasst:

const routes: Routes = [
  {
    path: 'admin/create',
    component: BookCreateComponent,
    title: 'Buch erstellen'
  },
  {
    path: 'admin/edit/:isbn',
    component: BookEditComponent,
    title: 'Buch bearbeiten'
  }
];

Leider wird der Titel nur verändert, wenn eine Route einen Eintrag title hat. Betreten wir also eine Route ohne Titel, bleibt der zuletzt gesetzte Titel erhalten.

Um komplexere Anwendungsfälle zu lösen, können wir die Logik des Routers überschreiben. Dafür müssen wir eine eigene TitleStrategy implementieren. Die folgende Klasse setzt zum Beispiel den Titel auf den in der Route definierten Wert – oder auf den Default BookMonkey, wenn kein Titel gegeben ist:

@Injectable()
export class CustomTitleStrategy extends TitleStrategy {
  constructor(private title: Title) {
    super();
  }

  updateTitle(routerState: RouterStateSnapshot) {
    const title = this.buildTitle(routerState) ?? 'BookMonkey';
    this.title.setTitle(title);
  }
}

So können wir zum Beispiel den Titel auch nach einem bestimmten Muster zusammensetzen, sodass immer der Text BookMonkey erhalten ist:

updateTitle(routerState: RouterStateSnapshot) {
  const title = this.buildTitle(routerState);
  if (title) {
    this.title.setTitle(`${title} | BookMonkey`);
  } else {
    this.title.setTitle('BookMonkey')
  }
}

Die selbst definierte TitleStrategy muss mithilfe eines Providers bekannt gemacht werden:

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [
    { provide: TitleStrategy, useClass: CustomTitleStrategy }
  ]
})
export class AppRoutingModule { }

Für eine ausführlichere Auseinandersetzung empfehlen wir den folgenden Blogartikel von Brandon Roberts:
Setting Page Titles Natively With The Angular Router

Autovervollständigung mit der Angular CLI

Die Angular CLI bietet ein praktisches neues Feature an: eine integrierte automatische Vervollständigung für die Kommandozeile. Zur Einrichtung muss einmalig der Befehl ng completion ansgeführt werden. Er ergänzt die Konfiguration der Kommandozeile (z. B. .bashrc oder .zshrc), sodass für die Autovervollständigung automatisch im Hintergrund die Angular CLI aufgerufen wird.

Tippen wir also z. B. in der Kommandozeile den Befehl ng und drücken die Tab-Taste, erhalten wir automatisch passende Vorschläge:

➜  book-monkey git:(main) ng
add           -- Adds support for an external library to your project.
build         -- Compiles an Angular application or library into an output directory named dist/ at the given output
cache         -- Configure persistent disk cache and retrieve cache statistics.
completion    -- Set up Angular CLI autocompletion for your terminal.
config        -- Retrieves or sets Angular configuration values in the angular.json file for the workspace.
deploy        -- Invokes the deploy builder for a specified project or for the default project in the workspace.
doc           -- Opens the official Angular documentation (angular.io) in a browser, and searches for a given keyword
e2e           -- Builds and serves an Angular application, then runs end-to-end tests.
extract-i18n  -- Extracts i18n messages from source code.
generate      -- Generates and/or modifies files based on a schematic.
# ...

Dadurch, dass die Autovervollständigung stets die Angular CLI nutzt, bezieht sich die Vervollständigung auf den aktuellen Kontext. Zum Beispiel liefert die Vervollständigung für ng generate alle möglichen Schematics, die wir im Projekt installiert haben. Damit verbessert sich sich Developer Experience enorm.

Neue Funktion inject()

Um Abhängigkeiten per Dependency Injection anzufordern, wird üblicherweise der Konstruktor von Komponenten und Services verwendet:

@Component({ /* ... */ })
export class MyComponent {
  constructor(private service: BookStoreService) {}
}

Alternativ konnte auch bisher schon die Klasse Injector mit der Methode get() verwendet werden. Diese Klasse musste aber wiederum auch mittels Dependency Injection angefordert werden:

import { Injector } from '@angular/core';

@Component({ /* ... */ })
export class MyComponent {
  constructor(private injector: Injector) {
    const service = injector.get(BookStoreService);
  }
}

In Angular 14 kommt die neue Funktion inject() hinzu. Im Gegensatz zum Injector muss sie nicht erst über Dependency Injection angefordert werden, sondern kann komplett eigenständig verwendet werden.

import { inject } from '@angular/core';
 
export function getService() {
  return inject(BookStoreService);
}

@Component({ /* ... */ })
export class MyComponent {
  constructor() {
    const service = getService();
  }
}

Eine Einschränkung ist hierbei zu beachten: Der Aufruf von inject() muss immer indirekt über den Konstruktor erfolgen, also aus einem sogenannten Injection Context. Tut man das nicht, wird der folgende Fehler geworfen:

ERROR Error: NG0203: inject() must be called from an injection context

Durch die Unabhängigkeit von der Komponentenklasse ergeben sich viele spannende Möglichkeiten zur Komposition. Es gilt jedoch abzuwarten, wie sich die neuen Patterns etablieren werden. Wir empfehlen also, Abhängigkeiten zunächst weiterhin direkt über den Konstruktor anzufordern.

Für einige Ideen zur Funktion inject() möchten wir auf einen Blogartikel von Younes Jaaidi verweisen:
Angular Inject & Injection Functions - Patterns & Anti-Patterns

Sonstiges

Neben den großen neuen Features hat das neue Release viele kleine Verbesserungen und Bug Fixes an Bord. Eine Auswahl haben wir hier zusammengestellt:

  • TypeScript-Unterstützung: Angular unterstützt offiziell TypeScript in der Version 4.7, siehe Commit. Ältere Versionen als 4.6 werden hingegen nicht mehr supportet, siehe Commit.
  • Types für Router Events: Die Events des Routers (über Router.events) besitzen jetzt ein neues Property type. Um bestimmte Events zu filtern, war es bisher immer notwendig, mithilfe von instanceof nach der Klasse zu filtern. Das neue Property vereinfacht den Umgang, siehe Commit.
  • Schematics Default Collection: In der angular.json konnte mit dem Property defaultCollection die Standard-Kollektion definiert werden, die für die Schematics (z. B. ng generate) genutzt wird. Bei der Installation von Drittbibliotheken wie @ngrx/schematics konnte diese Einstellung gesetzt werden. Dieses Property wurde nun ersetzt durch schematicCollections. Hier kann ein Array mit mehreren Collections definiert werden, die in der angegebenen Reihenfolge durchsucht werden. Damit entfällt bei wiederholten Befehlen die Notwendigkeit, die Collection manuell anzugeben. Siehe Commit.
  • defaultProject: Die Einstellung defaultProject in der angular.json ist deprecated. Stattdessen wird das aktuelle Projekt jetzt anhand des Arbeitsverzeichnisses ermittelt, siehe Commit.
  • Protected Propertys: In Komponenten ist es jetzt auch möglich, Propertys und Methoden im Template zu binden, die als protected markiert sind. Bisher funktionierte das nur für public.
  • Angular DevTools für Firefox: Die offizielle Browser-Extension zum Debuggen von Angular-Anwendungen ist jetzt auch für Firefox verfügbar. Der Download ist über das offizielle Verzeichnis von Mozilla möglich.


Die Roadmap für die zukünftige Entwicklung von Angular wird regelmäßig in der Dokumentation veröffentlicht: https://angular.io/guide/roadmap.

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

Viel Spaß wünschen Ferdinand, Danny und Johannes


Titelbild: Yosemite National Park, California, 2019. Foto von Ferdinand Malcher

Zurück
Suggestions? Feedback? Bugs? Please fork/edit this page on Github.