NgModules sind Angulars klassisches Architektur-Konstrukt: Container, die zusammengehörige Components, Direktiven und Pipes bündeln, Provider registrieren und ein öffentliches API definieren. Auch wenn Standalone-Components seit v19 der Default sind, bleiben NgModules First-Class — viele Bestands-Codebasen, Bibliotheken und Feature-Bundles setzen weiterhin produktiv darauf. Dieser Artikel erklärt den Aufbau und die typischen Patterns.

Container für Components, Direktiven und Pipes

Ein NgModule ist eine TypeScript-Klasse mit dem @NgModule-Dekorator. Es übernimmt drei Aufgaben:

  1. Bündelung: declariert Components, Direktiven und Pipes, die zusammengehören
  2. Komposition: importiert andere Module und macht deren exportierte Bausteine im eigenen Scope verfügbar
  3. Provider-Registrierung: stellt Services für die Dependency Injection bereit

Vor Standalone (v14) war NgModule der einzige Weg, Components, Direktiven und Pipes für ein Template registrieren zu lassen — ohne Modul keine Verwendung.

Alle Felder im Überblick

TypeScript user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

import { UserListComponent } from './user-list.component';
import { UserDetailComponent } from './user-detail.component';
import { HighlightDirective } from './highlight.directive';
import { UserApiService } from './user-api.service';

@NgModule({
    // 1. declarations: lokal registrierte Bausteine dieses Moduls.
    //    Components/Direktiven/Pipes dürfen in genau EINEM Modul deklariert werden.
    declarations: [UserListComponent, UserDetailComponent, HighlightDirective],

    // 2. imports: andere Module, deren EXPORTIERTE Bausteine hier nutzbar werden.
    imports: [CommonModule, RouterModule],

    // 3. exports: das öffentliche API. Nur exportierte Symbole sind
    //    von außen (in importierenden Modulen) verwendbar.
    exports: [UserListComponent],

    // 4. providers: Services für die DI. providedIn: 'root' ist meist besser
    //    (Tree-Shaking), hier nur wenn modul-lokaler Scope gewünscht ist.
    providers: [UserApiService]

    // 5. bootstrap: nur im Root-Modul. Liste der Components,
    //    die beim App-Start instanziert werden — typischerweise [AppComponent].
    // bootstrap: [AppComponent]
})
export class UserModule {}

Drei etablierte Rollen

Feature-Module

Bündelt alles, was zu einem fachlichen Bereich gehört — Routes, Components, Services. Wird typischerweise lazy geladen.

TypeScript user-feature.module.ts
@NgModule({
    declarations: [UserListComponent, UserDetailComponent, UserFormComponent],
    imports: [
        CommonModule,
        ReactiveFormsModule,
        RouterModule.forChild(USER_ROUTES) // forChild: Lazy-Feature
    ],
    providers: [UserApiService]
})
export class UserFeatureModule {}

Shared-Module

Sammelt wiederverwendbare Bausteine (Pipes, Direktiven, kleine UI-Components), die mehrere Features brauchen. Typischerweise ohne Provider.

TypeScript shared.module.ts
@NgModule({
    declarations: [TruncatePipe, HighlightDirective, SpinnerComponent],
    imports: [CommonModule],
    exports: [
        CommonModule, // re-export, damit Konsumenten nicht selbst importieren müssen
        TruncatePipe,
        HighlightDirective,
        SpinnerComponent
    ]
})
export class SharedModule {}

Core-Module

Hält app-globale Singleton-Services (Auth, Logging, HTTP-Interceptoren) und wird genau einmal im AppModule importiert.

Routen lädt der Router on demand

Mit loadChildren wird ein komplettes Feature-Modul erst beim Aufruf der zugehörigen Route geladen — als eigener JavaScript-Chunk.

TypeScript app-routes.ts
import { Routes } from '@angular/router';

export const APP_ROUTES: Routes = [
    { path: '', component: HomeComponent },
    {
        path: 'users',
        loadChildren: () =>
            import('./user/user-feature.module')
                .then(m => m.UserFeatureModule)
    }
];

Im Lazy-Modul registriert RouterModule.forChild(routes) die feature-eigenen Routen, die der Router unter dem Parent-Pfad einhängt.

Die wichtigsten Module-Bausteine

ModulPaketZweck
BrowserModule@angular/platform-browserPflicht im Root-Modul. Stellt browser-spezifische Services bereit.
CommonModule@angular/commonNgIf, NgFor, NgClass, Pipes wie DatePipe, AsyncPipe. Für Feature-Module.
FormsModule@angular/formsTemplate-driven Forms ([(ngModel)]).
ReactiveFormsModule@angular/formsReaktive Forms (FormGroup, FormControl).
RouterModule@angular/routerRouting. forRoot() im AppModule, forChild() in Feature-Modulen.
HttpClientModule@angular/common/httpHTTP-Client (alternativ: provideHttpClient() in standalone Apps).

Wissenswertes zu NgModules

BrowserModule nur einmal — im AppModule

BrowserModule darf in einer Anwendung exakt einmal importiert werden, üblicherweise im Root-AppModule. Ein zusätzlicher Import in Feature-Modulen wirft zur Laufzeit einen Fehler. In allen anderen Modulen kommt stattdessen CommonModule zum Einsatz, das die gleichen Direktiven und Pipes enthält.

CommonModule statt BrowserModule in Feature-Modulen

CommonModule ist das modul-taugliche Subset von BrowserModule ohne die einmaligen Browser-Services. Genau das, was lazy-geladene Feature-Module brauchen, um NgIf, NgFor und Standard-Pipes nutzen zu können.

forRoot vs. forChild

Module mit Provider-Konfiguration bieten oft zwei statische Methoden: forRoot(config) registriert Provider einmalig im Root-Injektor und wird im AppModule importiert; forChild() wird in Feature-Modulen importiert, ohne die Provider erneut zu registrieren. Klassisches Pattern bei RouterModule, NgRx und einigen Material-Modulen.

providers im Modul vs. providedIn: “root“

Services mit { providedIn: “root“ } sind tree-shakeable: Wird der Service nirgends injiziert, fliegt er aus dem Bundle. Provider, die im providers-Array eines Moduls stehen, sind das nicht — sie landen immer im Build, sobald das Modul geladen wird. Für app-globale Services ist providedIn: “root“ daher meist die bessere Wahl.

NgModules können Standalone-Components importieren

Seit v15 darf eine Standalone-Component direkt im imports-Array eines NgModules stehen — ohne sie in declarations aufzunehmen. Dadurch lassen sich neue Components als Standalone schreiben, während das umgebende Feature noch klassisch modulbasiert bleibt. Die ideale Brücke für eine schrittweise Migration.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Grundlagen

Zur Übersicht