@@ -14,7 +14,7 @@ import {ApplicationInitStatus} from './application_init';
1414import { PLATFORM_INITIALIZER } from './application_tokens' ;
1515import { getCompilerFacade , JitCompilerUsage } from './compiler/compiler_facade' ;
1616import { Console } from './console' ;
17- import { ENVIRONMENT_INITIALIZER , inject } from './di' ;
17+ import { ENVIRONMENT_INITIALIZER , inject , makeEnvironmentProviders } from './di' ;
1818import { Injectable } from './di/injectable' ;
1919import { InjectionToken } from './di/injection_token' ;
2020import { 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
617677function 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+ }
0 commit comments