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 {