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.

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.
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.
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('');
}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.
# Krok 1: Konwersja wszystkich deklaracji na standalone
ng g @angular/core:standalone --path=src/appNależ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.
# Krok 2: Usunięcie pustych NgModules
ng g @angular/core:standalone --path=src/appNależ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.
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));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:
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:
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)
}
];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.
Gotowy na rozmowy o Angular?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
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.
// 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: `<div class="spinner" role="status"></div>`
})
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.
{
"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.
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);
});
});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.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
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
loadChildrenz NgModules naloadComponentlub 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
strictStandalonew 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
TestBedbez konfiguracji modułów - Bezstrefowa detekcja zmian w Angular 21 i reaktywność oparta na sygnałach naturalnie współgrają z architekturą standalone dla maksymalnej wydajności
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
