Angular 21 — Фаза 2: Bootstrap (старт приложения)

Версия 21.0.3, standalone + zoneless. Точка входа src/main.ts:

bootstrapApplication(App, appConfig).catch(err => console.error(err));

Прослежено по исходникам src-v21 (а не по статьям) — пути указаны для каждого шага.

Цепочка вызовов (сверху вниз)

main.ts: bootstrapApplication(App, appConfig)
  platform-browser/src/browser.ts:127
    └─ (JIT-режим: сначала resolveComponentResources — подтянуть внешние templateUrl/styleUrl)
    └─ internalCreateApplication({ rootComponent, appProviders, platformProviders })
         core/src/application/create_application.ts:38
           ├─ createOrReusePlatformInjector(platformProviders)   // платформенный injector
           ├─ allAppProviders = [
           │     provideZonelessChangeDetectionInternal(),       // ← zoneless ПО УМОЛЧАНИЮ (v21!)
           │     errorHandlerEnvironmentInitializer,
           │     ...appProviders                                  // наши provideRouter и т.д.
           │  ]
           ├─ new EnvironmentNgModuleRefAdapter({...})            // создаёт EnvironmentInjector
           └─ bootstrap({ r3Injector, platformInjector, rootComponent })
                core/src/platform/bootstrap.ts:81
                  └─ ngZone.run(() => {                           // в zoneless это NoopNgZone
                        r3Injector.resolveInjectorInitializers()  // ENVIRONMENT_INITIALIZER
                        initStatus.runInitializers()              // APP_INITIALIZER (provideAppInitializer)
                        await initStatus.donePromise
                        appRef = injector.get(ApplicationRef)
                        appRef.bootstrap(rootComponent)           // ← создаёт и рендерит корневой компонент
                        return appRef
                     })

Ключевые объекты, которые создаются

  1. Platform injector — самый верхний DI-контейнер (один на страницу). createOrReusePlatformInjector (create_application.ts). Содержит общие для платформы сервисы.
  2. Environment injector (root) — контейнер уровня приложения. Сюда попадают все провайдеры из appConfig.providers плюс служебные. Реализация — R3Injector (core/src/di/r3_injector.ts). Создаётся через EnvironmentNgModuleRefAdapter.
  3. ApplicationRef — «дирижёр» приложения (core/src/application/application_ref.ts). Держит список view (allViews), флаги «грязности» (dirtyFlags) и метод tick().

Порядок инициализации (важно)

Внутри bootstrap() строго:

  1. resolveInjectorInitializers() — выполняет ENVIRONMENT_INITIALIZER (например, внутренние установки роутера).
  2. initStatus.runInitializers() — выполняет APP_INITIALIZER (в т.ч. наш provideAppInitializer(() => ...)). Если инициализатор вернул Promise — bootstrap ждёт его через initStatus.donePromise.
  3. Только после готовности инициализаторов — appRef.bootstrap(rootComponent).

Это объясняет, почему APP_INITIALIZER гарантированно отрабатывает до первого рендера компонента.

appRef.bootstrap(App) — рождение корневого компонента

ApplicationRef.bootstrap (application_ref.ts):

  • по App.ɵcmp (тот самый defineComponent из фазы Compile) создаёт ComponentRef через ComponentFactory / createRootComponent;
  • находит в DOM хост-элемент по селектору app-root;
  • выполняет create-проход template-функции (rf & 1) → строится DOM;
  • регистрирует view в ApplicationRef.allViews;
  • помечает приложение «грязным» и инициирует первый tick() → первый update-проход (rf & 2) заполняет интерполяции.

Профайлер на старте даёт события (порядок из enum ProfilerEvent): BootstrapApplicationStart → … → BootstrapComponentStart → ComponentStart → TemplateCreateStart/End (создание DOM) → TemplateUpdateStart/End (первые биндинги) → ComponentEnd → BootstrapComponentEnd → AfterRenderHooks → BootstrapApplicationEnd.

Роль NgZone в zoneless

bootstrap() всё равно вызывает ngZone.run(...), но при zoneless NgZone — это NoopNgZone (ничего не патчит). Change detection теперь запускается не «по любому async», а через scheduler, реагирующий на изменения сигналов и явные markForCheck/ApplicationRef.tick (см. фазу Runtime). Проверка на конфликт: если поданы и provideZoneChangeDetection, и zoneless — в dev будет warning (bootstrap.ts:96).

Привязка к исходникам

  • packages/platform-browser/src/browser.tsbootstrapApplication
  • packages/core/src/application/create_application.tsinternalCreateApplication
  • packages/core/src/platform/bootstrap.tsbootstrap (инициализаторы, NgZone)
  • packages/core/src/application/application_ref.tsApplicationRef, bootstrap, tick
  • packages/core/src/di/r3_injector.tsR3Injector (Environment injector)

Итог фазы Bootstrap

  1. bootstrapApplicationinternalCreateApplication собирает провайдеры (вкл. zoneless по умолчанию) и создаёт два инжектора: platform и environment.
  2. bootstrap() запускает инициализаторы (ENVIRONMENT → APP_INITIALIZER) и ждёт их.
  3. ApplicationRef.bootstrap(App) создаёт ComponentRef, выполняет create-проход (строит DOM) и первый tick (update-проход, заполняет биндинги).
  4. Дальше приложение живёт реактивно — см. 03-runtime.md.