Skip to content

Commit 58ed76b

Browse files
atscottalxhub
authored andcommitted
fix(core): Avoid refreshing a host view twice when using transplanted views (#53021)
This change fixes and issue where the expectation was that change detection always goes through `detectChangesInView`. In reality, `detectChangesInternal` directly calls `refreshView` and refreshes a view directly without checking if it was dirty (to my discontent). This update changes the implementation of `detectChangesInternal` to actually be "detect changes" not "force refresh of root view and detect changes". In addition, it adds the refresh flag to APIs that were previously calling `detectChangesInternal` so we get the same behavior as before (host view is forced to refresh). Note that the use of `RefreshView` instead of `Dirty` is _intentional_ here. The `RefreshView` flag is cleared before refreshing the view while the `Dirty` flag is cleared at the very end. Using the `Dirty` flag could have consequences because it is a more long-lasting change to the view flags. Because `detectChangesInView` will immediately clear the `RefreshView` flag, this change is much more limited and does not result in a different set of flags during the view refresh. PR Close #53021
1 parent 543df3d commit 58ed76b

File tree

4 files changed

+12
-8
lines changed

4 files changed

+12
-8
lines changed

packages/core/src/render3/instructions/change_detection.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ export function detectChangesInternal(lView: LView, notifyErrorHandler = true) {
4343
}
4444

4545
try {
46-
const tView = lView[TVIEW];
47-
const context = lView[CONTEXT];
48-
refreshView(tView, lView, tView.template, context);
4946
detectChangesInViewWhileDirty(lView);
5047
} catch (error) {
5148
if (notifyErrorHandler) {
@@ -67,6 +64,8 @@ export function detectChangesInternal(lView: LView, notifyErrorHandler = true) {
6764
}
6865

6966
function detectChangesInViewWhileDirty(lView: LView) {
67+
detectChangesInView(lView, ChangeDetectionMode.Global);
68+
7069
let retries = 0;
7170
// If after running change detection, this view still needs to be refreshed or there are
7271
// descendants views that need to be refreshed due to re-dirtying during the change detection

packages/core/src/render3/util/change_detection_utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {assertDefined} from '../../util/assert';
1010
import {getComponentViewByInstance} from '../context_discovery';
1111
import {detectChangesInternal} from '../instructions/change_detection';
1212
import {markViewDirty} from '../instructions/mark_view_dirty';
13-
import {TVIEW} from '../interfaces/view';
13+
import {FLAGS, LViewFlags} from '../interfaces/view';
1414

1515
import {getRootComponents} from './discovery_utils';
1616

@@ -38,5 +38,6 @@ export function applyChanges(component: {}): void {
3838
*/
3939
function detectChanges(component: {}): void {
4040
const view = getComponentViewByInstance(component);
41+
view[FLAGS] |= LViewFlags.RefreshView;
4142
detectChangesInternal(view);
4243
}

packages/core/src/render3/view_ref.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ export class ViewRef<T> implements EmbeddedViewRef<T>, ChangeDetectorRefInterfac
284284
* See {@link ChangeDetectorRef#detach} for more information.
285285
*/
286286
detectChanges(): void {
287+
// Add `RefreshView` flag to ensure this view is refreshed if not already dirty.
288+
// `RefreshView` flag is used intentionally over `Dirty` because it gets cleared before
289+
// executing any of the actual refresh code while the `Dirty` flag doesn't get cleared
290+
// until the end of the refresh. Using `RefreshView` prevents creating a potential difference
291+
// in the state of the LViewFlags during template execution.
292+
this._lView[FLAGS] |= LViewFlags.RefreshView;
287293
detectChangesInternal(this._lView, this.notifyErrorHandler);
288294
}
289295

packages/core/test/acceptance/change_detection_transplanted_view_spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -734,8 +734,7 @@ describe('change detection for transplanted views', () => {
734734
// Detach view, manually call `detectChanges`, and verify the template was refreshed
735735
component.rootViewContainerRef.detach();
736736
viewRef.detectChanges();
737-
// This view is a backwards reference so it's refreshed twice
738-
expect(component.checks).toEqual(2);
737+
expect(component.checks).toEqual(1);
739738
});
740739

741740
it('should work when change detecting detached transplanted view already marked for refresh',
@@ -751,8 +750,7 @@ describe('change detection for transplanted views', () => {
751750
// should not affect parent counters.
752751
viewRef.detectChanges();
753752
}).not.toThrow();
754-
// This view is a backwards reference so it's refreshed twice
755-
expect(component.checks).toEqual(2);
753+
expect(component.checks).toEqual(1);
756754
});
757755

758756
it('should work when re-inserting a previously detached transplanted view marked for refresh',

0 commit comments

Comments
 (0)