forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(core): private feature for potential zoneless-compatibility …
…debug check This commit adds a feature that might be useful for determining if an application is zoneless-ready. The way this works is generally only useful right now when zoneless is enabled. Some version of this may be useful in the future as a general configuration option to change detection to make `checkNoChanges` pass always exhaustive as an opt-in to address angular#45612.
- Loading branch information
Showing
10 changed files
with
389 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
packages/core/src/change_detection/scheduling/exhaustive_check_no_changes.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {ApplicationRef} from '../../application/application_ref'; | ||
import {ChangeDetectionSchedulerImpl} from './zoneless_scheduling_impl'; | ||
import {Injectable} from '../../di/injectable'; | ||
import {inject} from '../../di/injector_compatibility'; | ||
import {makeEnvironmentProviders} from '../../di/provider_collection'; | ||
import {NgZone} from '../../zone/ng_zone'; | ||
|
||
import {EnvironmentInjector} from '../../di/r3_injector'; | ||
import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token'; | ||
import {InjectionToken} from '../../di/injection_token'; | ||
import {CheckNoChangesMode} from '../../render3/state'; | ||
import {ErrorHandler} from '../../error_handler'; | ||
import {checkNoChangesInternal} from '../../render3/instructions/change_detection'; | ||
|
||
const EXHAUSTIVE_CHECK_NO_CHANGES_FOR_DEBUG = new InjectionToken<boolean>( | ||
'exhaustive check no changes for debug', | ||
); | ||
|
||
/** | ||
* For internal use only. For use with zoneless change detection to check bindings update without notification. | ||
* | ||
* @param options Used to configure when the 'exhaustive' checkNoChanges will execute. | ||
* - `interval` will periodically run exhaustive `checkNoChanges` on application views | ||
* - `useNgZoneOnStable` will us ZoneJS to determine when change detection might have run | ||
* in an application using ZoneJS to drive change detection. When the `NgZone.onStable` would | ||
* have emit, all views attached to the ApplicationRef are checked for changes. | ||
* | ||
* With both strategies above, if the zoneless scheduler has a change detection scheduled, | ||
* the check will be skipped for that round. | ||
* | ||
* - exhaustive means that all views attached to ApplicationRef and all the descendants of those views will be | ||
* checked for changes (excluding those subtrees which are detached via `ChangeDetectorRef.detach()`). This may | ||
* uncover *existing* `ExpressionChangedAfterItHasBeenCheckedError` bugs that are not related to zoneless. | ||
* `ExpressionChangedAfterItHasBeenCheckedError` does not work for components using `ChangeDetectionStrategy.OnPush` | ||
* because the check skips views which are `OnPush` and not marked for check. By default, this check is exhaustive | ||
* and will always check all views, regardless of their "dirty" state and `ChangeDetectionStrategy`. | ||
*/ | ||
export function provideCheckNoChangesForDebug(options: { | ||
interval?: number; | ||
useNgZoneOnStable?: boolean; | ||
exhaustive?: boolean; | ||
}) { | ||
if (typeof ngDevMode === 'undefined' || ngDevMode) { | ||
if (options.interval === undefined && !options.useNgZoneOnStable) { | ||
throw new Error('Must provide one of `useNgZoneOnStable` or `interval`'); | ||
} | ||
return makeEnvironmentProviders([ | ||
{ | ||
provide: EXHAUSTIVE_CHECK_NO_CHANGES_FOR_DEBUG, | ||
useValue: options?.exhaustive === false ? false : true, | ||
}, | ||
options?.useNgZoneOnStable ? {provide: NgZone, useClass: DebugNgZoneForCheckNoChanges} : [], | ||
options?.interval !== undefined ? exhaustiveCheckNoChangesInterval(options.interval) : [], | ||
]); | ||
} else { | ||
return makeEnvironmentProviders([]); | ||
} | ||
} | ||
|
||
@Injectable({providedIn: 'root'}) | ||
export class DebugNgZoneForCheckNoChanges extends NgZone { | ||
private applicationRef?: ApplicationRef; | ||
private scheduler?: ChangeDetectionSchedulerImpl; | ||
private errorHandler?: ErrorHandler; | ||
private readonly checkNoChangesMode = inject(EXHAUSTIVE_CHECK_NO_CHANGES_FOR_DEBUG) | ||
? CheckNoChangesMode.Exhaustive | ||
: CheckNoChangesMode.OnlyDirtyViews; | ||
|
||
constructor(injector: EnvironmentInjector) { | ||
// Use coalsecing to ensure we aren't ever running this check synchronously | ||
super({shouldCoalesceEventChangeDetection: true, shouldCoalesceRunChangeDetection: true}); | ||
|
||
// prevent emits to ensure code doesn't rely on these | ||
this.onMicrotaskEmpty.emit = () => {}; | ||
this.onStable.emit = () => { | ||
this.scheduler ||= injector.get(ChangeDetectionSchedulerImpl); | ||
if (this.scheduler.pendingRenderTaskId || this.scheduler.runningTick) { | ||
return; | ||
} | ||
this.applicationRef ||= injector.get(ApplicationRef); | ||
for (const view of this.applicationRef.allViews) { | ||
try { | ||
checkNoChangesInternal(view._lView, this.checkNoChangesMode, view.notifyErrorHandler); | ||
} catch (e) { | ||
this.errorHandler ||= injector.get(ErrorHandler); | ||
this.errorHandler.handleError(e); | ||
} | ||
} | ||
}; | ||
this.onUnstable.emit = () => {}; | ||
} | ||
} | ||
|
||
function exhaustiveCheckNoChangesInterval(interval: number) { | ||
return { | ||
provide: ENVIRONMENT_INITIALIZER, | ||
multi: true, | ||
useFactory: () => { | ||
const applicationRef = inject(ApplicationRef); | ||
const errorHandler = inject(ErrorHandler); | ||
const injector = inject(EnvironmentInjector); | ||
const scheduler = inject(ChangeDetectionSchedulerImpl); | ||
const ngZone = inject(NgZone); | ||
const checkNoChangesMode = inject(EXHAUSTIVE_CHECK_NO_CHANGES_FOR_DEBUG) | ||
? CheckNoChangesMode.Exhaustive | ||
: CheckNoChangesMode.OnlyDirtyViews; | ||
|
||
return () => { | ||
ngZone.runOutsideAngular(() => { | ||
const timer = setInterval(() => { | ||
if (scheduler.pendingRenderTaskId || scheduler.runningTick) { | ||
return; | ||
} | ||
for (const view of applicationRef.allViews) { | ||
try { | ||
checkNoChangesInternal(view._lView, checkNoChangesMode, view.notifyErrorHandler); | ||
} catch (e) { | ||
errorHandler.handleError(e); | ||
} | ||
} | ||
}, interval); | ||
|
||
injector.onDestroy(() => clearInterval(timer)); | ||
}); | ||
}; | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.