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.

Angular Standalone Components migracja i najlepsze praktyki

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.

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('');
}

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.

main.ts (po migracji)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]))
  ]
};

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:

app.routes.ts (przed)typescript
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:

app.routes.ts (po)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 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.

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: `<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.

tsconfig.jsonjson
{
  "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.

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);
  });
});

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 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 naturalnie współgrają z architekturą standalone dla maksymalnej wydajności

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Tagi

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

Udostępnij

Powiązane artykuły