Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
refactor(core): Make the zoneless-by-default change a flag flip
This makes the zoneless-by-default change a flag flip that can be
individually enabled in tests, createApplication, and bootstrapModule
for gradual rollout. In addition, the "require on CD provider" check is
also made individually flippable for gradual rollout.
  • Loading branch information
atscott committed Sep 16, 2025
commit 726c0bbf0332bc9c49091cc83957aeddc5383f5e
4 changes: 4 additions & 0 deletions packages/core/src/application/create_application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {ProfilerEvent} from '../render3/profiler_types';
import {errorHandlerEnvironmentInitializer} from '../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {PlatformRef} from '../platform/platform_ref';
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';

const ZONELESS_BY_DEFAULT = true;

/**
* Internal create application API that implements the core application creation logic and optional
Expand Down Expand Up @@ -64,6 +67,7 @@ export function internalCreateApplication(config: {
// bootstrap level as well as providers passed to the bootstrap call by a user.
const allAppProviders = [
provideZonelessChangeDetectionInternal(),
ZONELESS_BY_DEFAULT ? [] : internalProvideZoneChangeDetection({}),
errorHandlerEnvironmentInitializer,
...(appProviders || []),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import {Subscription} from 'rxjs';
import {ApplicationRef, ApplicationRefDirtyFlags} from '../../application/application_ref';
import {
ENVIRONMENT_INITIALIZER,
EnvironmentInjector,
EnvironmentProviders,
inject,
Injectable,
InjectionToken,
makeEnvironmentProviders,
provideEnvironmentInitializer,
StaticProvider,
} from '../../di';
import {RuntimeError, RuntimeErrorCode} from '../../errors';
Expand All @@ -28,12 +26,11 @@ import {InternalNgZoneOptions} from '../../zone/ng_zone';

import {
ChangeDetectionScheduler,
ZONELESS_SCHEDULER_DISABLED,
ZONELESS_ENABLED,
SCHEDULE_IN_ROOT_ZONE,
} from './zoneless_scheduling';
import {SCHEDULE_IN_ROOT_ZONE_DEFAULT} from './flags';
import {INTERNAL_APPLICATION_ERROR_HANDLER, ErrorHandler} from '../../error_handler';
import {INTERNAL_APPLICATION_ERROR_HANDLER} from '../../error_handler';

@Injectable({providedIn: 'root'})
export class NgZoneChangeDetectionScheduler {
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/platform/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {stringify} from '../util/stringify';
import {isPromise} from '../util/lang';
import {PendingTasksInternal} from '../pending_tasks_internal';

const REQUIRE_ONE_CD_PROVIDER = false;
const REQUIRE_ONE_CD_PROVIDER_CREATE_APPLICATION = false;
const REQUIRE_ONE_CD_PROVIDER_BOOTSTRAP_MODULE = false;

/**
* InjectionToken to control root component bootstrap behavior.
Expand Down Expand Up @@ -104,8 +105,11 @@ export function bootstrap<M>(
),
);
}
if (REQUIRE_ONE_CD_PROVIDER) {
if (!envInjector.get(PROVIDED_ZONELESS) && !envInjector.get(PROVIDED_NG_ZONE)) {
if (!envInjector.get(PROVIDED_ZONELESS) && !envInjector.get(PROVIDED_NG_ZONE)) {
if (
(REQUIRE_ONE_CD_PROVIDER_CREATE_APPLICATION && isApplicationBootstrapConfig(config)) ||
(REQUIRE_ONE_CD_PROVIDER_BOOTSTRAP_MODULE && !isApplicationBootstrapConfig(config))
) {
throw new Error(
'Missing change detection configuration: ' +
'please add either `provideZoneChangeDetection()` or `provideZonelessChangeDetection()` ' +
Expand Down
20 changes: 19 additions & 1 deletion packages/core/src/platform/platform_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ 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';
import {
getNgZoneOptions,
internalProvideZoneChangeDetection,
} from '../change_detection/scheduling/ng_zone_scheduling';
import {getNgZone} from '../zone/ng_zone';

const ZONELESS_BY_DEFAULT = true;

// 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`.
Expand Down Expand Up @@ -55,8 +61,20 @@ export class PlatformRef {
moduleFactory: NgModuleFactory<M>,
options?: BootstrapOptions,
): Promise<NgModuleRef<M>> {
const defaultZoneCdProviders = [];
if (!ZONELESS_BY_DEFAULT) {
const ngZoneFactory = () =>
getNgZone(options?.ngZone, {
...getNgZoneOptions({
eventCoalescing: options?.ngZoneEventCoalescing,
runCoalescing: options?.ngZoneRunCoalescing,
}),
});
defaultZoneCdProviders.push(internalProvideZoneChangeDetection({ngZoneFactory}));
}
const allAppProviders = [
provideZonelessChangeDetectionInternal(),
...defaultZoneCdProviders,
...(_additionalApplicationProviders ?? []),
errorHandlerEnvironmentInitializer,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
Expand Down Expand Up @@ -268,9 +269,11 @@
"WebAnimationsPlayer",
"WebAnimationsStyleNormalizer",
"XhrFactory",
"ZONELESS_BY_DEFAULT",
"ZONELESS_ENABLED",
"ZONELESS_SCHEDULER_DISABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275PRE_STYLE",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
Expand Down Expand Up @@ -591,6 +594,7 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
Expand Down Expand Up @@ -657,6 +661,7 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"interpolateParams",
"invalidCssUnitValue",
"invalidDefinition",
Expand Down
11 changes: 8 additions & 3 deletions packages/core/test/bundling/defer/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@
"shimStylesContent"
],
"lazy": [
"DeferComponent",
"_DeferComponent",
"AFTER_RENDER_SEQUENCES_TO_ADD",
"ANIMATIONS",
"APP_BOOTSTRAP_LISTENER",
Expand Down Expand Up @@ -190,6 +188,7 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
Expand Down Expand Up @@ -252,9 +251,11 @@
"VIEW_REFS",
"ViewEncapsulation",
"ViewRef",
"ZONELESS_BY_DEFAULT",
"ZONELESS_ENABLED",
"ZONELESS_SCHEDULER_DISABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275advance",
"\\u0275\\u0275defer",
"\\u0275\\u0275deferWhen",
Expand Down Expand Up @@ -515,6 +516,7 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
Expand Down Expand Up @@ -583,6 +585,7 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeAllTriggerCleanupFns",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
Expand Down Expand Up @@ -790,7 +793,9 @@
"wasLastNodeCreated",
"writeDirectClass",
"writeDirectStyle",
"writeToDirectiveInput"
"writeToDirectiveInput",
"DeferComponent",
"_DeferComponent"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
Expand Down Expand Up @@ -232,9 +233,11 @@
"ViewEncapsulation",
"ViewRef",
"XhrFactory",
"ZONELESS_BY_DEFAULT",
"ZONELESS_ENABLED",
"ZONELESS_SCHEDULER_DISABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
"\\u0275\\u0275directiveInject",
Expand Down Expand Up @@ -532,6 +535,7 @@
"getNextLContainer",
"getNgContainerSize",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNoOffsetIndex",
"getNodeInjectable",
"getNullInjector",
Expand Down Expand Up @@ -602,6 +606,7 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAngularZoneProperty",
Expand Down
5 changes: 5 additions & 0 deletions packages/core/test/bundling/router/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NoLeftoversInUrl",
"NoMatch",
"NodeInjector",
Expand Down Expand Up @@ -318,9 +319,11 @@
"ViewRef",
"XSS_SECURITY_URL",
"XhrFactory",
"ZONELESS_BY_DEFAULT",
"ZONELESS_ENABLED",
"ZONELESS_SCHEDULER_DISABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275EmptyOutletComponent",
"\\u0275\\u0275NgOnChangesFeature",
"\\u0275\\u0275attribute",
Expand Down Expand Up @@ -789,6 +792,7 @@
"getNextLContainer",
"getNgDirectiveDef",
"getNgModuleDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
Expand Down Expand Up @@ -885,6 +889,7 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"interpolation1",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"NgModuleRef",
"NgOnChangesFeatureImpl",
"NgZone",
"NgZoneChangeDetectionScheduler",
"NodeInjector",
"NodeInjectorDestroyRef",
"NodeInjectorFactory",
Expand Down Expand Up @@ -191,9 +192,11 @@
"ViewEncapsulation",
"ViewRef",
"XhrFactory",
"ZONELESS_BY_DEFAULT",
"ZONELESS_ENABLED",
"ZONELESS_SCHEDULER_DISABLED",
"ZoneAwareEffectScheduler",
"ZoneStablePendingTask",
"\\u0275\\u0275defineComponent",
"\\u0275\\u0275defineInjectable",
"\\u0275\\u0275directiveInject",
Expand Down Expand Up @@ -432,6 +435,7 @@
"getNearestLContainer",
"getNextLContainer",
"getNgDirectiveDef",
"getNgZoneOptions",
"getNodeInjectable",
"getNullInjector",
"getOrBorrowReactiveLViewConsumer",
Expand Down Expand Up @@ -489,6 +493,7 @@
"instructionState",
"internalCreateApplication",
"internalImportProvidersFrom",
"internalProvideZoneChangeDetection",
"invokeDirectivesHostBindings",
"invokeHostBindingsInCreationMode",
"isAngularZoneProperty",
Expand Down
4 changes: 4 additions & 0 deletions packages/core/testing/src/test_bed_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
ɵgetInjectableDef as getInjectableDef,
ɵInternalEnvironmentProviders as InternalEnvironmentProviders,
ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal,
ɵinternalProvideZoneChangeDetection as internalProvideZoneChangeDetection,
ɵisComponentDefPendingResolution,
ɵisEnvironmentProviders as isEnvironmentProviders,
ɵNG_COMP_DEF as NG_COMP_DEF,
Expand Down Expand Up @@ -91,6 +92,8 @@ enum TestingModuleOverride {
OVERRIDE_TEMPLATE,
}

const ZONELESS_BY_DEFAULT = true;
Comment thread
atscott marked this conversation as resolved.

function isTestingModuleOverride(value: unknown): value is TestingModuleOverride {
return (
value === TestingModuleOverride.DECLARATION || value === TestingModuleOverride.OVERRIDE_TEMPLATE
Expand Down Expand Up @@ -935,6 +938,7 @@ export class TestBedCompiler {
providers: [
...this.rootProviderOverrides,
provideZonelessChangeDetectionInternal(),
ZONELESS_BY_DEFAULT ? [] : internalProvideZoneChangeDetection({}),
TestBedApplicationErrorHandler,
{
provide: ENVIRONMENT_INITIALIZER,
Expand Down
4 changes: 4 additions & 0 deletions packages/platform-browser/testing/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import {
NgModule,
StaticProvider,
ɵprovideZonelessChangeDetectionInternal as provideZonelessChangeDetectionInternal,
ɵinternalProvideZoneChangeDetection as internalProvideZoneChangeDetection,
PlatformRef,
} from '@angular/core';
import {TestComponentRenderer} from '@angular/core/testing';
import {BrowserModule, platformBrowser} from '../../index';
import {DOMTestComponentRenderer} from './dom_test_component_renderer';

const ZONELESS_BY_DEFAULT = true;
Comment thread
atscott marked this conversation as resolved.

/**
* Platform for testing
*
Expand All @@ -36,6 +39,7 @@ export const platformBrowserTesting: (extraProviders?: StaticProvider[]) => Plat
providers: [
{provide: APP_ID, useValue: 'a'},
provideZonelessChangeDetectionInternal(),
ZONELESS_BY_DEFAULT ? [] : internalProvideZoneChangeDetection({}),
ɵprovideFakePlatformNavigation(),
{provide: TestComponentRenderer, useClass: DOMTestComponentRenderer},
],
Expand Down