Dependency Injection

Aus Zweites Gehirn, dem persönlichen Wiki
Dependency Injection
TypKonzept
QuellenQuelle - Angular Mastery Workshop
Quelle - Recherche - Angular heute 2026
Erstellt2026-09-26
Aktualisiert2026-09-26
Tagsangular, softwarearchitektur, entwurfsmuster

Ein Entwurfsmuster: Eine Klasse erzeugt ihre Abhängigkeiten nicht selbst, sondern bekommt sie von aussen. In Angular stellt ein Injektor zur Laufzeit die passenden Instanzen bereit. Das macht Code austauschbar und gut testbar.

Code-Beispiele aus Quelle - Angular Mastery Workshop (Folie 164–184), von den Folienbildern abgeschrieben (Stand Angular 12).

Grundidee

Komponenten und Services „bestellen“ ihre Abhängigkeiten, etwa einen HttpClient oder einen eigenen Service. Der Injektor erzeugt sie und liefert sie aus. Laut Kurs nutzte Angular dafür ausschliesslich Konstruktor-Injektion, identifiziert über den TypeScript-Typ oder ein eigenes Token (Folie 175–176).

Ein Service, der in der „root“-Ebene angemeldet ist, muss in keinem providers: [] stehen und kann überall injiziert werden. Hier bekommt er einen Angular-Service und einen Wert über ein Token (Folie 177):

import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { CONFIG, Config } from '../core/config'

@Injectable({
    providedIn: 'root'          // im Root-Injektor anmelden
})
export class UserService {

    constructor(
        private httpClient: HttpClient,          // über den Typ
        @Inject(CONFIG) private config: Config   // über ein Token
    ) {}

    loadUsers() { /* ... */ }
}

Weil die Abhängigkeit von aussen kommt, kann man sie im Test einfach durch eine Attrappe (Mock) ersetzen (siehe Softwaretest). Deshalb empfiehlt der Kurs, auch fremde Bibliotheken in einen Service zu verpacken (Folie 242).

Provider

Ein Provider sagt dem Injektor, wie er eine Abhängigkeit erzeugt. Die Varianten (Folie 172):

export const CONFIG = new InjectionToken<Config>('some config');   // Token definieren

@NgModule({
    providers: [

        UserService,                                                   // einfachste Form: die Klasse

        { provide: UserService, useClass: UserService },               // dasselbe, ausgeschrieben (nicht verwenden!)

        { provide: UserService, useClass: MockUserService },           // andere Implementierung einsetzen

        NewUserService,
        { provide: UserService, useExisting: NewUserService },         // bestehende Instanz wiederverwenden

        { provide: CONFIG, useValue: { prop: 'value' } },              // Wert für ein eigenes Token

        { provide: UserService, useFactory: userServiceFactory, deps: [HttpClient] }   // eigene Erzeugungslogik
    ]
})
export class SomeModule {}

export interface Config {
    prop: string;
}

Realistisches Beispiel im AppModule (Folie 173): die Standard-Fehlerbehandlung von Angular ersetzen, einen HTTP-Interceptor als Multi-Provider anmelden und einen Konfigurationswert bereitstellen.

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { CONFIG } from './core/config/tokens';
import { LoggingInterceptor } from './core/interceptors/logging.interceptor';
import { BackendLoggingErrorHandler } from './core/backend-logging-error-handler.service';

@NgModule({
  //...
  providers: [
    {
      provide: ErrorHandler,                  // Angular-Standard durch eigene Implementierung ersetzen
      useClass: BackendLoggingErrorHandler
    },
    {
      provide: HTTP_INTERCEPTORS,             // weiteren Provider zu einem "multi"-Token hinzufügen
      useClass: MyCustomInterceptor,
      multi: true
    },
    {
      provide: CONFIG,                        // eigener Provider
      useValue: { prop: 'value' }
    }
  ]
})
export class AppModule {}

(Auf der Folie wird LoggingInterceptor importiert, aber MyCustomInterceptor angemeldet. Gemeint ist wohl derselbe Interceptor. Der Import von ErrorHandler aus @angular/core fehlt.)

Die meisten Services werden mit providedIn: 'root' angemeldet und sind dann Singletons für die ganze App. Steht ein Service stattdessen in den providers eines lazy geladenen Moduls, gilt er nur dort (Folie 171, 174).

Injektor-Hierarchie

Injektoren bilden einen Baum (Folie 178–182):

  1. NullInjector ganz oben: liefert null, wenn @Optional() gesetzt ist, sonst einen Fehler.
  2. PlatformInjector: Plattformdienste wie der DomSanitizer.
  3. RootInjector: alles aus providedIn: 'root' und aus sofort (eager) geladenen Modulen, jeweils app-weit eine Instanz.
  4. Injektor eines lazy geladenen Moduls: Seine Provider gibt es erst, wenn das Modul geladen ist. Andere Features können sie deshalb nicht benutzen. Laut Kurs ist das gut, weil es die Isolation der Features erzwingt (siehe Angular-Architektur). Wird ein Service, der schon in root angemeldet ist, im lazy Modul nochmals angemeldet, entsteht eine zweite Instanz. Das will man meistens nicht (Folie 180).
  5. ElementInjector: Provider einer Komponente. Jede Instanz der Komponente bekommt ihre eigene Instanz der Services (Folie 182):
import { Component, Self } from '@angular/core';

@Component({
    selector: 'my-org-item-list',
    template: ' ... ',
    providers: [              // werden im ElementInjector angemeldet
        SortService,
        PaginationService
    ]
})
export class ItemListComponent {

    constructor(
        @Self() private sortService: SortService,
        @Self() private paginationService: PaginationService
    ) {}
}

@Self() verhindert, dass im Modul-Injektor weitergesucht wird. Fehlt der Provider, gibt es sofort einen Fehler, oder mit @Optional() den Wert null. Einsatz: mehrere Tabellen auf einer Seite mit unabhängiger Sortierung und Blätterung, zwei Bearbeitungen nebeneinander mit getrenntem Zustand (Folie 183).

Die Suche lässt sich steuern (Folie 184):

  • @Optional(): null statt Fehler, wenn kein Provider gefunden wird.
  • @Self(): nur im eigenen ElementInjector suchen.
  • @SkipSelf(): erst beim Elternteil suchen.
  • @Host(): nicht über den Host hinaus suchen (nützlich mit @SkipSelf() und viewProviders: []).
Widerspruch

Kurs: Angular injiziert ausschliesslich über den Konstruktor (Folie 175). Heute: Die Funktion inject() ist der übliche Weg, auch in funktionalen Guards, z.B. private http = inject(HttpClient);. Angular 22 bringt zudem den Decorator @Service() und injectAsync() zum lazy Laden von Services (Quelle - Recherche - Angular heute 2026). Die inject()-Schreibweise ist eine Einordnung von Claude.

Einordnung (Claude)

Dependency Injection ist keine Angular-Erfindung, sondern ein allgemeines Muster (u.a. bekannt aus Java/Spring und .NET). Es setzt das Prinzip „gegen Schnittstellen programmieren, nicht gegen Implementierungen“ um.

Verwandt