diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index e3f5980a0d..41690362cc 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -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; diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 397c2eba52..209aefc0d3 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -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 { /** 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 diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index d6fde0e553..10e6cc750c 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -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(templateRef: ViewEngine_TemplateRef, context?: C, index?: number): viewEngine_EmbeddedViewRef { + this.allocateContainerIfNeeded(); const adjustedIdx = this._adjustIndex(index); const viewRef = (templateRef as any) .createEmbeddedView(context || {}, this._lContainer, adjustedIdx); (viewRef as ViewRef).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)._lView !; const adjustedIdx = this._adjustIndex(index); @@ -259,7 +261,7 @@ export function createContainerRef( addRemoveViewFromContainer(lView, true, beforeNode); (viewRef as ViewRef).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] = []; + } + } }; } diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index cdb9c2bc63..dc4d4d68d0 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -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: `Test`, + }) + class Root { + @ViewChild(TemplateRef, {static: true}) + tmpl !: TemplateRef; + + 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', () => { diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index bb17abe483..0bce877913 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -278,6 +278,9 @@ { "name": "UnsubscriptionErrorImpl" }, + { + "name": "VIEW_REFS" + }, { "name": "ViewContainerRef" },