fix(core): forbid destroyed views to be inserted or moved in VC
Fixes #18615
This commit is contained in:
parent
d7be4f12b5
commit
972538be7a
@ -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;
|
||||||
|
@ -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,146 +1047,184 @@ 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>'}});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow to create a component at any bound location', async(() => {
|
describe('.createComponent', () => {
|
||||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
it('should allow to create a component at any bound location', async(() => {
|
||||||
.createComponent(MyComp);
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
const tc = fixture.debugElement.children[0].children[0];
|
.createComponent(MyComp);
|
||||||
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
dynamicVp.create();
|
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
||||||
fixture.detectChanges();
|
dynamicVp.create();
|
||||||
expect(fixture.debugElement.children[0].children[1].nativeElement)
|
fixture.detectChanges();
|
||||||
.toHaveText('dynamic greet');
|
expect(fixture.debugElement.children[0].children[1].nativeElement)
|
||||||
}));
|
.toHaveText('dynamic greet');
|
||||||
|
}));
|
||||||
|
|
||||||
it('should allow to create multiple components at a location', async(() => {
|
it('should allow to create multiple components at a location', async(() => {
|
||||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
.createComponent(MyComp);
|
.createComponent(MyComp);
|
||||||
const tc = fixture.debugElement.children[0].children[0];
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
||||||
dynamicVp.create();
|
dynamicVp.create();
|
||||||
dynamicVp.create();
|
dynamicVp.create();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.children[0].children[1].nativeElement)
|
expect(fixture.debugElement.children[0].children[1].nativeElement)
|
||||||
.toHaveText('dynamic greet');
|
.toHaveText('dynamic greet');
|
||||||
expect(fixture.debugElement.children[0].children[2].nativeElement)
|
expect(fixture.debugElement.children[0].children[2].nativeElement)
|
||||||
.toHaveText('dynamic greet');
|
.toHaveText('dynamic greet');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create a component that has been freshly compiled', () => {
|
it('should create a component that has been freshly compiled', () => {
|
||||||
@Component({template: ''})
|
@Component({template: ''})
|
||||||
class RootComp {
|
class RootComp {
|
||||||
constructor(public vc: ViewContainerRef) {}
|
constructor(public vc: ViewContainerRef) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [RootComp],
|
declarations: [RootComp],
|
||||||
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
})
|
})
|
||||||
class RootModule {
|
class RootModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({template: ''})
|
@Component({template: ''})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
constructor(@Inject('someToken') public someToken: string) {}
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [MyComp],
|
declarations: [MyComp],
|
||||||
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
||||||
})
|
})
|
||||||
class MyModule {
|
class MyModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const compFixture =
|
const compFixture =
|
||||||
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
|
TestBed.configureTestingModule({imports: [RootModule]}).createComponent(RootComp);
|
||||||
const compiler = <Compiler>TestBed.get(Compiler);
|
const compiler = <Compiler>TestBed.get(Compiler);
|
||||||
const myCompFactory =
|
const myCompFactory =
|
||||||
<ComponentFactory<MyComp>>compiler.compileModuleAndAllComponentsSync(MyModule)
|
<ComponentFactory<MyComp>>compiler.compileModuleAndAllComponentsSync(MyModule)
|
||||||
.componentFactories[0];
|
.componentFactories[0];
|
||||||
|
|
||||||
// Note: the ComponentFactory was created directly via the compiler, i.e. it
|
// Note: the ComponentFactory was created directly via the compiler, i.e. it
|
||||||
// does not have an association to an NgModuleRef.
|
// does not have an association to an NgModuleRef.
|
||||||
// -> expect the providers of the module that the view container belongs to.
|
// -> expect the providers of the module that the view container belongs to.
|
||||||
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
||||||
expect(compRef.instance.someToken).toBe('someRootValue');
|
expect(compRef.instance.someToken).toBe('someRootValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a component with the passed NgModuleRef', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class RootComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComp, MyComp],
|
||||||
|
entryComponents: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
|
})
|
||||||
|
class RootModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({providers: [{provide: 'someToken', useValue: 'someValue'}]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (<ComponentFactoryResolver>TestBed.get(ComponentFactoryResolver))
|
||||||
|
.resolveComponentFactory(MyComp);
|
||||||
|
|
||||||
|
// Note: MyComp was declared as entryComponent in the RootModule,
|
||||||
|
// but we pass MyModule to the createComponent call.
|
||||||
|
// -> expect the providers of MyModule!
|
||||||
|
const compRef = compFixture.componentInstance.vc.createComponent(
|
||||||
|
myCompFactory, undefined, undefined, undefined, myModule);
|
||||||
|
expect(compRef.instance.someToken).toBe('someValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a component with the NgModuleRef of the ComponentFactoryResolver',
|
||||||
|
() => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class RootComp {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [RootComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
||||||
|
})
|
||||||
|
class RootModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class MyComp {
|
||||||
|
constructor(@Inject('someToken') public someToken: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComp],
|
||||||
|
entryComponents: [MyComp],
|
||||||
|
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
||||||
|
})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Note: MyComp was declared as entryComponent in MyModule,
|
||||||
|
// and we don't pass an explicit ModuleRef to the createComponent call.
|
||||||
|
// -> expect the providers of MyModule!
|
||||||
|
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
||||||
|
expect(compRef.instance.someToken).toBe('someValue');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a component with the passed NgModuleRef', () => {
|
describe('.insert', () => {
|
||||||
@Component({template: ''})
|
it('should throw with destroyed views', async(() => {
|
||||||
class RootComp {
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
constructor(public vc: ViewContainerRef) {}
|
.createComponent(MyComp);
|
||||||
}
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
|
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
||||||
|
const ref = dynamicVp.create();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
@Component({template: ''})
|
ref.destroy();
|
||||||
class MyComp {
|
expect(() => {
|
||||||
constructor(@Inject('someToken') public someToken: string) {}
|
dynamicVp.insert(ref.hostView);
|
||||||
}
|
}).toThrowError('Cannot insert a destroyed View in a ViewContainer!');
|
||||||
|
}));
|
||||||
@NgModule({
|
|
||||||
declarations: [RootComp, MyComp],
|
|
||||||
entryComponents: [MyComp],
|
|
||||||
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
|
||||||
})
|
|
||||||
class RootModule {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({providers: [{provide: 'someToken', useValue: 'someValue'}]})
|
|
||||||
class MyModule {
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = (<ComponentFactoryResolver>TestBed.get(ComponentFactoryResolver))
|
|
||||||
.resolveComponentFactory(MyComp);
|
|
||||||
|
|
||||||
// Note: MyComp was declared as entryComponent in the RootModule,
|
|
||||||
// but we pass MyModule to the createComponent call.
|
|
||||||
// -> expect the providers of MyModule!
|
|
||||||
const compRef = compFixture.componentInstance.vc.createComponent(
|
|
||||||
myCompFactory, undefined, undefined, undefined, myModule);
|
|
||||||
expect(compRef.instance.someToken).toBe('someValue');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a component with the NgModuleRef of the ComponentFactoryResolver', () => {
|
describe('.move', () => {
|
||||||
@Component({template: ''})
|
it('should throw with destroyed views', async(() => {
|
||||||
class RootComp {
|
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
constructor(public vc: ViewContainerRef) {}
|
.createComponent(MyComp);
|
||||||
}
|
const tc = fixture.debugElement.children[0].children[0];
|
||||||
|
const dynamicVp: DynamicViewport = tc.injector.get(DynamicViewport);
|
||||||
|
const ref = dynamicVp.create();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
@NgModule({
|
ref.destroy();
|
||||||
declarations: [RootComp],
|
expect(() => {
|
||||||
providers: [{provide: 'someToken', useValue: 'someRootValue'}],
|
dynamicVp.move(ref.hostView, 1);
|
||||||
})
|
}).toThrowError('Cannot move a destroyed View in a ViewContainer!');
|
||||||
class RootModule {
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
@Component({template: ''})
|
|
||||||
class MyComp {
|
|
||||||
constructor(@Inject('someToken') public someToken: string) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [MyComp],
|
|
||||||
entryComponents: [MyComp],
|
|
||||||
providers: [{provide: 'someToken', useValue: 'someValue'}],
|
|
||||||
})
|
|
||||||
class MyModule {
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Note: MyComp was declared as entryComponent in MyModule,
|
|
||||||
// and we don't pass an explicit ModuleRef to the createComponent call.
|
|
||||||
// -> expect the providers of MyModule!
|
|
||||||
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
|
|
||||||
expect(compRef.instance.someToken).toBe('someValue');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support static attributes', () => {
|
it('should support static attributes', () => {
|
||||||
@ -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'})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user