Seit Angular 19 ist jede Komponente, Direktive und Pipe standardmäßig Standalone — du schreibst kein standalone: true mehr und brauchst kein NgModule mehr. Jede Komponente deklariert ihre Abhängigkeiten selbst in imports, und die Anwendung startet mit einer Funktion, nicht mit einem Modul.
Wie die Anwendung startet
Drei Dateien: main.ts startet die Anwendung, app.config.ts enthält die globalen Provider, und app.ts ist die Root-Komponente. Um darauf eine kleine App zu bauen, folge Deine erste App, Schritt für Schritt.
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));- v21: Neue Apps sind zoneless — kein
zone.jsim Bundle, kein spezieller Provider. - v21:
HttpClientist in root verfügbar, auch ohneprovideHttpClient(). Du rufst es nur noch auf, um zu konfigurieren (Interceptors, XHR). - v22: Komponenten sind standardmäßig
OnPush. Siehe Change Detection. - v22: TypeScript 6 ist Pflicht.
| Angular-Version | 22.2.2 |
| Zone.js geladen? | nein — Zoneless-Anwendung |
| Ist die Root-Komponente Standalone? | ja |
| In der Anwendung geschriebene NgModules | 0 |
| Konfigurierte Routen (oberste Ebene) | 4 |
| Lazy geladene Seiten (je ein Chunk) | 75 |
imports: Jede Komponente deklariert ihre Abhängigkeiten
Das Template einer Komponente sieht nur, was in ihren imports steht: andere Komponenten, Direktiven, Pipes. Dadurch sind Abhängigkeiten explizit, das Tree-Shaking funktioniert besser und Lazy Loading wird trivial.
import { Component, input } from '@angular/core';
import { CurrencyPipe, DatePipe } from '@angular/common';
import { RouterLink } from '@angular/router';
import { Avatar } from '@shared/ui/avatar';
@Component({
selector: 'app-user-card',
// Kein CommonModule: @if/@for sind Teil der Sprache, Pipes importierst du einzeln
imports: [RouterLink, DatePipe, CurrencyPipe, Avatar],
template: `
<app-avatar [name]="user().name" />
<a [routerLink]="['/users', user().id]">{{ user().name }}</a>
<small>Mitglied seit {{ user().joinedAt | date: 'MMM yyyy' }}</small>
`,
})
export class UserCard {
readonly user = input.required<User>();
}CommonModule wurde für *ngIf, *ngFor, ngClass und Pipes importiert. Heute ist der Control Flow Teil der Sprache, [class] ersetzt ngClass, und Pipes importierst du einzeln (DatePipe, CurrencyPipe). Das Ergebnis: kurze und präzise imports.
Früher und heute
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { authInterceptor } from './core/auth/auth-interceptor';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(), // globale Fehler → ErrorHandler
provideRouter(routes, withComponentInputBinding()), // Router + Parameter als input()
provideHttpClient(withInterceptors([authInterceptor])), // nur, wenn du Interceptors willst
// Nicht mehr nötig: provideZonelessChangeDetection() (Standard seit v21)
// Nicht mehr nötig: ein einfaches provideHttpClient() (HttpClient ist seit v21 in root)
],
};// Früher (Angular ≤ 14): alles lief über ein NgModule
@NgModule({
declarations: [AppComponent, HeaderComponent, UserCardComponent],
imports: [BrowserModule, HttpClientModule, RouterModule.forRoot(routes)],
providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
bootstrap: [AppComponent],
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);Aber ich habe Bibliotheken mit NgModules…
Kein Problem: Ein NgModule kann direkt in die imports einer Standalone-Komponente. Für Provider auf Anwendungsebene aus alten Modulen gibt es importProvidersFrom().
// Eine alte Bibliothek, die nur ein NgModule exportiert? Importiere es direkt.
@Component({
imports: [LegacyChartsModule], // ein NgModule in den imports einer Standalone-Komponente — erlaubt
template: `<legacy-chart [data]="data()" />`,
})
export class Report {}
// Ein Modul, das Provider auf Anwendungsebene hinzufügen muss:
export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(LegacyAnalyticsModule.forRoot({ key: '...' }))],
};Ein bestehendes Projekt migrieren
# Automatische Migration zu Standalone (du führst sie in 3 Schritten aus, wie das Schematic es verlangt)
ng generate @angular/core:standalone
# Weitere nützliche Migrationen für ein altes Projekt (alle in @angular/core v22 vorhanden)
ng generate @angular/core:control-flow # *ngIf/*ngFor/ngSwitch → @if/@for/@switch
ng generate @angular/core:common-to-standalone # CommonModule → einzelne Imports
ng generate @angular/core:inject # constructor DI → inject()
ng generate @angular/core:signals # @Input/@Output/@ViewChild → input()/output()/viewChild()
ng generate @angular/core:ngclass-to-class # [ngClass] → [class]
ng generate @angular/core:ngstyle-to-style # [ngStyle] → [style]
ng generate @angular/core:service # @Injectable({providedIn:'root'}) → @Service() (v22)
ng generate @angular/core:route-lazy-loading # component: X → loadComponent: () => import(...)
ng generate @angular/core:self-closing-tag # <app-x></app-x> → <app-x />
ng generate @angular/core:cleanup-unused-importsWann ja, wann nein
- Jeder neue Code: Komponenten, Direktiven, Pipes.
- Lazy Loading auf Komponentenebene (
loadComponent) oder für eine Gruppe von Routen (loadChildrenmit einer Routen-Datei). - Konfiguration über
provideX()-Funktionen statt überModule.forRoot().
- Du pflegst eine öffentliche Bibliothek, die sehr alte Angular-Versionen unterstützen muss.
- Du hast eine große, noch nicht migrierte Codebasis — du migrierst schrittweise, die beiden Modelle existieren nebeneinander.
Häufige Stolperfallen
- „X is not a known element“: Du hast vergessen, die Komponente zu
importshinzuzufügen. Jede Komponente importiert ihre Kinder selbst. - Unbekannte Pipe:
dategibt es nicht mehr „gratis“ — importiereDatePipe. standalone: falsetaucht nur in altem oder migriertem Code auf. Schreib es nicht in neuen Code.- Provider, die du in
app.config.tseinträgst, sind global. Für ein einzelnes Feature hängst du sie an die Route (siehe Providers).