Titelbild

Blogartikel | Angular 17 – Die Angular-Renaissance

Von am 22.11.2023

Am 14. September 2016 wurde die erste Version von Angular, der Neuimplementierung von AngularJS, veröffentlicht. Seitdem wird in der Regel alle sechs Monate eine neue Major Version des Webframeworks veröffentlicht. So auch im November 2023 mit Version 17. In den letzten Monaten und Jahren gab es einige fundamentale Änderungen am Framework die es einerseits simplifizieren und andererseits auch schneller machen. Unter anderem wurden Standalone Components, Signals und eine neue Control Flow Syntax eingeführt. Dieser Blogartikel soll die größten Änderungen kurz zusammenfassen. Er weist aber auch auf viele kleinere Änderungen hin. Quelle dafür ist der Angular Blog auf Medium [1-3].

Neuerungen

Neue Doku und neues Markendesign

Mit dem Release von Angular 17 wurde ein neues Logodesign sowie eine neue Dokumentationsseite vorgestellt. Anstelle von angular.io findet sich die Dokumentation zukünftig unter angular.dev. Die neue Dokumentation zielt vor allem auf eine verbesserte Nutzererfahrung durch die Verwendung interaktiver Codeausschnitte, die durch Webcontainer bereitgestellt werden, ab.

Alleinstehende Komponenten

Durch die Einführung der „Standalone Components“ lassen sich NgModule vermeiden. Der Programmcode wird dadurch im Allgemeinen übersichtlicher, effizienter, einfacher wartbar und testbarer. Die unabhängige Entwicklung von Komponenten und Tree-Shaking wird ermöglicht. Standardmäßig wird ab Angular 17 ein Projekt in diesem Format angelegt. Dennoch lassen sich das ngModule und das Standalone Modell miteinander mischen.

import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
})
export default class AppComponent implements OnInit {
  private router = inject(Router);
  private route = inject(ActivatedRoute);

  public title = 'Hello World!';

  ngOnInit() {}
}

Für diejenigen die es noch nicht wussten: Anstelle der Injizierung im Konstruktor kann seit Angular 14 auch einfach die inject() Methode verwendet werden. Die Wertzuweisung ist außerdem bei der Variablendeklaration sowieso besser aufgehoben da man sich hierdurch die Angabe eines Variable-Typs erspart. So braucht man auch keinen Konstruktor mehr. Außerdem ist neu das die StyleUrl nun als String übergeben werden kann und nicht mehr in einem Array gekapselt werden muss.

Der Startvorgang der Applikation ändert sich auch etwas:

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(BrowserModule),
    provideRouter(APP_ROUTES, withComponentInputBinding()),
  ],
}).catch((err) => console.error(err));

In der App-Pfaddefinition lassen sich alleinstehende Komponenten auch ohne viel Code zu schreiben verzögert laden.

import { Routes } from '@angular/router';

export const APP_ROUTES: Routes = [
  {
    path: 'home',
    loadComponent: () => import('./app.component'),
  },
];

Davor hätte das in etwa so ausgesehen:

loadComponent: () => import(‘./app.component”).then((mod) => mod.HomeComponent),

Signals vs RxJS

Seit jeher verwendet Angular Zone.js um Änderungen erkennen zu können und auf diese zu reagieren. Dabei wird der gesamte Komponentenbaum auf Änderungen überprüft. Nicht sehr effizient. Deshalb wurde sich ein neuer Ansatz überlegt, um Reaktivität in einer granularen Art und Weise lösen zu können. Dabei hat man sich an anderen Frameworks orientiert und Signals implementiert. Ziel ist es Zone.js in zukünftigen Angular Version komplett Optional anzubieten. Bisher wurde in Angular RxJS für den Umgang mit asynchronen Verhalten verwendet. Signals bieten für viele Situationen eine Alternative bzw. genauer gesagt für das BehaviourSubject.

Ein Signal hält immer einen Wert, der gelesen werden kann. Ändert sich dieser Wert werden Abonnenten darüber informiert. Signals lassen sich darüber hinaus in Observables umwandeln bzw. aus Observablen herleiten. Das „@angular/core/rxjs-interop“ package bietet dazu die Methoden:

  • toSignal()
  • toObservable()

Signale lassen sich direkt modifizieren und abonnieren. Die API bietet dazu vier Methoden. 

  • set … setzt einen neuen Wert des Signals
  • update … aktualisiert den Wert des Signals basierend auf dem aktuellen Wert
  • mutate … aktualisiert den Wert des Signals mit einer direkten Änderung
  • asReadonly … gibt das Signal als Readonly-Signal zurück, die oben genannten Methoden stehen dann nicht zur Verfügung.

Weiters lassen sich Signale aufgrund von einem oder mehreren Eingabe Signalen ableiten, indem eine angegebene Funktion angewendet wird. Das Signal wird jedes Mal neu berechnet, wenn sich eines der Eingabe-Signale ändert. computed() ist die dazugehörige Methode.

Last but not least gibt es eine effect() Methode. Ändert sich eines der Eingabesignale wird die angegebene Funktion ausgeführt.

Dazu nun ein Beispiel vom Angular Blog auf Medium.

import { Component, signal, computed, effect } from "@angular/core";

@Component({
  selector: 'my-app',
  standalone: true,
  template: `
    {{ fullName() }} <button (click)="setName('John')">Click</button>
  `,
})
export class App {
  firstName = signal('Jane');
  lastName = signal('Doe');
  fullName = computed(() => `${this.firstName()} ${this.lastName()}`);

  constructor() {
    effect(() => console.log('Name changed:', this.fullName()));
  }

  setName(newName: string) {
    this.firstName.set(newName);
  }
}

Das obige Snippet erzeugt einen berechneten Wert fullName, der von den Signalen firstName und lastName abhängt. Wir deklarieren auch einen Effekt, der jedes Mal ausgeführt wird, wenn wir den Wert eines der Signale, die er liest, ändern. Wenn wir den Wert von firstName auf “John” setzen, wird der Browser in der Konsole protokollieren:

“Name geändert: John Doe”

Das bereits erwähnte @angular/core/rxjs-interop Paket hat zudem eine weitere nützliche Funktion namens takeUntilDestroyed(), welche die Lebensdauer des Abonnements an die der Komponente koppelt. Es ist also das Äquivalent zu einem Deabonnieren im ngOnDestroy Life Cycle Hook. Beispiel:  

route = inject(ActivatedRoute)
destroyRef = inject(DestroyRef)

ngOnInit() {
   this.route.paramMap
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(res => {/* some operation */})
}

Decorator basierte Kontrollfluss-Syntax

Dieses Update bezieht sich vor allem auf die Verwendung von *ngIf, *ngSwitch, und *ngFor. Ab Angular 17 können Decorators für denselben Zweck verwendet werden. Die Transformation der neuen Syntax passiert zur Kompilierzeit und nutzt einen neuen Diffing-Algorithmus. Dadurch ist diese neue Form auch bedeutend schneller.

Zuvor machte man so in etwa ein *ngIf:

<div *ngIf="loggedIn; else anonymousUser">
  The user is logged in
</div>
<ng-template #anonymousUser>
  The user is not logged in
</ng-template>

Jetzt kann man das so schreiben:

@if (loggedIn) {
  The user is logged in
} @else {
  The user is not logged in
}

Beispiel für ein switch-case:

@switch (accessLevel) {
  @case ('admin') { <admin-dashboard/> }
  @case ('moderator') { <moderator-dashboard/> }
  @default { <user-dashboard/> }
}

Beispiel für eine for Schleife:

@for (user of users; track user.id) {
  {{ user.name }}
} @empty {
  Empty list of users
}

Zusätzlich gibt es ein ganz neues Feature. Mit dem @defer Decorator lassen sich einzelne Teile der Seite verzögert nachladen. Dazu gibt es ein paar Trigger (on idle/immediate/timer/viewport/interaction/hover and when<expr>) Beispiel:

@defer (on interaction)) {
  <comment-list/>
} @loading {
  Loading…
} @error {
  Loading failed :(
} @placeholder {
  <img src="comments-placeholder.png">
}

Hier wieder nur eine Randnotiz. Das angeführte <comment-list/> nutzt hier self-closing tags. Früher hätte <comment-list></comment-list> geschrieben werden müssen.

Vite/Esbuild anstelle von Webpack

Standardmäßig verwendet die Angular CLI nun Vite + Esbuild für die Softwareerstellung. Dies führt zu einer enormen Leistungssteigerung bei den Befehlen ng serve und ng build.

Server side rendering and hydration

Hydration wurde mit Angular 16 eingeführt. Bei der Erstellung einer neuen Applikation über die CLI wird man nun gefragt, ob man server-side rendering verwenden möchte. Bestätigt man dies wird ein Serverendpunkt erzeugt und die Anwendung ist startfähig.

Passing router data as component inputs

URL-Parameter können über einen Input abgegriffen werden.

  @Input(‘id’) locID = ”;

Dazu muss nur beim Applikationsstart dem Router die entsprechende Konfiguration mitgegeben werden.

  provideRouter(APP_ROUTES, withComponentInputBinding()),

Release von Analog.js v0.2.0

Auch wenn Analog.js nicht vom Angular Team, sondern nur ein OpenSource Projekt ist, so ist es dennoch interressant. Analog.js ist wie NextJS und SvelteKit ein Meta-Framework, das auf Angular aufbaut. Es bietet unter anderem File basiertes Routing, Markdown als Zielpfad, api server Routen, Server Side Rendering SSR und static side generation SSG. Erstellt wurde das Projekt von einem Instandhalter des NgRx Projektes, welches reaktives State Management für Angular Apps bietet.

Weg von Karma hin zu Jest und Web Test Runner

Ein weiterer unangesprochener Punkt ist das Testen der Applikation. Das derzeit verwendete Karma Paket ist veraltet und sollte nicht mehr verwendet werden. Deshalb wird auf Jest und Web Test Runner umgestellt. Jest ist bereits als experimentelles Feature in Angular implementiert. Der Fokus bei den nächsten Angular Updates wird sich insbesondere auf diese Umstellung richten.

Zusammenfassung und Abschluss

Bei Angular hat sich viel getan. Das muss es auch. Beim State of JavaScript Report 2022 [4] wird Angular von 49% der Nutzer verwendet und liegt damit prinzipiell auf dem zweiten Platz. Das Interesse liegt hingegen nur bei 20%. React weist hier etwa ein Verhältnis von 82% zu 47% auf. Will Angular relevant bleiben muss es einfacher und schneller werden, was diese Updates in der Tat liefern.


[1] M. Gechev, „Angular v15 is now available!“, Medium. Zugegriffen: 13. November 2023. [Online]. Verfügbar unter: https://blog.angular.io/angular-v15-is-now-available-df7be7f2f4c8

[2] M. Gechev, „Angular v16 is here!“, Medium. Zugegriffen: 13. November 2023. [Online]. Verfügbar unter: https://blog.angular.io/angular-v16-is-here-4d7a28ec680d

[3] M. Gechev, „Introducing Angular v17“, Medium. Zugegriffen: 13. November 2023. [Online]. Verfügbar unter: https://blog.angular.io/introducing-angular-v17-4d7033312e4b

[4] „State of JavaScript 2022: Front-end Frameworks“. Zugegriffen: 15. November 2023. [Online]. Verfügbar unter: https://2022.stateofjs.com/en-US/libraries/front-end-frameworks/

The comments are closed.