# Angular Standalone Components: Migracja i najlepsze praktyki w 2026 roku
> Kompletny przewodnik po migracji aplikacji Angular z NgModules do komponentów standalone. Obejmuje oficjalny 3-etapowy proces CLI, lazy loading, routing i najlepsze praktyki dla Angular 21.
- Published: 2026-04-21
- Updated: 2026-04-21
- Author: SharpSkill
- Tags: angular, standalone components, migracja, angular 21, ngmodule, lazy loading
- Reading time: 9 min
---
Komponenty standalone w Angular eliminują potrzebę stosowania NgModules, redukując ilość powtarzalnego kodu i umożliwiając precyzyjny lazy loading w całej aplikacji. Od momentu, gdy Angular 19 uczynił standalone domyślnym trybem, a Angular 21 ugruntował bezstrefową detekcję zmian, migracja starszych baz kodu opartych na modułach stała się zarówno prosta, jak i niezwykle opłacalna.
> **Kluczowy wniosek**
>
> Oficjalny schematyczny Angular CLI obsługuje większość migracji automatycznie w trzech przebiegach. Typowa aplikacja korporacyjna może zakończyć konwersję w jednym sprincie, a rozmiary paczek zmniejszają się o 30-50% dzięki lazy loadingowi na poziomie komponentów.
## NgModules vs komponenty standalone: co się zmieniło
NgModules pełniły rolę kontekstu kompilacji dla komponentów od Angular 2. Każdy komponent, dyrektywa i pipe musiały być zadeklarowane w dokładnie jednym module, a współdzielenie funkcjonalności wymagało starannie zorganizowanych importów i eksportów modułów. Tworzyło to ścisłe powiązanie między niepowiązanymi funkcjami i utrudniało tree-shaking.
Komponenty standalone odwracają ten model. Każdy komponent deklaruje własne zależności bezpośrednio w tablicy `imports` dekoratora `@Component`. Bez rejestracji w module, bez współdzielonych modułów, bez eksportowania połowy aplikacji.
```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('');
}
```
Tablica `imports` zastępuje cały graf zależności NgModule. Bundler widzi dokładnie, które komponenty, pipe i dyrektywy są potrzebne w każdym pliku, co umożliwia precyzyjny tree-shaking.
## Trzystopniowy proces migracji CLI
Angular dostarcza zautomatyzowany schematyk obsługujący migrację w trzech sekwencyjnych przebiegach. Każdy krok bazuje na poprzednim, a projekt powinien kompilować się poprawnie między każdym przebiegiem.
### Krok 1: Konwersja deklaracji na standalone
Pierwszy przebieg skanuje każdy komponent, dyrektywę i pipe w projekcie, dodaje `standalone: true` i przenosi niezbędne importy z nadrzędnego NgModule do własnej tablicy `imports` każdego komponentu.
```bash
# Krok 1: Konwersja wszystkich deklaracji na standalone
ng g @angular/core:standalone --path=src/app
```
Należy wybrać opcję **"Convert all components, directives and pipes to standalone"**. Schematyk używa analizy statycznej do rozwiązywania zależności, więc każdy komponent z metadanymi, których nie można przeanalizować w czasie budowania, zostanie pominięty z ostrzeżeniem.
### Krok 2: Usunięcie zbędnych NgModules
Gdy wszystkie deklaracje są już standalone, wiele NgModules staje się pustymi skorupami. Ten przebieg identyfikuje moduły, które jedynie reeksportowały standalone deklaracje, i je usuwa.
```bash
# Krok 2: Usunięcie pustych NgModules
ng g @angular/core:standalone --path=src/app
```
Należy wybrać opcję **"Remove unnecessary NgModule classes"**. Moduły zawierające nadal providery, konfiguracje routingu lub importowane przez wiele innych modułów zostaną zachowane z komentarzem TODO do ręcznego przeglądu.
### Krok 3: Przełączenie na bootstrap standalone
Ostatni przebieg zastępuje główny NgModule interfejsem API `bootstrapApplication` Angulara i konwertuje komponent główny na standalone.
```typescript
// main.ts (po migracji)
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]))
]
};
```
Wzorzec `ApplicationConfig` zastępuje tablice `providers` i `imports` modułu głównego. Wszystkie funkcje dostarczające (`provideRouter`, `provideHttpClient`, `provideAnimations`) działają bezpośrednio bez opakowań modułowych.
## Migracja routingu: z modułów na loadComponent
Moduły routingu wymagają ręcznej uwagi, ponieważ schematyk nie konwertuje automatycznie importów `loadChildren` z modułów na `loadComponent` lub `loadChildren` na poziomie tras z routami standalone.
Stary wzorzec ładował całe moduły funkcjonalne:
```typescript
// app.routes.ts (przed)
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
}
];
```
Odpowiednik standalone ładuje poszczególne komponenty lub pliki tras bezpośrednio:
```typescript
// app.routes.ts (po)
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` leniwie ładuje pojedynczy komponent. `loadChildren` z plikiem tras leniwie ładuje cały obszar funkcjonalny. Oba generują oddzielne chunki pobierane przez przeglądarkę na żądanie.
## Obsługa SharedModules i wspólnych zależności
SharedModules — uniwersalne moduły eksportujące powszechnie używane komponenty, dyrektywy i pipe — stanowią najczęstszą przeszkodę podczas migracji. Schematyk nie może ich automatycznie usunąć, ponieważ wiele modułów je importuje.
Rozwiązanie: konwersja współdzielonych deklaracji na standalone pojedynczo, a następnie usunięcie SharedModule, gdy nic go już nie importuje.
```typescript
// Przed: SharedModule reeksportuje wszystko
@NgModule({
declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
imports: [CommonModule]
})
export class SharedModule {}
// Po: Każda deklaracja jest standalone, importuj bezpośrednio
// loading-spinner.component.ts
@Component({
selector: 'app-loading-spinner',
standalone: true,
template: ``
})
export class LoadingSpinner {}
```
Konsumenci importują teraz `LoadingSpinner` bezpośrednio zamiast całego SharedModule. Bundler uwzględnia tylko konkretne komponenty potrzebne każdej trasie.
## Wymuszanie wyłącznie standalone
Po migracji zapobieganie powstawaniu nowych NgModules w bazie kodu jest kluczowe. Angular dostarcza opcję kompilatora TypeScript do tego celu.
```json
// tsconfig.json
{
"angularCompilerOptions": {
"strictStandalone": true
}
}
```
Po włączeniu `strictStandalone` każda próba utworzenia komponentu, dyrektywy lub pipe niebędącego standalone powoduje błąd kompilacji. Wymusza to nową architekturę w całym zespole.
## Wzrost wydajności: rozmiar paczek i lazy loading
Główna korzyść wydajnościowa komponentów standalone wynika z precyzyjnego lazy loadingu. W przypadku NgModules lazy loading działał na poziomie modułu — importowanie jednego komponentu z modułu wciągało każdą deklarację, którą ten moduł eksportował. Komponenty standalone przerywają to powiązanie.
Rzeczywisty benchmark na średniej wielkości aplikacji korporacyjnej (200+ komponentów) wykazał:
| Metryka | Oparte na NgModule | Standalone | Poprawa |
|---------|-------------------|------------|----------|
| Paczka początkowa | 485 KB | 218 KB | -55% |
| Największy leniwy chunk | 142 KB | 38 KB | -73% |
| Czas do interaktywności | 3,2s | 1,8s | -44% |
| Czas budowania (esbuild) | 12,4s | 8,1s | -35% |
Liczby te wynikają z usunięcia narzutu rozwiązywania modułów i umożliwienia bundlerowi eliminacji nieużywanych eksportów na poziomie komponentu zamiast modułu.
## Testowanie komponentów standalone
Testy jednostkowe znacznie się upraszczają z komponentami standalone. Konfiguracja `TestBed` nie wymaga już importowania całych modułów, aby spełnić zależności komponentu.
```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);
});
});
```
Komponent trafia bezpośrednio do tablicy `imports` w `TestBed.configureTestingModule`. Wszystkie zadeklarowane zależności są już rozwiązane przez własne `imports` komponentu, więc dodatkowe importy modułów nie są potrzebne.
## Najczęstsze pułapki migracyjne
**Cykliczne importy między komponentami standalone.** Gdy komponent A importuje komponent B, a B importuje A, kompilator TypeScript zgłasza błąd cyklicznej zależności. Rozwiązanie: wyodrębnienie współdzielonego interfejsu do oddzielnego pliku lub użycie `forwardRef()` jako tymczasowego obejścia podczas refaktoryzacji łańcucha zależności.
**Biblioteki zewnętrzne nadal używające NgModules.** Wiele bibliotek przeszło na standalone, ale niektóre starsze pakiety nadal eksportują NgModules. Należy importować te moduły bezpośrednio w tablicy `imports` komponentu standalone — Angular obsługuje mieszanie importów standalone i opartych na modułach.
**Brakujące providery po usunięciu AppModule.** Serwisy wcześniej dostarczane w tablicy `providers` modułu głównego muszą zostać przeniesione do `ApplicationConfig` w `app.config.ts` lub używać `providedIn: 'root'` w dekoratorze `@Injectable`. Serwisy o zasięgu trasy powinny wykorzystywać tablicę `providers` w konfiguracjach tras.
## Podsumowanie
- Oficjalny schematyk Angular CLI automatyzuje 80-90% migracji w trzech sekwencyjnych przebiegach: konwersja deklaracji, usuwanie modułów, przełączenie bootstrapu
- Moduły routingu wymagają ręcznej konwersji z `loadChildren` z NgModules na `loadComponent` lub standalone pliki tras
- SharedModules stanowią główną przeszkodę — należy konwertować każdą współdzieloną deklarację na standalone indywidualnie, a następnie usunąć moduł
- Włączenie `strictStandalone` w tsconfig zapobiega wprowadzaniu nowych NgModules po migracji
- Rozmiary paczek spadają o 30-55% dzięki tree-shakingowi na poziomie komponentów i granularnemu lazy loadingowi z `loadComponent`
- Testy jednostkowe znacznie się upraszczają — komponent standalone importuje się bezpośrednio w `TestBed` bez konfiguracji modułów
- Bezstrefowa detekcja zmian w Angular 21 i [reaktywność oparta na sygnałach](/technologies/angular/interview-questions/angular-signals) naturalnie współgrają z architekturą standalone dla maksymalnej wydajności
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/pl/blog/angular/angular-standalone-components-migration-best-practices