diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 004a15b22f9..acbe9ff0ea8 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -1664,12 +1664,16 @@ function refreshContainsDirtyView(lView: LView) { lContainer = getNextLContainer(lContainer)) { for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) { const embeddedLView = lContainer[i]; - if (embeddedLView[FLAGS] & LViewFlags.RefreshTransplantedView) { - const embeddedTView = embeddedLView[TVIEW]; - ngDevMode && assertDefined(embeddedTView, 'TView must be allocated'); - refreshView(embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!); - } else if (embeddedLView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) { - refreshContainsDirtyView(embeddedLView); + if (viewAttachedToChangeDetector(embeddedLView)) { + if (embeddedLView[FLAGS] & LViewFlags.RefreshTransplantedView) { + const embeddedTView = embeddedLView[TVIEW]; + ngDevMode && assertDefined(embeddedTView, 'TView must be allocated'); + refreshView( + embeddedTView, embeddedLView, embeddedTView.template, embeddedLView[CONTEXT]!); + + } else if (embeddedLView[TRANSPLANTED_VIEWS_TO_REFRESH] > 0) { + refreshContainsDirtyView(embeddedLView); + } } } } diff --git a/packages/core/test/acceptance/change_detection_transplanted_view_spec.ts b/packages/core/test/acceptance/change_detection_transplanted_view_spec.ts index b905ea6bef6..d0e13be634c 100644 --- a/packages/core/test/acceptance/change_detection_transplanted_view_spec.ts +++ b/packages/core/test/acceptance/change_detection_transplanted_view_spec.ts @@ -627,6 +627,124 @@ describe('change detection for transplanted views', () => { viewRef.detectChanges(); expect(component.checks).toEqual(1); }); + + describe('when detached', () => { + @Component({ + selector: 'on-push-component', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush + }) + class OnPushComponent { + @ViewChild('vc', {read: ViewContainerRef}) viewContainer!: ViewContainerRef; + @Input() template!: TemplateRef<{}>; + + createTemplate() { + return this.viewContainer.createEmbeddedView(this.template); + } + } + + @Component({ + selector: 'check-always-component', + template: ` + + `, + }) + class CheckAlwaysComponent { + @ViewChild('vc', {read: ViewContainerRef}) viewContainer!: ViewContainerRef; + @Input() template!: TemplateRef<{}>; + + createTemplate() { + return this.viewContainer.createEmbeddedView(this.template); + } + } + let fixture: ComponentFixture; + let appComponent: App; + let onPushComponent: OnPushComponent; + let checkAlwaysComponent: CheckAlwaysComponent; + + @Component({ + template: ` + {{ incrementChecks() }} + + + ` + }) + class App { + @ViewChild(OnPushComponent) onPushComponent!: OnPushComponent; + @ViewChild(CheckAlwaysComponent) checkAlwaysComponent!: CheckAlwaysComponent; + transplantedViewRefreshCount = 0; + incrementChecks() { + this.transplantedViewRefreshCount++; + } + } + beforeEach(() => { + TestBed.configureTestingModule({declarations: [App, OnPushComponent, CheckAlwaysComponent]}); + fixture = TestBed.createComponent(App); + fixture.detectChanges(); + appComponent = fixture.componentInstance; + onPushComponent = appComponent.onPushComponent; + checkAlwaysComponent = appComponent.checkAlwaysComponent; + }); + describe('inside OnPush components', () => { + it('should detect changes when attached', () => { + onPushComponent.createTemplate(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + }); + + it('should not detect changes', () => { + const viewRef = onPushComponent.createTemplate(); + viewRef.detach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(0); + viewRef.reattach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + }); + + it('should not detect changes on mixed detached/attached refs', () => { + onPushComponent.createTemplate(); + const viewRef = onPushComponent.createTemplate(); + viewRef.detach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + viewRef.reattach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(3); + }); + }); + + describe('inside CheckAlways component', () => { + it('should detect changes when attached', () => { + checkAlwaysComponent.createTemplate(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + }); + + it('should not detect changes', () => { + const viewRef = checkAlwaysComponent.createTemplate(); + viewRef.detach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(0); + viewRef.reattach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + }); + + it('should not detect changes on mixed detached/attached refs', () => { + checkAlwaysComponent.createTemplate(); + const viewRef = checkAlwaysComponent.createTemplate(); + viewRef.detach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(1); + viewRef.reattach(); + fixture.detectChanges(false); + expect(appComponent.transplantedViewRefreshCount).toEqual(3); + }); + }); + }); }); function trim(text: string|null): string {