Exkurs: Angular 8 → 18 für Rückkehrer:innen

Zielgruppe: Entwickler:in, die vor einigen Jahren mit Angular ~8 (2019) gearbeitet hat und jetzt an einem Projekt auf Angular 18 (Mai 2024) sitzt. Zwischen v8 und v18 liegen 10 Major-Releases. Dieses Dokument ordnet die Änderungen nach Konzept-Verschiebung, nicht nach Version, und sagt für jeden Punkt: was du bei v18 hast - und was erst danach kam (v19–v22), damit du dein nächstes Update planen kannst.

Verwandtes Dokument: Exkurs: Angular 8 → 22 - dasselbe für die aktuellste Version. Dieses Repo ist auf v22; es dient hier als „so ist es weitergegangen".

Wie dieser Exkurs aufgebaut ist

  1. Die drei großen Verschiebungen (v8 → v18) - wenn du nur einen Abschnitt liest, diesen.
  2. Was gleich geblieben ist - wie viel deines alten Wissens noch trägt.
  3. Übersetzungstabelle v8 → v18 - Idiom für Idiom.
  4. Die Verschiebungen im Detail - je Abschnitt: damals (v8) / bei v18 / warum / Ausblick seit v18 - plus eine „Selber machen"-Übung für dein v18-Projekt.
  5. Zeitleiste v8 → v18 (und ein Teaser „was danach kam").
  6. Der Wiedereinstieg - Reihenfolge, in der du dich einarbeitest.
  7. Was v18 schon hat / noch nicht hat - die scharfe Grenze.

Angular 18 in Zahlen: TypeScript >=5.4 <5.6, zone.js ~0.14, Node ^18.13 || >=20.9, RxJS ^7.4. Build-System: esbuild/Vite (seit v17 Default). Neues Paket @angular/build (v18).


1. Die drei großen Verschiebungen

a) Von NgModule zu Standalone

Damals (v8): Alles in @NgModule - declarations, imports, exports, entryComponents, SharedModule/CoreModule, Feature-Module mit RouterModule.forChild.

Bei v18: Standalone Components sind stabil (seit v15) und der Default aus ng new (seit v17). @Component({ standalone: true, imports: [...] }), bootstrapApplication(App, appConfig), ApplicationConfig mit provideX(). Wichtig für v18: standalone: true musst du noch hinschreiben - dass es der Default wird und wegfallen kann, kommt erst mit v19.

Ausblick seit v18: v19 macht standalone zum Default (eine Migrations-Schematic entfernt standalone: true überall); es gibt strictStandalone als tsconfig-Option. → In diesem Repo (v22) steht in keinem @Component mehr ein standalone.

b) Von „RxJS für alles" zu Signals

Damals (v8): Zustand = BehaviorSubject, Template = value$ | async, überall das takeUntil(destroy$) + ngOnDestroy-Ritual. Change Detection: bei jedem Event der ganze Baum.

Bei v18: Core-Signals sind stabil (seit v16): signal(), computed(), effect(). Im Template sig() statt | async. toSignal() / toObservable() als Brücken (stabil). takeUntilDestroyed() und DestroyRef (v16) ersetzen das destroy$-Muster. RxJS bleibt für echte Ströme (HTTP, Debounce, switchMap, WebSockets).

Ausblick seit v18: v19 bringt linkedSignal() und resource() (experimentell, asynchrone Signals); v20 stabilisiert effect/linkedSignal/toSignal weiter und entschärft die effect()-Timing-Regeln. → In diesem Repo: catalog.service.ts (Signal-Trio), auth.service.ts (Observable → Signal-Bridge).

c) Von Struktur-Direktiven zu eingebautem Control Flow

Damals (v8): *ngIf="x; else tpl", *ngFor="… trackBy: fn", <ng-template>, [ngSwitch] - mit CommonModule im Modul.

Bei v18: @if / @for / @switch und @defer sind stabil (seit v17). track ist Pflicht (direkt der Ausdruck, kein trackBy-Funktionsname). Es gibt eine Migrations-Schematic (ng generate @angular/core:control-flow), die deinen *ngIf/*ngFor-Bestand umschreibt. NgIf/NgFor funktionieren weiter, aber neue Templates brauchen kein CommonModule mehr.

Ausblick seit v18: v18 hat schon @let (lokale Template-Variable) noch nicht - das kam mit v18.1/v19. Ansonsten ist Control Flow bei v18 im Wesentlichen fertig. → Im Repo überall: app.component.html, catalog-search.component.html.

Unsichtbar, aber schon passiert: Der Compiler (View Engine → Ivy, ab v9/v13) und das Build-System (Webpack → esbuild/Vite, ab v17) sind bei v18 längst getauscht. Merkst du nur an Geschwindigkeit und besseren Fehlermeldungen.


2. Was gleich geblieben ist (Entwarnung)

Konzept v8 = v18
Komponenten, Templates, Services, Pipes, Direktiven dieselbe Idee, dieselben Decorators
Dependency Injection hierarchische Injektoren, providedIn: 'root', Provider-Rezepte - unverändert
RxJS weiterhin dabei und wichtig für Ströme
TypeScript dieselbe Sprache (v8: ~3.4, v18: ~5.4/5.5)
Angular CLI ng new / generate / build / serve / test / update
Angular Material / CDK dieselbe Bibliothek; bei v18 auf „M3" (Material 3) umgestellt, API weitgehend gleich
Router-Grundidee Routentabelle, <router-outlet>, Lazy Loading, Guards
Reactive Forms jetzt typisiert (seit v14)
zone.js bei v18 noch Default (zoneless ist experimentell) - dein ngZone-Wissen gilt weiter
Karma bei v18 noch der Standard-Test-Runner und nicht deprecated

3. Übersetzungstabelle v8 → v18

Aufgabe Angular 8 Angular 18
App starten platformBrowserDynamic().bootstrapModule(AppModule) bootstrapApplication(AppComponent, appConfig)
App-weite Provider @NgModule({ providers, imports }) ApplicationConfig mit provideRouter / provideHttpClient / …
Komponente „bekannt machen" in declarations eines Moduls @Component({ standalone: true, imports: [...] })
Abhängigkeit holen constructor(private http: HttpClient) {} private http = inject(HttpClient) (Konstruktor geht weiter)
lokaler Zustand x$ = new BehaviorSubject(0) + x$ | async x = signal(0) + x()
abgeleiteter Wert combineLatest([...]).pipe(map(...)) computed(() => …)
Subscription aufräumen takeUntil(this.destroy$) + ngOnDestroy takeUntilDestroyed() / DestroyRef
Bedingung im Template *ngIf="x; else tpl" @if (x) { } @else { }
Schleife im Template *ngFor="let i of items; trackBy: fn" @for (i of items(); track i.id) { } @empty { }
verzögert laden manuell / gar nicht @defer (on viewport) { }
Lazy-Route loadChildren: './x/x.module#XModule' loadComponent: () => import('...').then(m => m.X)
Route Guard @Injectable class G implements CanActivate const g: CanActivateFn = () => { … } (Funktion)
HTTP aktivieren imports: [HttpClientModule] provideHttpClient(withInterceptors([...])) - XHR ist noch Default, withFetch() opt-in
HTTP-Interceptor { provide: HTTP_INTERCEPTORS, useClass: …, multi: true } withInterceptors([fn]) (Funktion)
App-Init vor Bootstrap APP_INITIALIZER-Multi-Provider mit deps weiterhin APP_INITIALIZER (provideAppInitializer() kommt erst v19)
@Input() / @Output() @Input() x; @Output() y = new EventEmitter() x = input<T>(); y = output<T>() (Signal-basiert, seit v17.1–17.3)
Zwei-Wege-Bindung @Input() value; @Output() valueChange value = model<T>()
@ViewChild @ViewChild('r') r r = viewChild('r') (Signal, seit v17.2)
Change Detection zone.js, OnPush als bewusste Optimierung zone.js Default, OnPush opt-in; zoneless experimentell
Unit-Test-Runner Karma + Jasmine Karma + Jasmine (unverändert; Jest experimentell seit v16)
E2E-Test Protractor Protractor ist tot → Playwright / Cypress / WebdriverIO
SSR Angular Universal (renderModuleFactory) @angular/ssr, provideClientHydration(withEventReplay()) - Hydration + Event-Replay bei v18

4. Die Verschiebungen im Detail

NgModule → Standalone

Dependency Injection: Konstruktor → inject()

Reaktivität: RxJS → Signals

Templates: Control Flow

Routing

HTTP

Component-API: Decorators → Signal-Primitive

Bei v18 alle stabil verfügbar:

v8 v18
@Input() value: string value = input<string>() / input.required<string>()
@Output() change = new EventEmitter<T>() change = output<T>()
Banana-in-a-box (value + valueChange) value = model<T>()
@ViewChild('r') r / @ViewChildren r = viewChild('r') / viewChildren('r') (Signals)
@HostBinding / @HostListener host: { '[class.x]': 'y()', '(click)': 'onClick()' } im Decorator
ngOnChanges oft ein effect() auf den input()-Signal
ngAfterViewInit für DOM afterNextRender(() => …) / afterRender(...) (seit v17)

Change Detection

Build & Tooling

Testing

SSR / Hydration


5. Zeitleiste v8 → v18

Zum Einordnen. Maßgeblich im Zweifel: https://update.angular.dev und der CHANGELOG.

Version (≈ Datum) Für Rückkehrer:innen wichtig
8 (Mai 2019) dein Ausgangspunkt: NgModules, View Engine, Import-Funktion für loadChildren, differential loading
9 (Feb 2020) Ivy wird Default-Compiler
10 (Jun 2020) ng new --strict, CommonJS-Warnungen
11 (Nov 2020) schnellere Builds, TSLint → ESLint, HMR-Support
12 (Mai 2021) „Ivy Everywhere": View Engine deprecated, Prod-Build als Default, ?./?? in Templates
13 (Nov 2021) View Engine entfernt, IE11 raus, keine ComponentFactory mehr, Build-Cache
14 (Jun 2022) Standalone (Preview), typisierte Reactive Forms, inject(), provide*-Funktionen
15 (Nov 2022) Standalone stabil, funktionale Guards, NgOptimizedImage, Directive Composition, Material auf „MDC"
16 (Mai 2023) Signals (Preview), takeUntilDestroyed/DestroyRef, SSR-Hydration (Preview), esbuild-Dev-Server, required inputs, self-closing tags, Protractor aus ng new
17 (Nov 2023) @if/@for/@switch + @defer stabil, esbuild/Vite Application-Builder als Default, afterRender, neue Doku angular.dev, Standalone als ng new-Default, Hydration GA
18 (Mai 2024) ← du bist hier. Zoneless (experimentell), Material 3 stabil, SSR Event Replay, Redirects als Funktion, Paket @angular/build, Signal-Inputs/Outputs/Queries stabil

Und danach - was auf deinem Update-Weg wartet (Details: Exkurs 8 → 22 und angular-update-migration.md):


6. Der Wiedereinstieg

Dieses Repo ist auf v22, nicht v18 - aber die Struktur ist dieselbe, und die v18-Deltas stehen oben in Abschnitt 3/4. Lies die Dateien für die Form, denk die Deltas dazu:

  1. library-frontend/src/main.ts - bootstrapApplication statt bootstrapModule.
  2. src/app/app.config.ts - die provideX()-Liste. Delta v18: hier stünde noch { provide: APP_INITIALIZER, useFactory: …, deps: […], multi: true } statt provideAppInitializer(…).
  3. src/app/app.component.ts - Standalone-imports, inject(). Delta v18: mit standalone: true im Decorator.
  4. src/app/app.routes.ts - loadComponent, funktionale Guards.
  5. src/app/features/catalog/catalog.service.ts - Signal-Trio statt BehaviorSubject.
  6. src/app/features/catalog/catalog-search.component.html - @if/@for.
  7. src/app/core/auth/auth.interceptor.ts + role.guard.ts - funktionale Interceptors/Guards.

Danach: Frontend-Lernpfad (geführt, mit Übungen), angular-konzepte.md (Nachschlagewerk).


7. Was v18 schon hat - und was noch nicht

Hast du bei v18 (nutzt du es schon?): inject(), Standalone, funktionale Guards/Interceptors, @if/@for/@switch/@defer, Signals (signal/computed/effect/toSignal), input()/output()/model()/viewChild(), takeUntilDestroyed/DestroyRef, afterNextRender, NgOptimizedImage, withComponentInputBinding, esbuild/Vite-Build, Material 3, SSR-Hydration + Event-Replay, self-closing tags, host-Objekt.

Hast du bei v18 NOCH NICHT (kommt mit v19–v22): standalone als Default · provideAppInitializer() · @let im Template · linkedSignal() · resource() · incremental Hydration · stabiles Zoneless · fetch als HTTP-Default · OnPush als Default · TypeScript 6.0 · Vitest-Test-Runner.


Weiterführend

⌂ Cockpit