mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(core): HMR not matching component that injects ViewContainerRef (#59596)
If a component injects `ViewContainerRef`, its `LView` gets wrapped in an empty `LContainer` and the container's host becomes the `LView`. The HMR logic wasn't accounting for this which meant that such components wouldn't be replaced. Fixes #59592. PR Close #59596
This commit is contained in:
committed by
Pawel Kozlowski
parent
70c4b83509
commit
6f7716268a
@@ -109,8 +109,13 @@ function recreateMatchingLViews(oldDef: ComponentDef<unknown>, rootLView: LView)
|
||||
const current = rootLView[i];
|
||||
|
||||
if (isLContainer(current)) {
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < current.length; i++) {
|
||||
recreateMatchingLViews(oldDef, current[i]);
|
||||
// The host can be an LView if a component is injecting `ViewContainerRef`.
|
||||
if (isLView(current[HOST])) {
|
||||
recreateMatchingLViews(oldDef, current[HOST]);
|
||||
}
|
||||
|
||||
for (let j = CONTAINER_HEADER_OFFSET; j < current.length; j++) {
|
||||
recreateMatchingLViews(oldDef, current[j]);
|
||||
}
|
||||
} else if (isLView(current)) {
|
||||
recreateMatchingLViews(oldDef, current);
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Type,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
ViewContainerRef,
|
||||
ɵNG_COMP_DEF,
|
||||
ɵɵreplaceMetadata,
|
||||
} from '@angular/core';
|
||||
@@ -416,6 +417,58 @@ describe('hot module replacement', () => {
|
||||
verifyNodesWereRecreated(recreatedNodes);
|
||||
});
|
||||
|
||||
it('should be able to replace a component that injects ViewContainerRef', () => {
|
||||
const initialMetadata: Component = {
|
||||
selector: 'child-cmp',
|
||||
standalone: true,
|
||||
template: 'Hello <strong>world</strong>',
|
||||
};
|
||||
|
||||
@Component(initialMetadata)
|
||||
class ChildCmp {
|
||||
vcr = inject(ViewContainerRef);
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [ChildCmp],
|
||||
template: '<child-cmp/>',
|
||||
})
|
||||
class RootCmp {}
|
||||
|
||||
const fixture = TestBed.createComponent(RootCmp);
|
||||
fixture.detectChanges();
|
||||
markNodesAsCreatedInitially(fixture.nativeElement);
|
||||
|
||||
expectHTML(
|
||||
fixture.nativeElement,
|
||||
`
|
||||
<child-cmp>
|
||||
Hello <strong>world</strong>
|
||||
</child-cmp>
|
||||
`,
|
||||
);
|
||||
|
||||
replaceMetadata(ChildCmp, {
|
||||
...initialMetadata,
|
||||
template: `Hello <i>Bob</i>!`,
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const recreatedNodes = childrenOf(...fixture.nativeElement.querySelectorAll('child-cmp'));
|
||||
verifyNodesRemainUntouched(fixture.nativeElement, recreatedNodes);
|
||||
verifyNodesWereRecreated(recreatedNodes);
|
||||
|
||||
expectHTML(
|
||||
fixture.nativeElement,
|
||||
`
|
||||
<child-cmp>
|
||||
Hello <i>Bob</i>!
|
||||
</child-cmp>
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('queries', () => {
|
||||
it('should update ViewChildren query results', async () => {
|
||||
@Component({
|
||||
|
||||
Reference in New Issue
Block a user