# 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.
- Published: 2026-04-21
- Updated: 2026-04-27
- Author: SharpSkill
- Tags: angular, standalone components, migration, angular 21, ngmodule, lazy loading
- Reading time: 9 min
---
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.
```typescript
// hero-list.component.ts
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: `
@for (hero of heroes | search:query; track hero.id) {
}
`
})
export class HeroListComponent {
heroes = signal([]);
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.
```typescript
// main.ts (after migration)
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));
```
```typescript
// app.config.ts
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:
```typescript
// app.routes.ts (before)
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
}
];
```
Het standalone-equivalent laadt afzonderlijke componenten of routebestanden direct:
```typescript
// app.routes.ts (after)
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)
}
];
```
```typescript
// settings/settings.routes.ts
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.
## 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: ``
})
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.
```json
// tsconfig.json
{
"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.
```typescript
// hero-list.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';
describe('HeroListComponent', () => {
let fixture: ComponentFixture;
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.
## 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](/technologies/angular/interview-questions/angular-signals) sluiten van nature aan op de standalone-architectuur voor maximale performance
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/nl/blog/angular/angular-standalone-components-migration-best-practices