fix(core): forbid destroyed views to be inserted or moved in VC

Fixes #18615
This commit is contained in:
Marc Laval 2017-08-07 15:59:38 +02:00 committed by Hans
parent d7be4f12b5
commit 972538be7a
2 changed files with 181 additions and 129 deletions

View File

@ -187,6 +187,9 @@ class ViewContainerRef_ implements ViewContainerData {
}
insert(viewRef: ViewRef, index?: number): ViewRef {
if (viewRef.destroyed) {
throw new Error('Cannot insert a destroyed View in a ViewContainer!');
}
const viewRef_ = <ViewRef_>viewRef;
const viewData = viewRef_._view;
attachEmbeddedView(this._view, this._data, index, viewData);
@ -195,6 +198,9 @@ class ViewContainerRef_ implements ViewContainerData {
}
move(viewRef: ViewRef_, currentIndex: number): ViewRef {
if (viewRef.destroyed) {
throw new Error('Cannot move a destroyed View in a ViewContainer!');
}
const previousIndex = this._embeddedViews.indexOf(viewRef._view);
moveEmbeddedView(this._data, previousIndex, currentIndex);
return viewRef;

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Compiler, ComponentFactory, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
import {Compiler, ComponentFactory, ComponentRef, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf, ViewRef} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
import {getDebugContext} from '@angular/core/src/errors';
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@ -1030,7 +1030,7 @@ function declareTests({useJit}: {useJit: boolean}) {
fixture.destroy();
});
describe('ViewContainerRef.createComponent', () => {
describe('ViewContainerRef', () => {
beforeEach(() => {
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
// factory doesn't get created
@ -1047,6 +1047,7 @@ function declareTests({useJit}: {useJit: boolean}) {
MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
});
describe('.createComponent', () => {
it('should allow to create a component at any bound location', async(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
@ -1149,7 +1150,8 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(compRef.instance.someToken).toBe('someValue');
});
it('should create a component with the NgModuleRef of the ComponentFactoryResolver', () => {
it('should create a component with the NgModuleRef of the ComponentFactoryResolver',
() => {
@Component({template: ''})
class RootComp {
constructor(public vc: ViewContainerRef) {}
@ -1175,11 +1177,13 @@ function declareTests({useJit}: {useJit: boolean}) {
class MyModule {
}
const compFixture =
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
const compFixture = TestBed.configureTestingModule({imports: [RootModule]})
.createComponent(RootComp);
const compiler = <Compiler>TestBed.get(Compiler);
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
const myCompFactory = myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
const myModule =
compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
const myCompFactory =
myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
// Note: MyComp was declared as entryComponent in MyModule,
// and we don't pass an explicit ModuleRef to the createComponent call.
@ -1189,6 +1193,40 @@ function declareTests({useJit}: {useJit: boolean}) {
});
});
describe('.insert', () => {
it('should throw with destroyed views', async(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
const ref = dynamicVp.create();
fixture.detectChanges();
ref.destroy();
expect(() => {
dynamicVp.insert(ref.hostView);
}).toThrowError('Cannot insert a destroyed View in a ViewContainer!');
}));
});
describe('.move', () => {
it('should throw with destroyed views', async(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp);
const tc = fixture.debugElement.children[0].children[0];
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
const ref = dynamicVp.create();
fixture.detectChanges();
ref.destroy();
expect(() => {
dynamicVp.move(ref.hostView, 1);
}).toThrowError('Cannot move a destroyed View in a ViewContainer!');
}));
});
});
it('should support static attributes', () => {
TestBed.configureTestingModule({declarations: [MyComp, NeedsAttribute]});
const template = '<input static type="text" title>';
@ -1856,7 +1894,15 @@ class DynamicViewport {
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService) !;
}
create() { this.vc.createComponent(this.componentFactory, this.vc.length, this.injector); }
create(): ComponentRef<ChildCompUsingService> {
return this.vc.createComponent(this.componentFactory, this.vc.length, this.injector);
}
insert(viewRef: ViewRef, index?: number): ViewRef { return this.vc.insert(viewRef, index); }
move(viewRef: ViewRef, currentIndex: number): ViewRef {
return this.vc.move(viewRef, currentIndex);
}
}
@Directive({selector: '[my-dir]', inputs: ['dirProp: elprop'], exportAs: 'mydir'})