-
Notifications
You must be signed in to change notification settings - Fork 26.2k
ExpressionChangedAfterItHasBeenCheckedError is not thrown if ChangeDetection.OnPush is used #45612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Indeed, this is a known issue. It would be very breaky to enable this check for |
What is the progress on this issue? I found this issue only because I couldn't believe that suddenly ALL "ExpressionChangedAfterItHasBeenCheckedError" were fixed in our app after changing all components CDS to OnPush. All those errors just magically disappeared from our application... too good to be true :D |
As I understand it, this isn't just about the warning. The warning is telling the developer that the code isn't working the way they intended. In our case, we rearranged some code resulting in a fetch being triggered by an @input setter. Since the fetch was async, it still worked, and we didn't realize right away that we had lost the loading spinner. It's interesting that calling checkNoChanges caused a lot of breakages. I wonder how many of those are really bugs. Can it at least be main opt-in? Thanks! |
All of them are bugs! The sad thing is that projects have actually used If this will be fixed this will indeed have to be opt-in, to avoid a large scale breaking change. We are reevaluating change detection details in support of the signal based components RFC, but there are not yet concrete plans on how |
…eing refreshed This commit updates the internal `markViewDirty` logic to avoid marking views dirty up to the root when we encounter a view that is already being refreshed. This is wasteful because we clear the dirty bit at the end of refreshing a view anyways so it really has no affect on that view. This change should reduce the number of tree traversals for replay observables and async pipe. For these cases, when we execute change detection on the view for the first time, the async pipe subscribes to the observable and its value emits synchronously. `markForCheck` is then called inside `AsyncPipe`, which traverses all the way up to the root view. This happens for _every_ `AsyncPipe` binding that emits synchronously on subscription. Sometimes we can reach a view and refresh it when the parents have been skipped. For example, this happens with views that have updated signals. Ancestors are only traversed and not refreshed. Marking dirty all the way up to the root in this case can result in those ancestor views being marked with the dirty flag and it not being cleared. This only became problematic when we started to support backwards referenced transplanted views and changed signal change detection to not mark ancestors for check. In addition to that, the loop in change detection support to remove `ExpressionChanged...` errors for these cases can potentially cause infinite loops in rare cases when these views are refreshed at a time when parents have only been traversed and `markForCheck` is called. Prior to the loop, if this `markForCheck` was combined with an actual change to binding values, this would cause `ExpressionChangedAfterItWasCheckedError`. For regular cases where we are refreshing a view, update a binding, and call `markForCheck`, this is either `ExpressionChanged...` in views with default change detection or no error at all (but without any state update) in views that are `OnPush` since the `Dirty` flag is cleared at the end of `refreshView` and `checkNoChanges` uses the same `Dirty` bit for traversal (angular#45612). The only case where this might cause different and unexpected behavior is if change detection starts in the middle of a view tree via `ChangeDetectorRef.detectChanges` _and_ a state update happens where the state is read in a view above the root of the traversal along with `markForCheck`. This might be the case for updating host bindings during change detection. That said, we have never really intended to support updating state during change detection, especially in a location above the current spot in the current view tree. This violates unidirectional data flow, a principle we have always intended to enforce outside of using signals for state.
This commit adds a feature that is 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. Because this is an experimental, debug-only feature, it is okay to merge during the RC period.
…eck (#55663) This commit adds a feature that is 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 #45612. Because this is an experimental, debug-only feature, it is okay to merge during the RC period. PR Close #55663
…eck (#55663) This commit adds a feature that is 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 #45612. Because this is an experimental, debug-only feature, it is okay to merge during the RC period. PR Close #55663
…ideCheckNoChangesConfig` This commit makes several changes changes to the `provideExperimentalCheckNoChangesForDebug` API: * Rename it * Promote to dev preview * Apply the exhaustive behavior to _all_ checkNoChanges runs * Remove `useNgZoneOnStable` option. This wasn't found to be generally more useful than `interval` fixes angular#45612 BREAKING CHANGE: `provideExperimentalCheckNoChangesForDebug` has several breaking changes: * It is renamed to `provideCheckNoChangesConfig` * The behavior applies to _all_ checkNoChanges runs * The `useNgZoneOnStable` option is removed. This wasn't found to be generally more useful than `interval`
When components use `OnPush`, they prevent `ExpressionHasChanged...` from being thrown when it should (angular/angular#45612). The exhaustive option ignores dirty flags and traverses the entire view tree checking for changes. This commit configures component tests to do this by default and opts out any tests with existing issues.
When components use `OnPush`, they prevent `ExpressionHasChanged...` from being thrown when it should (angular/angular#45612). The exhaustive option ignores dirty flags and traverses the entire view tree checking for changes. This commit configures component tests to do this by default and opts out any tests with existing issues.
…ult (#31069) When components use `OnPush`, they prevent `ExpressionHasChanged...` from being thrown when it should (angular/angular#45612). The exhaustive option ignores dirty flags and traverses the entire view tree checking for changes. This commit configures component tests to do this by default and opts out any tests with existing issues.
…ult (#31069) When components use `OnPush`, they prevent `ExpressionHasChanged...` from being thrown when it should (angular/angular#45612). The exhaustive option ignores dirty flags and traverses the entire view tree checking for changes. This commit configures component tests to do this by default and opts out any tests with existing issues. (cherry picked from commit 839dfba)
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
No
Description
When changing a template value while the change detection is taking place, the user is warned with an
ExpressionChangedAfterItHasBeenCheckedError
.This is not the case for components which use the
ChangeDetectionStrategy.OnPush
.By doing some debugging I found the problem:
markForCheck
for the component, while change detection is running.This results in no ExpressionChangedAfterItHasBeenCheckedError because the view is not checked, since its dirty status has been cleared in the change detection process
This can be really hard to debug without the warning. So I found a workaround for now, that is to add
|| isInCheckNoChangesMode()
to the refreshView check so that it becomesif (componentView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty) || isInCheckNoChangesMode()) {
. This is very ugly and imperformant, but at least it helps detect those errors.Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-ivy-ked7hw?file=src/app/app.component.ts
Please provide the exception or error you saw
No response
Please provide the environment you discovered this bug in (run
ng version
)Anything else?
No response
The text was updated successfully, but these errors were encountered: