perf(core): Avoid traversal when markForCheck
is called on a view being refreshed
#54347
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.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 andmarkForCheck
is called. Prior to the loop, if thismarkForCheck
was combined with an actual change to binding values, this would causeExpressionChangedAfterItWasCheckedError
.For regular cases where we are refreshing a view, update a binding, and call
markForCheck
, this is eitherExpressionChanged...
in views with default change detection or no error at all (but without any state update) in views that areOnPush
since theDirty
flag is cleared at the end ofrefreshView
andcheckNoChanges
uses the sameDirty
bit for traversal (#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 withmarkForCheck
. This might be the case for updating host bindings during change detection. That said, we have never really intented 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 intented to enforce outside of using signals for state.