fix(core): Remove Zone-based change provider from internals by default (#63382)

This change removes the internally provided `ZoneJS`-based change
detection scheduler. This makes Angular Zoneless by default and allows
tree-shaking of the Zone change detection providers.

BREAKING CHANGE: Angular no longer provides a change detection scheduler
for ZoneJS-based change detection by default. Add
`provideZoneChangeDetection` to the providers of your
`bootstrapApplication` function or your `AppModule` (if using
`bootstrapModule`). This provider addition will be covered by an
automated migration.

PR Close #63382
This commit is contained in:
Andrew Scott 2025-07-08 14:22:24 -07:00 committed by Jessica Janiuk
parent 07b4c12984
commit 45fed3d201
15 changed files with 39 additions and 74 deletions

View file

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
import {EnvironmentInjector} from '../di/r3_injector';
import {Type} from '../interface/type';
@ -15,8 +14,7 @@ import {assertStandaloneComponentType} from '../render3/errors';
import {EnvironmentNgModuleRefAdapter} from '../render3/ng_module_ref';
import {ApplicationRef} from './application_ref';
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {provideZonelessChangeDetectionInternal} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {bootstrap} from '../platform/bootstrap';
import {profiler} from '../render3/profiler';
import {ProfilerEvent} from '../render3/profiler_types';
@ -65,8 +63,7 @@ export function internalCreateApplication(config: {
// Create root application injector based on a set of providers configured at the platform
// bootstrap level as well as providers passed to the bootstrap call by a user.
const allAppProviders = [
internalProvideZoneChangeDetection({}),
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
provideZonelessChangeDetectionInternal(),
errorHandlerEnvironmentInitializer,
...(appProviders || []),
];

View file

@ -66,7 +66,7 @@ export abstract class ChangeDetectionScheduler {
/** Token used to indicate if zoneless was enabled via provideZonelessChangeDetection(). */
export const ZONELESS_ENABLED = new InjectionToken<boolean>(
typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless enabled' : '',
{providedIn: 'root', factory: () => false},
{providedIn: 'root', factory: () => true},
);
/** Token used to indicate `provideZonelessChangeDetection` was used. */

View file

@ -11,7 +11,7 @@ import {Subscription} from 'rxjs';
import {ApplicationRef, ApplicationRefDirtyFlags} from '../../application/application_ref';
import {Injectable} from '../../di/injectable';
import {inject} from '../../di/injector_compatibility';
import {EnvironmentProviders} from '../../di/interface/provider';
import {EnvironmentProviders, Provider} from '../../di/interface/provider';
import {makeEnvironmentProviders} from '../../di/provider_collection';
import {RuntimeError, RuntimeErrorCode, formatRuntimeError} from '../../errors';
import {PendingTasksInternal} from '../../pending_tasks_internal';
@ -381,12 +381,17 @@ export function provideZonelessChangeDetection(): EnvironmentProviders {
}
return makeEnvironmentProviders([
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
{provide: NgZone, useClass: NoopNgZone},
{provide: ZONELESS_ENABLED, useValue: true},
{provide: SCHEDULE_IN_ROOT_ZONE, useValue: false},
...provideZonelessChangeDetectionInternal(),
typeof ngDevMode === 'undefined' || ngDevMode
? [{provide: PROVIDED_ZONELESS, useValue: true}]
: [],
]);
}
export function provideZonelessChangeDetectionInternal(): Provider[] {
return [
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
{provide: NgZone, useClass: NoopNgZone},
{provide: ZONELESS_ENABLED, useValue: true},
];
}

View file

@ -41,8 +41,8 @@ export {
internalProvideZoneChangeDetection as ɵinternalProvideZoneChangeDetection,
PROVIDED_NG_ZONE as ɵPROVIDED_NG_ZONE,
} from './change_detection/scheduling/ng_zone_scheduling';
export {ChangeDetectionSchedulerImpl as ɵChangeDetectionSchedulerImpl} from './change_detection/scheduling/zoneless_scheduling_impl';
export {PROVIDED_ZONELESS as ɵPROVIDED_ZONELESS} from './change_detection/scheduling/zoneless_scheduling';
export {provideZonelessChangeDetectionInternal as ɵprovideZonelessChangeDetectionInternal} from './change_detection/scheduling/zoneless_scheduling_impl';
export {
ChangeDetectionScheduler as ɵChangeDetectionScheduler,
NotificationSource as ɵNotificationSource,

View file

@ -13,6 +13,7 @@ import type {EnvironmentProviders} from './di/interface/provider';
import {makeEnvironmentProviders, provideEnvironmentInitializer} from './di/provider_collection';
import {EnvironmentInjector} from './di/r3_injector';
import {DOCUMENT} from './document';
import {RuntimeError, RuntimeErrorCode} from './errors';
import {DestroyRef} from './linker/destroy_ref';
import {NgZone} from './zone/ng_zone';
@ -89,7 +90,16 @@ export const INTERNAL_APPLICATION_ERROR_HANDLER = new InjectionToken<(e: any) =>
export const errorHandlerEnvironmentInitializer = {
provide: ENVIRONMENT_INITIALIZER,
useValue: () => void inject(ErrorHandler),
useValue: () => {
const handler = inject(ErrorHandler, {optional: true});
if ((typeof ngDevMode === 'undefined' || ngDevMode) && handler === null) {
throw new RuntimeError(
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
`A required Injectable was not found in the dependency injection tree. ` +
'If you are bootstrapping an NgModule, make sure that the `BrowserModule` is imported.',
);
}
},
multi: true,
};

View file

@ -8,22 +8,17 @@
import {compileNgModuleFactory} from '../application/application_ngmodule_factory_compiler';
import {BootstrapOptions, optionsReducer} from '../application/application_ref';
import {
getNgZoneOptions,
internalProvideZoneChangeDetection,
} from '../change_detection/scheduling/ng_zone_scheduling';
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {provideZonelessChangeDetectionInternal} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {Injectable, Injector, StaticProvider} from '../di';
import {errorHandlerEnvironmentInitializer} from '../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {Type} from '../interface/type';
import {CompilerOptions} from '../linker';
import {getNgZone} from '../zone/ng_zone';
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {createNgModuleRefWithProviders} from '../render3/ng_module_ref';
import {bootstrap, setModuleBootstrapImpl} from './bootstrap';
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
// Holds the set of providers to be used for the *next* application to be bootstrapped.
// Used only for providing the zone related providers by default with `downgradeModule`.
@ -46,7 +41,6 @@ export class PlatformRef {
private _modules: NgModuleRef<any>[] = [];
private _destroyListeners: Array<() => void> = [];
private _destroyed: boolean = false;
private _additionalApplicationProviders?: StaticProvider[];
/** @internal */
constructor(private _injector: Injector) {}
@ -61,20 +55,8 @@ export class PlatformRef {
moduleFactory: NgModuleFactory<M>,
options?: BootstrapOptions,
): Promise<NgModuleRef<M>> {
const scheduleInRootZone = (options as any)?.scheduleInRootZone;
const ngZoneFactory = () =>
getNgZone(options?.ngZone, {
...getNgZoneOptions({
eventCoalescing: options?.ngZoneEventCoalescing,
runCoalescing: options?.ngZoneRunCoalescing,
}),
scheduleInRootZone,
});
const allAppProviders = [
internalProvideZoneChangeDetection({
ngZoneFactory,
}),
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
provideZonelessChangeDetectionInternal(),
...(_additionalApplicationProviders ?? []),
errorHandlerEnvironmentInitializer,
];

View file

@ -175,7 +175,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -271,7 +270,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275PRE_STYLE",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
@ -592,7 +590,6 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -659,7 +656,6 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"interpolateParams",
"invalidCssUnitValue",
"invalidDefinition",
@ -815,6 +811,7 @@
"projectNodes",
"provideAnimations",
"provideZonelessChangeDetection",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"publishSignalConfiguration",

View file

@ -190,7 +190,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -255,7 +254,6 @@
"ViewRef",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275advance",
"\\u0275\\u0275defer",
"\\u0275\\u0275deferWhen",
@ -516,7 +514,6 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -585,7 +582,6 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeAllTriggerCleanupFns",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
@ -691,6 +687,7 @@
"profilerCallbacks",
"projectNodes",
"provideZonelessChangeDetection",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"publishSignalConfiguration",

View file

@ -183,7 +183,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -274,7 +273,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275InternalFormsSharedModule",
"\\u0275NgNoValidate",
"\\u0275\\u0275InheritDefinitionFeature",
@ -669,8 +667,6 @@
"getNextLContainer",
"getNgDirectiveDef",
"getNgModuleDef",
"getNgZone",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -762,7 +758,6 @@
"instantiateAllDirectives",
"instructionState",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAbstractControlOptions",
@ -943,6 +938,7 @@
"profiler",
"profilerCallbacks",
"projectNodes",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"providersResolver",

View file

@ -179,7 +179,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -270,7 +269,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275InternalFormsSharedModule",
"\\u0275NgNoValidate",
"\\u0275\\u0275InheritDefinitionFeature",
@ -667,8 +665,6 @@
"getNextLContainer",
"getNgDirectiveDef",
"getNgModuleDef",
"getNgZone",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -760,7 +756,6 @@
"instantiateAllDirectives",
"instructionState",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAngularZoneProperty",
@ -938,6 +933,7 @@
"profiler",
"profilerCallbacks",
"projectNodes",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"providersResolver",

View file

@ -157,7 +157,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -235,7 +234,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
"\\u0275\\u0275directiveInject",
@ -533,7 +531,6 @@
"getNextLContainer",
"getNgContainerSize",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNoOffsetIndex",
"getNodeInjectable",
"getNullInjector",
@ -604,7 +601,6 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAngularZoneProperty",
@ -729,6 +725,7 @@
"profilerCallbacks",
"projectNodes",
"provideClientHydration",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"publishSignalConfiguration",

View file

@ -191,7 +191,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NoLeftoversInUrl",
"NoMatch",
"NodeInjector",
@ -321,7 +320,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275EmptyOutletComponent",
"\\u0275\\u0275NgOnChangesFeature",
"\\u0275\\u0275attribute",
@ -790,7 +788,6 @@
"getNextLContainer",
"getNgDirectiveDef",
"getNgModuleDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -887,7 +884,6 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"interpolation1",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
@ -1076,6 +1072,7 @@
"projectNodes",
"provideRouter",
"provideZonelessChangeDetection",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"publishSignalConfiguration",

View file

@ -128,7 +128,6 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
@ -194,7 +193,6 @@
"XhrFactory",
"ZONELESS_ENABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
"\\u0275\\u0275directiveInject",
@ -433,7 +431,6 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
@ -491,7 +488,6 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAngularZoneProperty",
@ -585,6 +581,7 @@
"profilerCallbacks",
"projectNodes",
"provideZonelessChangeDetection",
"provideZonelessChangeDetectionInternal",
"providerToFactory",
"providerToRecord",
"publishSignalConfiguration",

View file

@ -10,8 +10,6 @@ import {ResourceLoader} from '@angular/compiler';
import {
ApplicationInitStatus,
ɵINTERNAL_APPLICATION_ERROR_HANDLER as INTERNAL_APPLICATION_ERROR_HANDLER,
ɵChangeDetectionScheduler as ChangeDetectionScheduler,
ɵChangeDetectionSchedulerImpl as ChangeDetectionSchedulerImpl,
Compiler,
COMPILER_OPTIONS,
Component,
@ -43,7 +41,7 @@ import {
ɵgetAsyncClassMetadataFn as getAsyncClassMetadataFn,
ɵgetInjectableDef as getInjectableDef,
ɵInternalEnvironmentProviders as InternalEnvironmentProviders,
ɵinternalProvideZoneChangeDetection as internalProvideZoneChangeDetection,
ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal,
ɵisComponentDefPendingResolution,
ɵisEnvironmentProviders as isEnvironmentProviders,
ɵNG_COMP_DEF as NG_COMP_DEF,
@ -936,9 +934,8 @@ export class TestBedCompiler {
compileNgModuleDefs(RootScopeModule as NgModuleType<any>, {
providers: [
...this.rootProviderOverrides,
internalProvideZoneChangeDetection({}),
provideZonelessChangeDetectionInternal(),
TestBedApplicationErrorHandler,
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,

View file

@ -11,9 +11,7 @@ import {
createPlatformFactory,
NgModule,
StaticProvider,
ɵinternalProvideZoneChangeDetection as internalProvideZoneChangeDetection,
ɵChangeDetectionScheduler as ChangeDetectionScheduler,
ɵChangeDetectionSchedulerImpl as ChangeDetectionSchedulerImpl,
ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal,
PlatformRef,
} from '@angular/core';
import {TestComponentRenderer} from '@angular/core/testing';
@ -37,8 +35,7 @@ export const platformBrowserTesting: (extraProviders?: StaticProvider[]) => Plat
exports: [BrowserModule],
providers: [
{provide: APP_ID, useValue: 'a'},
internalProvideZoneChangeDetection({}),
{provide: ChangeDetectionScheduler, useExisting: ChangeDetectionSchedulerImpl},
provideZonelessChangeDetectionInternal(),
ɵprovideFakePlatformNavigation(),
{provide: TestComponentRenderer, useClass: DOMTestComponentRenderer},
],