fix(ivy): error when calling remove() or detach() on an empty view container (#29986)

Fixes Ivy throwing an error if `remove()` or `detach()` are called on an empty `ViewContainerRef`.

This PR resolves FW-1270.

PR Close #29986
This commit is contained in:
Kristiyan Kostadinov 2019-04-19 19:46:52 +02:00 committed by Ben Lesh
parent 2271f200d7
commit 6ae0084255
3 changed files with 47 additions and 23 deletions

View File

@ -371,9 +371,10 @@ export function insertView(lView: LView, lContainer: LContainer, index: number)
* @param removeIndex The index of the view to detach * @param removeIndex The index of the view to detach
* @returns Detached LView instance. * @returns Detached LView instance.
*/ */
export function detachView(lContainer: LContainer, removeIndex: number): LView { export function detachView(lContainer: LContainer, removeIndex: number): LView|undefined {
const views = lContainer[VIEWS]; const views = lContainer[VIEWS];
const viewToDetach = views[removeIndex]; const viewToDetach = views[removeIndex];
if (viewToDetach) {
if (removeIndex > 0) { if (removeIndex > 0) {
views[removeIndex - 1][NEXT] = viewToDetach[NEXT] as LView; views[removeIndex - 1][NEXT] = viewToDetach[NEXT] as LView;
} }
@ -388,6 +389,7 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView {
viewToDetach[NEXT] = null; viewToDetach[NEXT] = null;
// Unsets the attached flag // Unsets the attached flag
viewToDetach[FLAGS] &= ~LViewFlags.Attached; viewToDetach[FLAGS] &= ~LViewFlags.Attached;
}
return viewToDetach; return viewToDetach;
} }
@ -399,9 +401,11 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView {
*/ */
export function removeView(lContainer: LContainer, removeIndex: number) { export function removeView(lContainer: LContainer, removeIndex: number) {
const view = lContainer[VIEWS][removeIndex]; const view = lContainer[VIEWS][removeIndex];
if (view) {
detachView(lContainer, removeIndex); detachView(lContainer, removeIndex);
destroyLView(view); destroyLView(view);
} }
}
/** /**
* A standalone function which destroys an LView, * A standalone function which destroys an LView,

View File

@ -281,8 +281,8 @@ export function createContainerRef(
detach(index?: number): viewEngine_ViewRef|null { detach(index?: number): viewEngine_ViewRef|null {
const adjustedIdx = this._adjustIndex(index, -1); const adjustedIdx = this._adjustIndex(index, -1);
const view = detachView(this._lContainer, adjustedIdx); const view = detachView(this._lContainer, adjustedIdx);
const wasDetached = this._viewRefs.splice(adjustedIdx, 1)[0] != null; const wasDetached = view && this._viewRefs.splice(adjustedIdx, 1)[0] != null;
return wasDetached ? new ViewRef(view, view[CONTEXT], -1) : null; return wasDetached ? new ViewRef(view !, view ![CONTEXT], -1) : null;
} }
private _adjustIndex(index?: number, shift: number = 0) { private _adjustIndex(index?: number, shift: number = 0) {

View File

@ -113,6 +113,26 @@ describe('ViewContainerRef', () => {
}); });
}); });
it('should not throw when calling remove() on an empty container', () => {
const fixture = TestBed.createComponent(ViewContainerRefApp);
fixture.detectChanges();
const viewContainerRef = fixture.componentInstance.vcrComp.vcr;
expect(viewContainerRef.length).toBe(0);
expect(() => viewContainerRef.remove()).not.toThrow();
});
it('should not throw when calling detach() on an empty container', () => {
const fixture = TestBed.createComponent(ViewContainerRefApp);
fixture.detectChanges();
const viewContainerRef = fixture.componentInstance.vcrComp.vcr;
expect(viewContainerRef.length).toBe(0);
expect(() => viewContainerRef.detach()).not.toThrow();
});
describe('destroy should clean the DOM in all cases:', () => { describe('destroy should clean the DOM in all cases:', () => {
function executeTest(template: string) { function executeTest(template: string) {
TestBed.overrideTemplate(DestroyCasesComp, template).configureTestingModule({ TestBed.overrideTemplate(DestroyCasesComp, template).configureTestingModule({