fix(ivy): sync ViewRefs between multiple ViewContainerRefs (#30985)
Previously, multiple ViewContainerRef instances (obtained by injecting ViewContainerRef multiple times) each had private state that could be out of sync with actual LContainer, if views were inserted/removed/queried across the different instances. In particular each instance had its own array which tracked ViewRefs inserted via that instance. This commit moves the ViewRefs array onto the LContainer itself, so that it can be shared across multiple ViewContainerRef instances. A test is added that verifies ViewContainerRefs now provide a consistent view of the container. FW-1377 #resolve PR Close #30985
This commit is contained in:
parent
b1664425a9
commit
bd3b0564e6
|
@ -1459,7 +1459,8 @@ export function createLContainer(
|
|||
null, // next
|
||||
null, // queries
|
||||
tNode, // t_host
|
||||
native, // native
|
||||
native, // native,
|
||||
null, // view refs
|
||||
);
|
||||
ngDevMode && attachLContainerDebug(lContainer);
|
||||
return lContainer;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ViewRef} from '../../linker/view_ref';
|
||||
|
||||
import {TNode} from './node';
|
||||
import {LQueries} from './query';
|
||||
import {RComment, RElement} from './renderer';
|
||||
|
@ -28,6 +30,7 @@ export const ACTIVE_INDEX = 2;
|
|||
// PARENT, NEXT, QUERIES and T_HOST are indices 3, 4, 5 and 6.
|
||||
// As we already have these constants in LView, we don't need to re-create them.
|
||||
export const NATIVE = 7;
|
||||
export const VIEW_REFS = 8;
|
||||
|
||||
/**
|
||||
* Size of LContainer's header. Represents the index after which all views in the
|
||||
|
@ -35,7 +38,7 @@ export const NATIVE = 7;
|
|||
* which views are already in the DOM (and don't need to be re-added) and so we can
|
||||
* remove views from the DOM when they are no longer required.
|
||||
*/
|
||||
export const CONTAINER_HEADER_OFFSET = 8;
|
||||
export const CONTAINER_HEADER_OFFSET = 9;
|
||||
|
||||
/**
|
||||
* The state associated with a container.
|
||||
|
@ -99,6 +102,13 @@ export interface LContainer extends Array<any> {
|
|||
/** The comment element that serves as an anchor for this LContainer. */
|
||||
readonly[NATIVE]:
|
||||
RComment; // TODO(misko): remove as this value can be gotten by unwrapping `[HOST]`
|
||||
|
||||
/**
|
||||
* Array of `ViewRef`s used by any `ViewContainerRef`s that point to this container.
|
||||
*
|
||||
* This is lazily initialized by `ViewContainerRef` when the first view is inserted.
|
||||
*/
|
||||
[VIEW_REFS]: ViewRef[]|null;
|
||||
}
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
|
|
|
@ -19,7 +19,7 @@ import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
|
|||
|
||||
import {NodeInjector, getParentInjectorLocation} from './di';
|
||||
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from './interfaces/container';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view';
|
||||
|
@ -174,8 +174,6 @@ export function createContainerRef(
|
|||
if (!R3ViewContainerRef) {
|
||||
// TODO: Fix class name, should be ViewContainerRef, but there appears to be a rollup bug
|
||||
R3ViewContainerRef = class ViewContainerRef_ extends ViewContainerRefToken {
|
||||
private _viewRefs: viewEngine_ViewRef[] = [];
|
||||
|
||||
constructor(
|
||||
private _lContainer: LContainer,
|
||||
private _hostTNode: TElementNode|TContainerNode|TElementContainerNode,
|
||||
|
@ -206,7 +204,9 @@ export function createContainerRef(
|
|||
}
|
||||
}
|
||||
|
||||
get(index: number): viewEngine_ViewRef|null { return this._viewRefs[index] || null; }
|
||||
get(index: number): viewEngine_ViewRef|null {
|
||||
return this._lContainer[VIEW_REFS] !== null && this._lContainer[VIEW_REFS] ![index] || null;
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
// Note that if there are no views, the container
|
||||
|
@ -217,11 +217,12 @@ export function createContainerRef(
|
|||
|
||||
createEmbeddedView<C>(templateRef: ViewEngine_TemplateRef<C>, context?: C, index?: number):
|
||||
viewEngine_EmbeddedViewRef<C> {
|
||||
this.allocateContainerIfNeeded();
|
||||
const adjustedIdx = this._adjustIndex(index);
|
||||
const viewRef = (templateRef as any)
|
||||
.createEmbeddedView(context || <any>{}, this._lContainer, adjustedIdx);
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
||||
this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 0, viewRef);
|
||||
return viewRef;
|
||||
}
|
||||
|
||||
|
@ -244,6 +245,7 @@ export function createContainerRef(
|
|||
if (viewRef.destroyed) {
|
||||
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
|
||||
}
|
||||
this.allocateContainerIfNeeded();
|
||||
const lView = (viewRef as ViewRef<any>)._lView !;
|
||||
const adjustedIdx = this._adjustIndex(index);
|
||||
|
||||
|
@ -259,7 +261,7 @@ export function createContainerRef(
|
|||
addRemoveViewFromContainer(lView, true, beforeNode);
|
||||
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
||||
this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 0, viewRef);
|
||||
|
||||
return viewRef;
|
||||
}
|
||||
|
@ -274,18 +276,24 @@ export function createContainerRef(
|
|||
return viewRef;
|
||||
}
|
||||
|
||||
indexOf(viewRef: viewEngine_ViewRef): number { return this._viewRefs.indexOf(viewRef); }
|
||||
indexOf(viewRef: viewEngine_ViewRef): number {
|
||||
return this._lContainer[VIEW_REFS] !== null ?
|
||||
this._lContainer[VIEW_REFS] !.indexOf(viewRef) :
|
||||
0;
|
||||
}
|
||||
|
||||
remove(index?: number): void {
|
||||
this.allocateContainerIfNeeded();
|
||||
const adjustedIdx = this._adjustIndex(index, -1);
|
||||
removeView(this._lContainer, adjustedIdx);
|
||||
this._viewRefs.splice(adjustedIdx, 1);
|
||||
this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 1);
|
||||
}
|
||||
|
||||
detach(index?: number): viewEngine_ViewRef|null {
|
||||
this.allocateContainerIfNeeded();
|
||||
const adjustedIdx = this._adjustIndex(index, -1);
|
||||
const view = detachView(this._lContainer, adjustedIdx);
|
||||
const wasDetached = view && this._viewRefs.splice(adjustedIdx, 1)[0] != null;
|
||||
const wasDetached = view && this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 1)[0] != null;
|
||||
return wasDetached ? new ViewRef(view !, view ![CONTEXT], -1) : null;
|
||||
}
|
||||
|
||||
|
@ -300,6 +308,12 @@ export function createContainerRef(
|
|||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private allocateContainerIfNeeded(): void {
|
||||
if (this._lContainer[VIEW_REFS] === null) {
|
||||
this._lContainer[VIEW_REFS] = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1202,6 +1202,37 @@ describe('di', () => {
|
|||
// Each ViewContainerRef instance should be unique
|
||||
expect(otherDirective.isSameInstance).toBe(false);
|
||||
});
|
||||
|
||||
it('should sync ViewContainerRef state between all injected instances', () => {
|
||||
@Component({
|
||||
selector: 'root',
|
||||
template: `<ng-template #tmpl>Test</ng-template>`,
|
||||
})
|
||||
class Root {
|
||||
@ViewChild(TemplateRef, {static: true})
|
||||
tmpl !: TemplateRef<any>;
|
||||
|
||||
constructor(public vcr: ViewContainerRef, public vcr2: ViewContainerRef) {}
|
||||
|
||||
ngOnInit(): void { this.vcr.createEmbeddedView(this.tmpl); }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Root],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Root);
|
||||
fixture.detectChanges();
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
expect(cmp.vcr.length).toBe(1);
|
||||
expect(cmp.vcr2.length).toBe(1);
|
||||
expect(cmp.vcr2.get(0)).toEqual(cmp.vcr.get(0));
|
||||
|
||||
cmp.vcr2.remove(0);
|
||||
expect(cmp.vcr.length).toBe(0);
|
||||
expect(cmp.vcr.get(0)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChangeDetectorRef', () => {
|
||||
|
|
|
@ -278,6 +278,9 @@
|
|||
{
|
||||
"name": "UnsubscriptionErrorImpl"
|
||||
},
|
||||
{
|
||||
"name": "VIEW_REFS"
|
||||
},
|
||||
{
|
||||
"name": "ViewContainerRef"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue