@@ -14,7 +14,7 @@ import {ApplicationInitStatus} from './application_init';
14
14
import { PLATFORM_INITIALIZER } from './application_tokens' ;
15
15
import { getCompilerFacade , JitCompilerUsage } from './compiler/compiler_facade' ;
16
16
import { Console } from './console' ;
17
- import { ENVIRONMENT_INITIALIZER , inject } from './di' ;
17
+ import { ENVIRONMENT_INITIALIZER , inject , makeEnvironmentProviders } from './di' ;
18
18
import { Injectable } from './di/injectable' ;
19
19
import { InjectionToken } from './di/injection_token' ;
20
20
import { Injector } from './di/injector' ;
@@ -226,24 +226,20 @@ export function internalCreateApplication(config: {
226
226
// Create root application injector based on a set of providers configured at the platform
227
227
// bootstrap level as well as providers passed to the bootstrap call by a user.
228
228
const allAppProviders = [
229
- provideNgZoneChangeDetection ( new NgZone ( getNgZoneOptions ( ) ) ) ,
229
+ provideZoneChangeDetection ( ) ,
230
230
...( appProviders || [ ] ) ,
231
231
] ;
232
232
const adapter = new EnvironmentNgModuleRefAdapter ( {
233
233
providers : allAppProviders ,
234
234
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.
236
238
runEnvironmentInitializers : false ,
237
239
} ) ;
238
240
const envInjector = adapter . injector ;
239
241
const ngZone = envInjector . get ( NgZone ) ;
240
242
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
-
247
243
return ngZone . run ( ( ) => {
248
244
envInjector . resolveInjectorInitializers ( ) ;
249
245
const exceptionHandler : ErrorHandler | null = envInjector . get ( ErrorHandler , null ) ;
@@ -381,6 +377,58 @@ export function getPlatform(): PlatformRef|null {
381
377
return _platformInjector ?. get ( PlatformRef ) ?? null ;
382
378
}
383
379
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
+
384
432
/**
385
433
* Provides additional options to the bootstrapping process.
386
434
*
@@ -470,14 +518,26 @@ export class PlatformRef {
470
518
// as instantiating the module creates some providers eagerly.
471
519
// So we create a mini parent injector that just contains the new NgZone and
472
520
// 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
+ } ) ) ;
474
525
// Note: Create ngZoneInjector within ngZone.run so that all of the instantiated services are
475
526
// created within the Angular zone
476
527
// Do not try to replace ngZone.run with ApplicationRef#run because ApplicationRef would then be
477
528
// created outside of the Angular zone.
478
529
return ngZone . run ( ( ) => {
479
530
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
+
481
541
const exceptionHandler = moduleRef . injector . get ( ErrorHandler , null ) ;
482
542
if ( ( typeof ngDevMode === 'undefined' || ngDevMode ) && exceptionHandler === null ) {
483
543
throw new RuntimeError (
@@ -597,7 +657,7 @@ export class PlatformRef {
597
657
}
598
658
599
659
// Set of options recognized by the NgZone.
600
- interface NgZoneOptions {
660
+ interface InternalNgZoneOptions {
601
661
enableLongStackTrace : boolean ;
602
662
shouldCoalesceEventChangeDetection : boolean ;
603
663
shouldCoalesceRunChangeDetection : boolean ;
@@ -606,16 +666,16 @@ interface NgZoneOptions {
606
666
// Transforms a set of `BootstrapOptions` (supported by the NgModule-based bootstrap APIs) ->
607
667
// `NgZoneOptions` that are recognized by the NgZone constructor. Passing no options will result in
608
668
// a set of default options returned.
609
- function getNgZoneOptions ( options ?: BootstrapOptions ) : NgZoneOptions {
669
+ function getNgZoneOptions ( options ?: NgZoneOptions ) : InternalNgZoneOptions {
610
670
return {
611
671
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 ,
614
674
} ;
615
675
}
616
676
617
677
function getNgZone (
618
- ngZoneToUse : NgZone | 'zone.js' | 'noop' = 'zone.js' , options : NgZoneOptions ) : NgZone {
678
+ ngZoneToUse : NgZone | 'zone.js' | 'noop' = 'zone.js' , options : InternalNgZoneOptions ) : NgZone {
619
679
if ( ngZoneToUse === 'noop' ) {
620
680
return new NoopNgZone ( ) ;
621
681
}
@@ -1172,15 +1232,15 @@ export class NgZoneChangeDetectionScheduler {
1172
1232
}
1173
1233
1174
1234
/**
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 .
1177
1237
*/
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' : '' ) ;
1179
1240
1180
- export function provideNgZoneChangeDetection ( ngZone : NgZone ) : StaticProvider [ ] {
1241
+ export function internalProvideZoneChangeDetection ( ngZoneFactory : ( ) => NgZone ) : StaticProvider [ ] {
1181
1242
return [
1182
- NG_DEV_MODE ? { provide : NG_ZONE_DEV_MODE , useValue : ngZone } : [ ] ,
1183
- { provide : NgZone , useValue : ngZone } ,
1243
+ { provide : NgZone , useFactory : ngZoneFactory } ,
1184
1244
{
1185
1245
provide : ENVIRONMENT_INITIALIZER ,
1186
1246
multi : true ,
@@ -1201,3 +1261,33 @@ export function provideNgZoneChangeDetection(ngZone: NgZone): StaticProvider[] {
1201
1261
{ provide : ZONE_IS_STABLE_OBSERVABLE , useFactory : isStableFactory } ,
1202
1262
] ;
1203
1263
}
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