# Angular Standalone Components: Міграція та найкращі практики у 2026 році > Повний посібник з міграції Angular-додатків з NgModules на standalone-компоненти. Охоплює офіційний 3-кроковий процес CLI, lazy loading, маршрутизацію та найкращі практики для Angular 21. - Published: 2026-04-21 - Updated: 2026-04-21 - Author: SharpSkill - Tags: angular, standalone components, міграція, angular 21, ngmodule, lazy loading - Reading time: 9 min --- 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-експортів половини додатку. ```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(''); } ``` Масив `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. ```typescript // main.ts (після міграції) 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])) ] }; ``` Патерн `ApplicationConfig` замінює масиви `providers` та `imports` кореневого модуля. Всі функції-провайдери (`provideRouter`, `provideHttpClient`, `provideAnimations`) працюють безпосередньо без модульних обгорток. ## Міграція маршрутизації: від модулів до loadComponent Модулі маршрутизації потребують ручної уваги, оскільки схема не конвертує автоматично імпорти `loadChildren` з модулів на `loadComponent` або `loadChildren` на рівні маршрутів зі standalone-маршрутами. Старий патерн завантажував цілі функціональні модулі: ```typescript // app.routes.ts (до) const routes: Routes = [ { path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module') .then(m => m.DashboardModule) } ]; ``` Standalone-еквівалент завантажує окремі компоненти або файли маршрутів безпосередньо: ```typescript // app.routes.ts (після) 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` ліниво завантажує окремий компонент. `loadChildren` з файлом маршрутів ліниво завантажує цілу функціональну область. Обидва варіанти створюють окремі chunk-и, які браузер отримує за запитом. ## Робота з 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: `
` }) export class LoadingSpinner {} ``` Споживачі тепер імпортують `LoadingSpinner` безпосередньо замість цілого SharedModule. Бандлер включає лише ті компоненти, які потрібні кожному маршруту. ## Примусове використання виключно standalone Після міграції запобігання появі нових NgModules у кодовій базі є критично важливим. Angular надає опцію компілятора TypeScript для цього. ```json // tsconfig.json { "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` більше не вимагає імпорту цілих модулів для задоволення залежностей компонента. ```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); }); }); ``` Компонент потрапляє безпосередньо в масив `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 та [реактивність на основі сигналів](/technologies/angular/interview-questions/angular-signals) природно поєднуються зі standalone-архітектурою для максимальної продуктивності --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/angular/angular-standalone-components-migration-best-practices