Skip to content

Commit d7d6514

Browse files
atscottdylhunn
authored andcommittedMar 31, 2023
feat(core): Add ability to configure NgZone in bootstrapApplication (#49557)
This commit adds a provider function that allows developers to configure the `NgZone` instance for the application. In the future, this provider will be used for applications to specifically opt-in to change detection powered by ZoneJS rather than it being provided by default. This API does _not_ specifically provide support for developers to define their own `NgZone` implementation or opt in to `NoopNgZone` directly. Both of these are possible today, but are effectively unsupported (applications that use these are left to their own devices to run change detection at the appropriate times). That said, developers can still use DI in `bootstrapApplication` to provide an `NgZone` implementation instead, it's just not specifically available in the `provideZoneChangeDetection` function. PR Close #49557
1 parent 58c1faf commit d7d6514

File tree

10 files changed

+191
-44
lines changed

10 files changed

+191
-44
lines changed
 

‎goldens/public-api/core/index.md

+9
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,12 @@ export class NgZone {
10501050
runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], name?: string): T;
10511051
}
10521052

1053+
// @public
1054+
export interface NgZoneOptions {
1055+
eventCoalescing?: boolean;
1056+
runCoalescing?: boolean;
1057+
}
1058+
10531059
// @public
10541060
export const NO_ERRORS_SCHEMA: SchemaMetadata;
10551061

@@ -1158,6 +1164,9 @@ export type Provider = TypeProvider | ValueProvider | ClassProvider | Constructo
11581164
// @public
11591165
export type ProviderToken<T> = Type<T> | AbstractType<T> | InjectionToken<T>;
11601166

1167+
// @public
1168+
export function provideZoneChangeDetection(options?: NgZoneOptions): EnvironmentProviders;
1169+
11611170
// @public
11621171
export interface Query {
11631172
// (undocumented)

‎packages/core/src/application_ref.ts

+112-22
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {ApplicationInitStatus} from './application_init';
1414
import {PLATFORM_INITIALIZER} from './application_tokens';
1515
import {getCompilerFacade, JitCompilerUsage} from './compiler/compiler_facade';
1616
import {Console} from './console';
17-
import {ENVIRONMENT_INITIALIZER, inject} from './di';
17+
import {ENVIRONMENT_INITIALIZER, inject, makeEnvironmentProviders} from './di';
1818
import {Injectable} from './di/injectable';
1919
import {InjectionToken} from './di/injection_token';
2020
import {Injector} from './di/injector';
@@ -226,24 +226,20 @@ export function internalCreateApplication(config: {
226226
// Create root application injector based on a set of providers configured at the platform
227227
// bootstrap level as well as providers passed to the bootstrap call by a user.
228228
const allAppProviders = [
229-
provideNgZoneChangeDetection(new NgZone(getNgZoneOptions())),
229+
provideZoneChangeDetection(),
230230
...(appProviders || []),
231231
];
232232
const adapter = new EnvironmentNgModuleRefAdapter({
233233
providers: allAppProviders,
234234
parent: platformInjector as EnvironmentInjector,
235-
debugName: NG_DEV_MODE ? 'Environment Injector' : '',
235+
debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '',
236+
// We skip environment initializers because we need to run them inside the NgZone, which happens
237+
// after we get the NgZone instance from the Injector.
236238
runEnvironmentInitializers: false,
237239
});
238240
const envInjector = adapter.injector;
239241
const ngZone = envInjector.get(NgZone);
240242

241-
// Ensure the application hasn't provided a different NgZone in its own providers
242-
if (NG_DEV_MODE && envInjector.get(NG_ZONE_DEV_MODE) !== ngZone) {
243-
// TODO: convert to runtime error
244-
throw new Error('Providing `NgZone` directly in the providers is not supported.');
245-
}
246-
247243
return ngZone.run(() => {
248244
envInjector.resolveInjectorInitializers();
249245
const exceptionHandler: ErrorHandler|null = envInjector.get(ErrorHandler, null);
@@ -381,6 +377,58 @@ export function getPlatform(): PlatformRef|null {
381377
return _platformInjector?.get(PlatformRef) ?? null;
382378
}
383379

380+
/**
381+
* Used to configure event and run coalescing with `provideZoneChangeDetection`.
382+
*
383+
* @publicApi
384+
*
385+
* @see provideZoneChangeDetection
386+
*/
387+
export interface NgZoneOptions {
388+
/**
389+
* Optionally specify coalescing event change detections or not.
390+
* Consider the following case.
391+
*
392+
* ```
393+
* <div (click)="doSomething()">
394+
* <button (click)="doSomethingElse()"></button>
395+
* </div>
396+
* ```
397+
*
398+
* When button is clicked, because of the event bubbling, both
399+
* event handlers will be called and 2 change detections will be
400+
* triggered. We can coalesce such kind of events to only trigger
401+
* change detection only once.
402+
*
403+
* By default, this option will be false. So the events will not be
404+
* coalesced and the change detection will be triggered multiple times.
405+
* And if this option be set to true, the change detection will be
406+
* triggered async by scheduling a animation frame. So in the case above,
407+
* the change detection will only be triggered once.
408+
*/
409+
eventCoalescing?: boolean;
410+
411+
/**
412+
* Optionally specify if `NgZone#run()` method invocations should be coalesced
413+
* into a single change detection.
414+
*
415+
* Consider the following case.
416+
* ```
417+
* for (let i = 0; i < 10; i ++) {
418+
* ngZone.run(() => {
419+
* // do something
420+
* });
421+
* }
422+
* ```
423+
*
424+
* This case triggers the change detection multiple times.
425+
* With ngZoneRunCoalescing options, all change detections in an event loop trigger only once.
426+
* In addition, the change detection executes in requestAnimation.
427+
*
428+
*/
429+
runCoalescing?: boolean;
430+
}
431+
384432
/**
385433
* Provides additional options to the bootstrapping process.
386434
*
@@ -470,14 +518,26 @@ export class PlatformRef {
470518
// as instantiating the module creates some providers eagerly.
471519
// So we create a mini parent injector that just contains the new NgZone and
472520
// pass that as parent to the NgModuleFactory.
473-
const ngZone = getNgZone(options?.ngZone, getNgZoneOptions(options));
521+
const ngZone = getNgZone(options?.ngZone, getNgZoneOptions({
522+
eventCoalescing: options?.ngZoneEventCoalescing,
523+
runCoalescing: options?.ngZoneRunCoalescing
524+
}));
474525
// Note: Create ngZoneInjector within ngZone.run so that all of the instantiated services are
475526
// created within the Angular zone
476527
// Do not try to replace ngZone.run with ApplicationRef#run because ApplicationRef would then be
477528
// created outside of the Angular zone.
478529
return ngZone.run(() => {
479530
const moduleRef = createNgModuleRefWithProviders(
480-
moduleFactory.moduleType, this.injector, provideNgZoneChangeDetection(ngZone));
531+
moduleFactory.moduleType, this.injector,
532+
internalProvideZoneChangeDetection(() => ngZone));
533+
534+
if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
535+
moduleRef.injector.get(PROVIDED_NG_ZONE, null) !== null) {
536+
throw new RuntimeError(
537+
RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT,
538+
'`bootstrapModule` does not support `provideZoneChangeDetection`. Use `BootstrapOptions` instead.');
539+
}
540+
481541
const exceptionHandler = moduleRef.injector.get(ErrorHandler, null);
482542
if ((typeof ngDevMode === 'undefined' || ngDevMode) && exceptionHandler === null) {
483543
throw new RuntimeError(
@@ -597,7 +657,7 @@ export class PlatformRef {
597657
}
598658

599659
// Set of options recognized by the NgZone.
600-
interface NgZoneOptions {
660+
interface InternalNgZoneOptions {
601661
enableLongStackTrace: boolean;
602662
shouldCoalesceEventChangeDetection: boolean;
603663
shouldCoalesceRunChangeDetection: boolean;
@@ -606,16 +666,16 @@ interface NgZoneOptions {
606666
// Transforms a set of `BootstrapOptions` (supported by the NgModule-based bootstrap APIs) ->
607667
// `NgZoneOptions` that are recognized by the NgZone constructor. Passing no options will result in
608668
// a set of default options returned.
609-
function getNgZoneOptions(options?: BootstrapOptions): NgZoneOptions {
669+
function getNgZoneOptions(options?: NgZoneOptions): InternalNgZoneOptions {
610670
return {
611671
enableLongStackTrace: typeof ngDevMode === 'undefined' ? false : !!ngDevMode,
612-
shouldCoalesceEventChangeDetection: options?.ngZoneEventCoalescing ?? false,
613-
shouldCoalesceRunChangeDetection: options?.ngZoneRunCoalescing ?? false,
672+
shouldCoalesceEventChangeDetection: options?.eventCoalescing ?? false,
673+
shouldCoalesceRunChangeDetection: options?.runCoalescing ?? false,
614674
};
615675
}
616676

617677
function getNgZone(
618-
ngZoneToUse: NgZone|'zone.js'|'noop' = 'zone.js', options: NgZoneOptions): NgZone {
678+
ngZoneToUse: NgZone|'zone.js'|'noop' = 'zone.js', options: InternalNgZoneOptions): NgZone {
619679
if (ngZoneToUse === 'noop') {
620680
return new NoopNgZone();
621681
}
@@ -1172,15 +1232,15 @@ export class NgZoneChangeDetectionScheduler {
11721232
}
11731233

11741234
/**
1175-
* Internal token used to provide verify that the NgZone in DI is the same as the one provided with
1176-
* `provideNgZoneChangeDetection`.
1235+
* Internal token used to verify that `provideZoneChangeDetection` is not used
1236+
* with the bootstrapModule API.
11771237
*/
1178-
const NG_ZONE_DEV_MODE = new InjectionToken<NgZone>(NG_DEV_MODE ? 'NG_ZONE token' : '');
1238+
const PROVIDED_NG_ZONE = new InjectionToken<boolean>(
1239+
(typeof ngDevMode === 'undefined' || ngDevMode) ? 'provideZoneChangeDetection token' : '');
11791240

1180-
export function provideNgZoneChangeDetection(ngZone: NgZone): StaticProvider[] {
1241+
export function internalProvideZoneChangeDetection(ngZoneFactory: () => NgZone): StaticProvider[] {
11811242
return [
1182-
NG_DEV_MODE ? {provide: NG_ZONE_DEV_MODE, useValue: ngZone} : [],
1183-
{provide: NgZone, useValue: ngZone},
1243+
{provide: NgZone, useFactory: ngZoneFactory},
11841244
{
11851245
provide: ENVIRONMENT_INITIALIZER,
11861246
multi: true,
@@ -1201,3 +1261,33 @@ export function provideNgZoneChangeDetection(ngZone: NgZone): StaticProvider[] {
12011261
{provide: ZONE_IS_STABLE_OBSERVABLE, useFactory: isStableFactory},
12021262
];
12031263
}
1264+
1265+
/**
1266+
* Provides `NgZone`-based change detection for the application bootstrapped using
1267+
* `bootstrapApplication`.
1268+
*
1269+
* `NgZone` is already provided in applications by default. This provider allows you to configure
1270+
* options like `eventCoalescing` in the `NgZone`.
1271+
* This provider is not available for `platformBrowser().bootstrapModule`, which uses
1272+
* `BootstrapOptions` instead.
1273+
*
1274+
* @usageNotes
1275+
* ```typescript=
1276+
* bootstrapApplication(MyApp, {providers: [
1277+
* provideZoneChangeDetection({eventCoalescing: true}),
1278+
* ]});
1279+
* ```
1280+
*
1281+
* @publicApi
1282+
* @see bootstrapApplication
1283+
* @see NgZoneOptions
1284+
*/
1285+
export function provideZoneChangeDetection(options?: NgZoneOptions): EnvironmentProviders {
1286+
const zoneProviders =
1287+
internalProvideZoneChangeDetection(() => new NgZone(getNgZoneOptions(options)));
1288+
return makeEnvironmentProviders([
1289+
(typeof ngDevMode === 'undefined' || ngDevMode) ? {provide: PROVIDED_NG_ZONE, useValue: true} :
1290+
[],
1291+
zoneProviders,
1292+
]);
1293+
}

‎packages/core/src/core.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export * from './metadata';
1515
export * from './version';
1616
export {TypeDecorator} from './util/decorators';
1717
export * from './di';
18-
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, BootstrapOptions, PlatformRef, ApplicationRef, createPlatformFactory, NgProbeToken, APP_BOOTSTRAP_LISTENER} from './application_ref';
18+
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, BootstrapOptions, NgZoneOptions, PlatformRef, ApplicationRef, provideZoneChangeDetection, createPlatformFactory, NgProbeToken, APP_BOOTSTRAP_LISTENER} from './application_ref';
1919
export {enableProdMode, isDevMode} from './util/is_dev_mode';
2020
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, PLATFORM_ID, ANIMATION_MODULE_TYPE, CSP_NONCE} from './application_tokens';
2121
export {APP_INITIALIZER, ApplicationInitStatus} from './application_init';

‎packages/core/src/core_private_export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS, internalCreateApplication as ɵinternalCreateApplication, provideNgZoneChangeDetection as ɵprovideNgZoneChangeDetection} from './application_ref';
9+
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS, internalCreateApplication as ɵinternalCreateApplication} from './application_ref';
1010
export {defaultIterableDiffers as ɵdefaultIterableDiffers, defaultKeyValueDiffers as ɵdefaultKeyValueDiffers} from './change_detection/change_detection';
1111
export {Console as ɵConsole} from './console';
1212
export {convertToBitFlags as ɵconvertToBitFlags, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility';

‎packages/core/test/bundling/router/bundle.golden_symbols.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,6 @@
491491
{
492492
"name": "NoneEncapsulationDomRenderer"
493493
},
494-
{
495-
"name": "NoopNgZone"
496-
},
497494
{
498495
"name": "NullInjector"
499496
},
@@ -1655,6 +1652,9 @@
16551652
{
16561653
"name": "lookupTokenUsingNodeInjector"
16571654
},
1655+
{
1656+
"name": "makeEnvironmentProviders"
1657+
},
16581658
{
16591659
"name": "makeRecord"
16601660
},
@@ -1797,7 +1797,7 @@
17971797
"name": "promise"
17981798
},
17991799
{
1800-
"name": "provideNgZoneChangeDetection"
1800+
"name": "provideZoneChangeDetection"
18011801
},
18021802
{
18031803
"name": "redirectIfUrlTree"

‎packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json

+1-7
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,6 @@
290290
{
291291
"name": "NoneEncapsulationDomRenderer"
292292
},
293-
{
294-
"name": "NoopNgZone"
295-
},
296293
{
297294
"name": "NullInjector"
298295
},
@@ -557,9 +554,6 @@
557554
{
558555
"name": "createElementRef"
559556
},
560-
{
561-
"name": "createEnvironmentInjector"
562-
},
563557
{
564558
"name": "createInjector"
565559
},
@@ -930,7 +924,7 @@
930924
"name": "promise"
931925
},
932926
{
933-
"name": "provideNgZoneChangeDetection"
927+
"name": "provideZoneChangeDetection"
934928
},
935929
{
936930
"name": "refCount"

‎packages/core/testing/src/test_bed_compiler.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {ResourceLoader} from '@angular/compiler';
10-
import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, resolveForwardRef, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵInternalEnvironmentProviders as InternalEnvironmentProviders, ɵisEnvironmentProviders as isEnvironmentProviders, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵprovideNgZoneChangeDetection as provideNgZoneChangeDetection, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDeclaration as InjectableDeclaration} from '@angular/core';
10+
import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, provideZoneChangeDetection, resolveForwardRef, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵInternalEnvironmentProviders as InternalEnvironmentProviders, ɵisEnvironmentProviders as isEnvironmentProviders, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDeclaration as InjectableDeclaration} from '@angular/core';
1111

1212
import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading';
1313
import {ComponentDef, ComponentType} from '../../src/render3';
@@ -753,9 +753,8 @@ export class TestBedCompiler {
753753
providers: [...this.rootProviderOverrides],
754754
});
755755

756-
const ngZone = new NgZone({enableLongStackTrace: true});
757-
const providers: Provider[] = [
758-
provideNgZoneChangeDetection(ngZone),
756+
const providers = [
757+
provideZoneChangeDetection(),
759758
{provide: Compiler, useFactory: () => new R3TestCompiler(this)},
760759
...this.providers,
761760
...this.providerOverrides,

‎packages/platform-browser/test/browser/bootstrap_spec.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
import {animate, style, transition, trigger} from '@angular/animations';
1010
import {DOCUMENT, isPlatformBrowser, ɵgetDOM as getDOM} from '@angular/common';
11-
import {ANIMATION_MODULE_TYPE, APP_INITIALIZER, Compiler, Component, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Directive, ErrorHandler, Inject, inject as _inject, InjectionToken, Injector, LOCALE_ID, NgModule, NgModuleRef, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Provider, Sanitizer, StaticProvider, Testability, TestabilityRegistry, TransferState, Type, VERSION} from '@angular/core';
12-
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
11+
import {ANIMATION_MODULE_TYPE, APP_INITIALIZER, Compiler, Component, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Directive, ErrorHandler, Inject, inject as _inject, InjectionToken, Injector, LOCALE_ID, NgModule, NgModuleRef, NgZone, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Provider, Sanitizer, StaticProvider, Testability, TestabilityRegistry, TransferState, Type, VERSION} from '@angular/core';
12+
import {ApplicationRef, destroyPlatform, provideZoneChangeDetection} from '@angular/core/src/application_ref';
1313
import {Console} from '@angular/core/src/console';
1414
import {ComponentRef} from '@angular/core/src/linker/component_factory';
1515
import {inject, TestBed} from '@angular/core/testing';
@@ -385,6 +385,31 @@ function bootstrap(
385385
expect(el.innerText).toBe('Hello from AnimationCmp!');
386386
});
387387
});
388+
389+
it('initializes modules inside the NgZone when using `provideZoneChangeDetection`',
390+
async () => {
391+
let moduleInitialized = false;
392+
@NgModule({})
393+
class SomeModule {
394+
constructor() {
395+
expect(NgZone.isInAngularZone()).toBe(true);
396+
moduleInitialized = true;
397+
}
398+
}
399+
@Component({
400+
template: '',
401+
selector: 'hello-app',
402+
imports: [SomeModule],
403+
standalone: true,
404+
})
405+
class AnimationCmp {
406+
}
407+
408+
await bootstrapApplication(AnimationCmp, {
409+
providers: [provideZoneChangeDetection({eventCoalescing: true})],
410+
});
411+
expect(moduleInitialized).toBe(true);
412+
});
388413
});
389414

390415
it('should throw if bootstrapped Directive is not a Component', done => {
@@ -623,6 +648,15 @@ function bootstrap(
623648
})();
624649
});
625650

651+
it('should not allow provideZoneChangeDetection in bootstrapModule', async () => {
652+
@NgModule({imports: [BrowserModule], providers: [provideZoneChangeDetection()]})
653+
class SomeModule {
654+
}
655+
656+
await expectAsync(platformBrowserDynamic().bootstrapModule(SomeModule))
657+
.toBeRejectedWithError(/provideZoneChangeDetection.*BootstrapOptions/);
658+
});
659+
626660
it('should register each application with the testability registry', async () => {
627661
const ngModuleRef1: NgModuleRef<unknown> = await bootstrap(HelloRootCmp, testProviders);
628662
const ngModuleRef2: NgModuleRef<unknown> = await bootstrap(HelloRootCmp2, testProviders);

‎packages/platform-browser/test/browser/bootstrap_standalone_spec.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component, ErrorHandler, Inject, Injectable, InjectionToken, NgModule, PlatformRef} from '@angular/core';
9+
import {Component, ErrorHandler, Inject, Injectable, InjectionToken, NgModule, NgZone, PlatformRef} from '@angular/core';
1010
import {R3Injector} from '@angular/core/src/di/r3_injector';
11+
import {NoopNgZone} from '@angular/core/src/zone/ng_zone';
1112
import {withBody} from '@angular/private/testing';
1213

1314
import {bootstrapApplication, BrowserModule} from '../../src/browser';
@@ -61,6 +62,26 @@ describe('bootstrapApplication for standalone components', () => {
6162
expect(document.body.textContent).toBe('(Ambient)');
6263
}));
6364

65+
it('should be able to provide a custom zone implementation in DI',
66+
withBody('<test-app></test-app>', async () => {
67+
@Component({
68+
selector: 'test-app',
69+
standalone: true,
70+
template: ``,
71+
})
72+
class StandaloneCmp {
73+
}
74+
75+
class CustomZone extends NoopNgZone {}
76+
const instance = new CustomZone();
77+
78+
const appRef = await bootstrapApplication(
79+
StandaloneCmp, {providers: [{provide: NgZone, useValue: instance}]});
80+
81+
appRef.tick();
82+
expect(appRef.injector.get(NgZone)).toEqual(instance);
83+
}));
84+
6485
/*
6586
This test verifies that ambient providers for the standalone component being bootstrapped
6687
(providers collected from the import graph of a standalone component) are instantiated in a

‎packages/platform-browser/testing/src/browser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
import {PlatformLocation} from '@angular/common';
99
import {MockPlatformLocation} from '@angular/common/testing';
10-
import {APP_ID, createPlatformFactory, NgModule, PLATFORM_INITIALIZER, platformCore, StaticProvider, ɵprovideNgZoneChangeDetection as provideNgZoneChangeDetection} from '@angular/core';
10+
import {APP_ID, createPlatformFactory, NgModule, PLATFORM_INITIALIZER, platformCore, provideZoneChangeDetection, StaticProvider} from '@angular/core';
1111
import {BrowserModule, ɵBrowserDomAdapter as BrowserDomAdapter} from '@angular/platform-browser';
1212

1313
import {BrowserDetection, createNgZone} from './browser_util';
@@ -37,7 +37,7 @@ export const platformBrowserTesting =
3737
exports: [BrowserModule],
3838
providers: [
3939
{provide: APP_ID, useValue: 'a'},
40-
provideNgZoneChangeDetection(createNgZone()),
40+
provideZoneChangeDetection(),
4141
{provide: PlatformLocation, useClass: MockPlatformLocation},
4242
]
4343
})

0 commit comments

Comments
 (0)
Please sign in to comment.