|
9 | 9 | import {ApplicationRef} from '../../application/application_ref'; |
10 | 10 | import {ChangeDetectionSchedulerImpl} from './zoneless_scheduling_impl'; |
11 | 11 | import {inject} from '../../di/injector_compatibility'; |
12 | | -import {makeEnvironmentProviders} from '../../di/provider_collection'; |
| 12 | +import {provideEnvironmentInitializer} from '../../di/provider_collection'; |
13 | 13 | import {NgZone} from '../../zone/ng_zone'; |
14 | 14 |
|
15 | | -import {EnvironmentInjector} from '../../di/r3_injector'; |
16 | | -import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token'; |
17 | | -import {CheckNoChangesMode} from '../../render3/state'; |
18 | 15 | import {ErrorHandler} from '../../error_handler'; |
19 | 16 | import {checkNoChangesInternal} from '../../render3/instructions/change_detection'; |
20 | | -import {ZONELESS_ENABLED} from './zoneless_scheduling'; |
21 | 17 |
|
22 | | -/** |
23 | | - * Used to periodically verify no expressions have changed after they were checked. |
24 | | - * |
25 | | - * @param options Used to configure when the check will execute. |
26 | | - * - `interval` will periodically run exhaustive `checkNoChanges` on application views |
27 | | - * - `useNgZoneOnStable` will use ZoneJS to determine when change detection might have run |
28 | | - * in an application using ZoneJS to drive change detection. When the `NgZone.onStable` would |
29 | | - * have emitted, all views attached to the `ApplicationRef` are checked for changes. |
30 | | - * - 'exhaustive' means that all views attached to `ApplicationRef` and all the descendants of those views will be |
31 | | - * checked for changes (excluding those subtrees which are detached via `ChangeDetectorRef.detach()`). |
32 | | - * This is useful because the check that runs after regular change detection does not work for components using `ChangeDetectionStrategy.OnPush`. |
33 | | - * This check is will surface any existing errors hidden by `OnPush` components. By default, this check is exhaustive |
34 | | - * and will always check all views, regardless of their "dirty" state and `ChangeDetectionStrategy`. |
35 | | - * |
36 | | - * When the `useNgZoneOnStable` option is `true`, this function will provide its own `NgZone` implementation and needs |
37 | | - * to come after any other `NgZone` provider, including `provideZoneChangeDetection()` and `provideZonelessChangeDetection()`. |
38 | | - * |
39 | | - * @experimental |
40 | | - * @publicApi |
41 | | - */ |
42 | | -export function provideExperimentalCheckNoChangesForDebug(options: { |
43 | | - interval?: number; |
44 | | - useNgZoneOnStable?: boolean; |
45 | | - exhaustive?: boolean; |
46 | | -}) { |
47 | | - if (typeof ngDevMode === 'undefined' || ngDevMode) { |
48 | | - if (options.interval === undefined && !options.useNgZoneOnStable) { |
49 | | - throw new Error('Must provide one of `useNgZoneOnStable` or `interval`'); |
50 | | - } |
51 | | - const checkNoChangesMode = |
52 | | - options?.exhaustive === false |
53 | | - ? CheckNoChangesMode.OnlyDirtyViews |
54 | | - : CheckNoChangesMode.Exhaustive; |
55 | | - return makeEnvironmentProviders([ |
56 | | - options?.useNgZoneOnStable |
57 | | - ? {provide: NgZone, useFactory: () => new DebugNgZoneForCheckNoChanges(checkNoChangesMode)} |
58 | | - : [], |
59 | | - options?.interval !== undefined |
60 | | - ? exhaustiveCheckNoChangesInterval(options.interval, checkNoChangesMode) |
61 | | - : [], |
62 | | - { |
63 | | - provide: ENVIRONMENT_INITIALIZER, |
64 | | - multi: true, |
65 | | - useValue: () => { |
66 | | - if ( |
67 | | - options?.useNgZoneOnStable && |
68 | | - !(inject(NgZone) instanceof DebugNgZoneForCheckNoChanges) |
69 | | - ) { |
70 | | - throw new Error( |
71 | | - '`provideExperimentalCheckNoChangesForDebug` with `useNgZoneOnStable` must be after any other provider for `NgZone`.', |
72 | | - ); |
| 18 | +export function exhaustiveCheckNoChangesInterval(interval: number) { |
| 19 | + return provideEnvironmentInitializer(() => { |
| 20 | + const applicationRef = inject(ApplicationRef); |
| 21 | + const errorHandler = inject(ErrorHandler); |
| 22 | + const scheduler = inject(ChangeDetectionSchedulerImpl); |
| 23 | + const ngZone = inject(NgZone); |
| 24 | + |
| 25 | + function scheduleCheckNoChanges() { |
| 26 | + ngZone.runOutsideAngular(() => { |
| 27 | + setTimeout(() => { |
| 28 | + if (applicationRef.destroyed) { |
| 29 | + return; |
| 30 | + } |
| 31 | + if (scheduler.pendingRenderTaskId || scheduler.runningTick) { |
| 32 | + scheduleCheckNoChanges(); |
| 33 | + return; |
73 | 34 | } |
74 | | - }, |
75 | | - }, |
76 | | - ]); |
77 | | - } else { |
78 | | - return makeEnvironmentProviders([]); |
79 | | - } |
80 | | -} |
81 | | - |
82 | | -export class DebugNgZoneForCheckNoChanges extends NgZone { |
83 | | - private applicationRef?: ApplicationRef; |
84 | | - private scheduler?: ChangeDetectionSchedulerImpl; |
85 | | - private errorHandler?: ErrorHandler; |
86 | | - private readonly injector = inject(EnvironmentInjector); |
87 | 35 |
|
88 | | - constructor(private readonly checkNoChangesMode: CheckNoChangesMode) { |
89 | | - const zonelessEnabled = inject(ZONELESS_ENABLED); |
90 | | - // Use coalescing to ensure we aren't ever running this check synchronously |
91 | | - super({ |
92 | | - shouldCoalesceEventChangeDetection: true, |
93 | | - shouldCoalesceRunChangeDetection: zonelessEnabled, |
94 | | - }); |
| 36 | + for (const view of applicationRef.allViews) { |
| 37 | + try { |
| 38 | + checkNoChangesInternal(view._lView, true /** exhaustive */); |
| 39 | + } catch (e) { |
| 40 | + errorHandler.handleError(e); |
| 41 | + } |
| 42 | + } |
95 | 43 |
|
96 | | - if (zonelessEnabled) { |
97 | | - // prevent emits to ensure code doesn't rely on these |
98 | | - this.onMicrotaskEmpty.emit = () => {}; |
99 | | - this.onStable.emit = () => { |
100 | | - this.scheduler ||= this.injector.get(ChangeDetectionSchedulerImpl); |
101 | | - if (this.scheduler.pendingRenderTaskId || this.scheduler.runningTick) { |
102 | | - return; |
103 | | - } |
104 | | - this.checkApplicationViews(); |
105 | | - }; |
106 | | - this.onUnstable.emit = () => {}; |
107 | | - } else { |
108 | | - this.runOutsideAngular(() => { |
109 | | - this.onStable.subscribe(() => { |
110 | | - this.checkApplicationViews(); |
111 | | - }); |
| 44 | + scheduleCheckNoChanges(); |
| 45 | + }, interval); |
112 | 46 | }); |
113 | 47 | } |
114 | | - } |
115 | | - |
116 | | - private checkApplicationViews() { |
117 | | - this.applicationRef ||= this.injector.get(ApplicationRef); |
118 | | - for (const view of this.applicationRef.allViews) { |
119 | | - try { |
120 | | - checkNoChangesInternal(view._lView, this.checkNoChangesMode); |
121 | | - } catch (e) { |
122 | | - this.errorHandler ||= this.injector.get(ErrorHandler); |
123 | | - this.errorHandler.handleError(e); |
124 | | - } |
125 | | - } |
126 | | - } |
127 | | -} |
128 | | - |
129 | | -function exhaustiveCheckNoChangesInterval( |
130 | | - interval: number, |
131 | | - checkNoChangesMode: CheckNoChangesMode, |
132 | | -) { |
133 | | - return { |
134 | | - provide: ENVIRONMENT_INITIALIZER, |
135 | | - multi: true, |
136 | | - useFactory: () => { |
137 | | - const applicationRef = inject(ApplicationRef); |
138 | | - const errorHandler = inject(ErrorHandler); |
139 | | - const scheduler = inject(ChangeDetectionSchedulerImpl); |
140 | | - const ngZone = inject(NgZone); |
141 | | - |
142 | | - return () => { |
143 | | - function scheduleCheckNoChanges() { |
144 | | - ngZone.runOutsideAngular(() => { |
145 | | - setTimeout(() => { |
146 | | - if (applicationRef.destroyed) { |
147 | | - return; |
148 | | - } |
149 | | - if (scheduler.pendingRenderTaskId || scheduler.runningTick) { |
150 | | - scheduleCheckNoChanges(); |
151 | | - return; |
152 | | - } |
153 | | - |
154 | | - for (const view of applicationRef.allViews) { |
155 | | - try { |
156 | | - checkNoChangesInternal(view._lView, checkNoChangesMode); |
157 | | - } catch (e) { |
158 | | - errorHandler.handleError(e); |
159 | | - } |
160 | | - } |
161 | | - |
162 | | - scheduleCheckNoChanges(); |
163 | | - }, interval); |
164 | | - }); |
165 | | - } |
166 | | - scheduleCheckNoChanges(); |
167 | | - }; |
168 | | - }, |
169 | | - }; |
| 48 | + scheduleCheckNoChanges(); |
| 49 | + }); |
170 | 50 | } |
0 commit comments