Angular Standalone Components: Міграція та найкращі практики у 2026 році

Повний посібник з міграції Angular-додатків з NgModules на standalone-компоненти. Охоплює офіційний 3-кроковий процес CLI, lazy loading, маршрутизацію та найкращі практики для Angular 21.

Angular Standalone Components міграція та найкращі практики

Standalone-компоненти Angular усувають потребу в NgModules, зменшуючи обсяг шаблонного коду та відкриваючи можливості для детального lazy loading по всьому додатку. Відтоді як Angular 19 зробив standalone режимом за замовчуванням, а Angular 21 закріпив безвідповідальне виявлення змін без зон, міграція застарілих модульних кодових баз стала водночас простою та високоефективною.

Ключовий висновок

Офіційна схема Angular CLI автоматично обробляє більшу частину міграції за три проходи. Типовий корпоративний додаток може завершити конверсію за один спринт, а розміри бандлів зменшуються на 30-50% завдяки lazy loading на рівні компонентів.

NgModules проти standalone-компонентів: що змінилося

NgModules виконували роль контексту компіляції для компонентів починаючи з Angular 2. Кожен компонент, директива та pipe мали бути оголошені рівно в одному модулі, а спільна функціональність вимагала ретельно організованих імпортів та експортів модулів. Це створювало тісний зв'язок між непов'язаними функціями та ускладнювало tree-shaking.

Standalone-компоненти перевертають цю модель. Кожен компонент оголошує власні залежності безпосередньо в масиві imports декоратора @Component. Без реєстрації в модулі, без спільних модулів, без barrel-експортів половини додатку.

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

Масив imports замінює весь граф залежностей NgModule. Бандлер бачить точно, які компоненти, pipe та директиви потрібні кожному файлу, що уможливлює точний tree-shaking.

3-кроковий процес міграції CLI

Angular надає автоматизовану схему, яка обробляє міграцію в трьох послідовних проходах. Кожен крок базується на попередньому, і проєкт повинен компілюватися чисто між кожним проходом.

Крок 1: Конвертація оголошень у standalone

Перший прохід сканує кожен компонент, директиву та pipe в проєкті, додає standalone: true і переносить необхідні імпорти з батьківського NgModule у власний масив imports кожного компонента.

bash
# Крок 1: Конвертація всіх оголошень у standalone
ng g @angular/core:standalone --path=src/app

Оберіть "Convert all components, directives and pipes to standalone" при запиті. Схема використовує статичний аналіз для вирішення залежностей, тому будь-який компонент з метаданими, які неможливо проаналізувати під час збірки, буде пропущений з попередженням.

Крок 2: Видалення непотрібних NgModules

Коли всі оголошення вже standalone, багато NgModules стають порожніми оболонками. Цей прохід ідентифікує модулі, які лише реекспортували standalone-оголошення, і видаляє їх.

bash
# Крок 2: Видалення порожніх NgModules
ng g @angular/core:standalone --path=src/app

Оберіть "Remove unnecessary NgModule classes". Модулі, які все ще містять провайдери, конфігурації маршрутів або імпортуються кількома іншими модулями, будуть збережені з коментарем TODO для ручного перегляду.

Крок 3: Перехід на standalone bootstrap

Останній прохід замінює кореневий NgModule на API bootstrapApplication Angular і конвертує кореневий компонент у standalone.

main.ts (після міграції)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]))
  ]
};

Патерн ApplicationConfig замінює масиви providers та imports кореневого модуля. Всі функції-провайдери (provideRouter, provideHttpClient, provideAnimations) працюють безпосередньо без модульних обгорток.

Міграція маршрутизації: від модулів до loadComponent

Модулі маршрутизації потребують ручної уваги, оскільки схема не конвертує автоматично імпорти loadChildren з модулів на loadComponent або loadChildren на рівні маршрутів зі standalone-маршрутами.

Старий патерн завантажував цілі функціональні модулі:

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

Standalone-еквівалент завантажує окремі компоненти або файли маршрутів безпосередньо:

app.routes.ts (після)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 ліниво завантажує окремий компонент. loadChildren з файлом маршрутів ліниво завантажує цілу функціональну область. Обидва варіанти створюють окремі chunk-и, які браузер отримує за запитом.

Готовий до співбесід з Angular?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Робота з SharedModules та спільними залежностями

SharedModules — універсальні модулі, що експортують широко використовувані компоненти, директиви та pipe — є найпоширенішою перешкодою під час міграції. Схема не може їх автоматично видалити, оскільки їх імпортують кілька модулів.

Рішення: конвертувати спільні оголошення в standalone по одному, а потім видалити SharedModule, коли нічого більше його не імпортує.

typescript
// До: SharedModule реекспортує все
@NgModule({
  declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
  exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
  imports: [CommonModule]
})
export class SharedModule {}

// Після: Кожне оголошення standalone, імпортуйте безпосередньо
// loading-spinner.component.ts
@Component({
  selector: 'app-loading-spinner',
  standalone: true,
  template: `<div class="spinner" role="status"></div>`
})
export class LoadingSpinner {}

Споживачі тепер імпортують LoadingSpinner безпосередньо замість цілого SharedModule. Бандлер включає лише ті компоненти, які потрібні кожному маршруту.

Примусове використання виключно standalone

Після міграції запобігання появі нових NgModules у кодовій базі є критично важливим. Angular надає опцію компілятора TypeScript для цього.

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

З увімкненим strictStandalone будь-яка спроба створити не-standalone компонент, директиву або pipe призводить до помилки компіляції. Це забезпечує дотримання нової архітектури у всій команді.

Приріст продуктивності: розмір бандлів та lazy loading

Основна перевага standalone-компонентів у продуктивності полягає в детальному lazy loading. З NgModules lazy loading працював на рівні модуля — імпорт одного компонента з модуля тягнув за собою кожне оголошення, яке цей модуль експортував. Standalone-компоненти розривають цей зв'язок.

Реальний бенчмарк на середньому корпоративному додатку (200+ компонентів) показав:

| Метрика | На базі NgModule | Standalone | Покращення | |---------|-----------------|------------|------------| | Початковий бандл | 485 КБ | 218 КБ | -55% | | Найбільший лінивий chunk | 142 КБ | 38 КБ | -73% | | Час до інтерактивності | 3,2с | 1,8с | -44% | | Час збірки (esbuild) | 12,4с | 8,1с | -35% |

Ці показники є результатом усунення накладних витрат на розв'язання модулів та надання бандлеру можливості елімінувати невикористані експорти на рівні компонентів, а не модулів.

Тестування standalone-компонентів

Юніт-тести значно спрощуються зі standalone-компонентами. Конфігурація TestBed більше не вимагає імпорту цілих модулів для задоволення залежностей компонента.

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

Компонент потрапляє безпосередньо в масив imports TestBed.configureTestingModule. Усі оголошені залежності вже розв'язані через власні imports компонента, тому додаткові імпорти модулів не потрібні.

Типові пастки міграції

Циклічні імпорти між standalone-компонентами. Коли компонент A імпортує компонент B, а B імпортує A, компілятор TypeScript видає помилку циклічної залежності. Рішення: виділити спільний інтерфейс в окремий файл або використати forwardRef() як тимчасове рішення під час рефакторингу ланцюга залежностей.

Сторонні бібліотеки, що все ще використовують NgModules. Багато бібліотек перейшли на standalone, але деякі застарілі пакети все ще експортують NgModules. Імпортуйте ці модулі безпосередньо в масив imports standalone-компонента — Angular підтримує змішування standalone та модульних імпортів.

Відсутні провайдери після видалення AppModule. Сервіси, що раніше надавалися в масиві providers кореневого модуля, повинні бути перенесені до ApplicationConfig в app.config.ts або використовувати providedIn: 'root' в декораторі @Injectable. Сервіси з областю видимості маршруту повинні використовувати масив providers у конфігураціях маршрутів.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Підсумки

  • Офіційна схема Angular CLI автоматизує 80-90% міграції у трьох послідовних проходах: конвертація оголошень, видалення модулів, зміна bootstrap
  • Модулі маршрутизації потребують ручної конвертації з loadChildren з NgModules на loadComponent або standalone файли маршрутів
  • SharedModules є основною перешкодою — конвертуйте кожне спільне оголошення в standalone окремо, потім видаліть модуль
  • Увімкніть strictStandalone в tsconfig для запобігання появі нових NgModules після міграції
  • Розміри бандлів зменшуються на 30-55% завдяки tree-shaking на рівні компонентів та детальному lazy loading з loadComponent
  • Юніт-тести значно спрощуються — standalone-компонент імпортується безпосередньо в TestBed без конфігурації модулів
  • Беззонне виявлення змін Angular 21 та реактивність на основі сигналів природно поєднуються зі standalone-архітектурою для максимальної продуктивності

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#angular
#standalone components
#міграція
#angular 21
#ngmodule
#lazy loading

Поділитися

Пов'язані статті