mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(core): check if transplanted views are attached to change detector (#46974)
Prevents change detection on views transplanted in OnPush components that have been detached from change detection. PR Close #46974
This commit is contained in:
parent
951d02e1de
commit
dbed2cf079
2 changed files with 128 additions and 6 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: `
|
||||
<ng-container #vc></ng-container>
|
||||
`,
|
||||
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: `
|
||||
<ng-container #vc></ng-container>
|
||||
`,
|
||||
})
|
||||
class CheckAlwaysComponent {
|
||||
@ViewChild('vc', {read: ViewContainerRef}) viewContainer!: ViewContainerRef;
|
||||
@Input() template!: TemplateRef<{}>;
|
||||
|
||||
createTemplate() {
|
||||
return this.viewContainer.createEmbeddedView(this.template);
|
||||
}
|
||||
}
|
||||
let fixture: ComponentFixture<App>;
|
||||
let appComponent: App;
|
||||
let onPushComponent: OnPushComponent;
|
||||
let checkAlwaysComponent: CheckAlwaysComponent;
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template #transplantedTemplate>{{ incrementChecks() }}</ng-template>
|
||||
<on-push-component [template]="transplantedTemplate"></on-push-component>
|
||||
<check-always-component [template]="transplantedTemplate"></check-always-component>
|
||||
`
|
||||
})
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue