Angular Standalone Components: migratie en best practices in 2026

Volledige gids voor het migreren van Angular-applicaties van NgModules naar standalone components. Behandelt de officiële CLI-migratie in 3 stappen, lazy loading, routing en best practices voor Angular 21.

Architectuur van de migratie naar Angular standalone components: overgang van NgModule- naar standalone-componentboom

Angular standalone components maken NgModules overbodig, verminderen boilerplate en maken fijnmazig lazy loading mogelijk in de hele applicatie. Sinds Angular 19 standalone tot standaard maakte en Angular 21 zoneless change detection consolideerde, is het migreren van legacy modulegebaseerde codebases zowel eenvoudig als impactvol geworden.

Belangrijkste inzicht

Het officiële Angular CLI-schematic regelt het grootste deel van de migratie automatisch in drie passes. Een typische enterprise-applicatie kan de conversie binnen één sprint afronden, waarbij bundlegroottes met 30-50 % dalen dankzij lazy loading per component.

NgModules vs standalone components: wat is er veranderd

NgModules dienden sinds Angular 2 als de compilatiecontext voor componenten. Elk component, elke directive en elke pipe moest in precies één module worden gedeclareerd, en gedeelde functionaliteit vereiste zorgvuldig georchestreerde module-imports en -exports. Dit leidde tot strakke koppeling tussen onafhankelijke features en bemoeilijkte tree-shaking.

Standalone components draaien dit model om. Elk component declareert zijn eigen afhankelijkheden direct in de imports-array van de @Component-decorator. Geen moduleregistratie, geen gedeelde modules, geen barrel-exports van de halve applicatie.

hero-list.component.tstypescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';

@Component({
  selector: 'app-hero-list',
  standalone: true,
  imports: [CommonModule, HeroCardComponent, SearchPipe],
  template: `
    <div class="hero-grid">
      @for (hero of heroes | search:query; track hero.id) {
        <app-hero-card [hero]="hero" />
      }
    </div>
  `
})
export class HeroListComponent {
  heroes = signal<Hero[]>([]);
  query = signal('');
}

De imports-array vervangt de volledige NgModule-afhankelijkheidsgraaf. De bundler ziet precies welke componenten, pipes en directives elk bestand nodig heeft, wat nauwkeurige tree-shaking mogelijk maakt.

Het CLI-migratieproces in 3 stappen

Angular biedt een geautomatiseerd schematic dat de migratie in drie opeenvolgende passes afhandelt. Elke stap bouwt voort op de vorige, en het project moet tussen elke pass schoon compileren.

Stap 1: declaraties omzetten naar standalone

De eerste pass scant elk component, elke directive en elke pipe in het project, voegt standalone: true toe en verplaatst de noodzakelijke imports vanuit de bovenliggende NgModule naar de eigen imports-array van elk component.

bash
# Step 1: Convert all declarations to standalone
ng g @angular/core:standalone --path=src/app

Kies bij de prompt "Convert all components, directives and pipes to standalone". Het schematic gebruikt statische analyse om afhankelijkheden te resolven, dus elk component met metadata die niet bij build-time geanalyseerd kunnen worden, wordt overgeslagen met een waarschuwing.

Stap 2: onnodige NgModules verwijderen

Nu alle declaraties standalone zijn, worden veel NgModules lege schillen. Deze pass identificeert modules die alleen standalone-declaraties opnieuw exporteerden en verwijdert ze.

bash
# Step 2: Remove empty NgModules
ng g @angular/core:standalone --path=src/app

Kies "Remove unnecessary NgModule classes". Modules die nog providers, route-configuraties bevatten of door meerdere andere modules worden geïmporteerd, blijven behouden met een TODO-commentaar voor handmatige review.

Stap 3: overschakelen naar standalone bootstrap

De laatste pass vervangt de root-NgModule door Angular's bootstrapApplication-API en zet het root-component om naar standalone.

main.ts (after migration)typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch(err => console.error(err));
app.config.tstypescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
};

Het ApplicationConfig-patroon vervangt de providers- en imports-arrays van de root-module. Alle provider-functies (provideRouter, provideHttpClient, provideAnimations) werken direct zonder module-wrappers.

Routing-migratie: van modules naar loadComponent

Routing-modules vereisen handmatige aandacht omdat het schematic loadChildren-module-imports niet automatisch omzet naar loadComponent of route-niveau loadChildren met standalone-routes.

Het legacy-patroon laadde hele feature-modules:

app.routes.ts (before)typescript
const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module')
      .then(m => m.DashboardModule)
  }
];

Het standalone-equivalent laadt afzonderlijke componenten of routebestanden direct:

app.routes.ts (after)typescript
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(c => c.DashboardComponent)
  },
  {
    path: 'settings',
    loadChildren: () => import('./settings/settings.routes')
      .then(r => r.settingsRoutes)
  }
];
settings/settings.routes.tstypescript
import { Routes } from '@angular/router';

export const settingsRoutes: Routes = [
  {
    path: '',
    loadComponent: () => import('./settings.component')
      .then(c => c.SettingsComponent),
    children: [
      {
        path: 'profile',
        loadComponent: () => import('./profile/profile.component')
          .then(c => c.ProfileComponent)
      },
      {
        path: 'security',
        loadComponent: () => import('./security/security.component')
          .then(c => c.SecurityComponent)
      }
    ]
  }
];

loadComponent lazy-laadt een enkel component. loadChildren met een routebestand lazy-laadt een heel feature-gebied. Beide produceren losse chunks die de browser op aanvraag ophaalt.

Klaar om je Angular gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Omgaan met SharedModules en gemeenschappelijke afhankelijkheden

SharedModules — de allesomvattende modules die veelgebruikte componenten, directives en pipes exporteren — vormen de meest voorkomende blokkade tijdens de migratie. Het schematic kan ze niet automatisch verwijderen omdat meerdere modules ze importeren.

De oplossing: gedeelde declaraties één voor één omzetten naar standalone en daarna de SharedModule verwijderen zodra niets ze nog importeert.

typescript
// Before: SharedModule re-exports everything
@NgModule({
  declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
  exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
  imports: [CommonModule]
})
export class SharedModule {}

// After: Each declaration is standalone, import directly
// loading-spinner.component.ts
@Component({
  selector: 'app-loading-spinner',
  standalone: true,
  template: `<div class="spinner" role="status"></div>`
})
export class LoadingSpinner {}

Gebruikers importeren LoadingSpinner nu direct in plaats van de hele SharedModule. De bundler neemt alleen de specifieke componenten op die elke route nodig heeft.

Standalone-only ontwikkeling afdwingen

Na de migratie is het cruciaal te voorkomen dat nieuwe NgModules opnieuw in de codebase sluipen. Angular biedt hiervoor een TypeScript-compileroptie.

tsconfig.jsonjson
{
  "angularCompilerOptions": {
    "strictStandalone": true
  }
}

Met strictStandalone ingeschakeld levert elke poging om een niet-standalone component, directive of pipe te maken een compilatiefout op. Dit dwingt de nieuwe architectuur af binnen het hele team.

Performancewinst: bundlegrootte en lazy loading

Het voornaamste performance-voordeel van standalone components komt van granulair lazy loading. Met NgModules werkte lazy loading op moduleniveau — één component uit een module importeren trok elke declaratie mee die de module exporteerde. Standalone components doorbreken die koppeling.

Een real-world benchmark op een middelgrote enterprise-applicatie (200+ componenten) toonde:

| Metriek | NgModule-gebaseerd | Standalone | Verbetering | |--------|---------------|------------|-------------| | Initiële bundle | 485 KB | 218 KB | -55 % | | Grootste lazy chunk | 142 KB | 38 KB | -73 % | | Time to Interactive | 3,2 s | 1,8 s | -44 % | | Buildtijd (esbuild) | 12,4 s | 8,1 s | -35 % |

Deze cijfers komen voort uit het wegvallen van de overhead van module-resolutie en de mogelijkheid voor de bundler om ongebruikte exports op componentniveau te elimineren in plaats van op moduleniveau.

Standalone components testen

Unittests vereenvoudigen aanzienlijk met standalone components. De TestBed-configuratie vereist niet langer het importeren van hele modules om de afhankelijkheden van een component te bevredigen.

hero-list.component.spec.tstypescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';

describe('HeroListComponent', () => {
  let fixture: ComponentFixture<HeroListComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [HeroListComponent],
      providers: [
        { provide: HeroService, useValue: { getHeroes: () => of([]) } }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(HeroListComponent);
  });

  it('should render hero cards', () => {
    fixture.componentRef.setInput('heroes', mockHeroes);
    fixture.detectChanges();
    const cards = fixture.nativeElement.querySelectorAll('app-hero-card');
    expect(cards.length).toBe(mockHeroes.length);
  });
});

Het component gaat direct de imports-array van TestBed.configureTestingModule in. Alle gedeclareerde afhankelijkheden zijn al opgelost via het eigen imports, dus extra module-imports zijn niet nodig.

Veelvoorkomende migratievalkuilen

Circulaire imports tussen standalone components. Wanneer component A component B importeert en B importeert A, gooit de TypeScript-compiler een fout vanwege circulaire afhankelijkheid. De oplossing: extraheer de gedeelde interface naar een apart bestand of gebruik forwardRef() als tijdelijke workaround terwijl de afhankelijkheidsketen wordt gerefactord.

Externe libraries die nog NgModules gebruiken. Veel libraries zijn naar standalone gemigreerd, maar sommige legacy-pakketten exporteren nog NgModules. Importeer die modules direct in de imports-array van het standalone component — Angular ondersteunt het mengen van standalone- en modulegebaseerde imports.

Ontbrekende providers na het verwijderen van AppModule. Services die voorheen werden geleverd in de providers-array van de root-module moeten verhuizen naar de ApplicationConfig in app.config.ts of providedIn: 'root' gebruiken in de @Injectable-decorator. Route-scoped services moeten de providers-array in de routeconfiguraties gebruiken.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Conclusie

  • Het officiële Angular CLI-schematic automatiseert 80-90 % van de migratie via drie opeenvolgende passes: declaraties omzetten, modules verwijderen, bootstrap omschakelen
  • Routing-modules vereisen handmatige conversie van loadChildren met NgModules naar loadComponent of standalone routebestanden
  • SharedModules zijn de voornaamste blokkade — zet elke gedeelde declaratie afzonderlijk om naar standalone en verwijder daarna de module
  • Schakel strictStandalone in tsconfig in om te voorkomen dat na de migratie nieuwe NgModules worden geïntroduceerd
  • Bundlegroottes dalen 30-55 % dankzij tree-shaking op componentniveau en granulair lazy loading met loadComponent
  • Unittests vereenvoudigen drastisch — importeer het standalone component direct in TestBed zonder moduleconfiguratie
  • De zoneless change detection van Angular 21 en signal-gebaseerde reactiviteit sluiten van nature aan op de standalone-architectuur voor maximale performance

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#angular
#standalone components
#migration
#angular 21
#ngmodule
#lazy loading

Delen

Gerelateerde artikelen