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 { 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 viewRef_ = <ViewRef_>viewRef;
const viewData = viewRef_._view; const viewData = viewRef_._view;
attachEmbeddedView(this._view, this._data, index, viewData); attachEmbeddedView(this._view, this._data, index, viewData);
@ -195,6 +198,9 @@ class ViewContainerRef_ implements ViewContainerData {
} }
move(viewRef: ViewRef_, currentIndex: number): ViewRef { 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); const previousIndex = this._embeddedViews.indexOf(viewRef._view);
moveEmbeddedView(this._data, previousIndex, currentIndex); moveEmbeddedView(this._data, previousIndex, currentIndex);
return viewRef; return viewRef;

View File

@ -7,7 +7,7 @@
*/ */
import {CommonModule} from '@angular/common'; 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 {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
import {getDebugContext} from '@angular/core/src/errors'; import {getDebugContext} from '@angular/core/src/errors';
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver'; import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@ -1030,7 +1030,7 @@ function declareTests({useJit}: {useJit: boolean}) {
fixture.destroy(); fixture.destroy();
}); });
describe('ViewContainerRef.createComponent', () => { describe('ViewContainerRef', () => {
beforeEach(() => { beforeEach(() => {
// we need a module to declarate ChildCompUsingService as an entryComponent otherwise the // we need a module to declarate ChildCompUsingService as an entryComponent otherwise the
// factory doesn't get created // factory doesn't get created
@ -1047,6 +1047,7 @@ function declareTests({useJit}: {useJit: boolean}) {
MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}}); MyComp, {add: {template: '<div><dynamic-vp #dynamic></dynamic-vp></div>'}});
}); });
describe('.createComponent', () => {
it('should allow to create a component at any bound location', async(() => { it('should allow to create a component at any bound location', async(() => {
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(MyComp); .createComponent(MyComp);
@ -1149,7 +1150,8 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(compRef.instance.someToken).toBe('someValue'); 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: ''}) @Component({template: ''})
class RootComp { class RootComp {
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
@ -1175,11 +1177,13 @@ function declareTests({useJit}: {useJit: boolean}) {
class MyModule { class MyModule {
} }
const compFixture = const compFixture = TestBed.configureTestingModule({imports: [RootModule]})
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp); .createComponent(RootComp);
const compiler = <Compiler>TestBed.get(Compiler); const compiler = <Compiler>TestBed.get(Compiler);
const myModule = compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef)); const myModule =
const myCompFactory = myModule.componentFactoryResolver.resolveComponentFactory(MyComp); compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
const myCompFactory =
myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
// Note: MyComp was declared as entryComponent in MyModule, // Note: MyComp was declared as entryComponent in MyModule,
// and we don't pass an explicit ModuleRef to the createComponent call. // 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', () => { it('should support static attributes', () => {
TestBed.configureTestingModule({declarations: [MyComp, NeedsAttribute]}); TestBed.configureTestingModule({declarations: [MyComp, NeedsAttribute]});
const template = '<input static type="text" title>'; const template = '<input static type="text" title>';
@ -1856,7 +1894,15 @@ class DynamicViewport {
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService) !; 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'}) @Directive({selector: '[my-dir]', inputs: ['dirProp: elprop'], exportAs: 'mydir'})