fix(ivy): detach ViewRef from ApplicationRef on destroy (#27276)
Currently we store the `_appRef` when a `ViewRef` is attached, however we don't use it for anything. These changes use it to detach the view from the `ApplicationRef` when it is destroyed. These changes also fix that the `ComponentRef` doesn't remove its `ViewRef` on destroy. PR Close #27276
This commit is contained in:
parent
0487fbe236
commit
4622d0b23a
|
@ -59,7 +59,9 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this._viewContainerRef && viewAttached(this._view)) {
|
||||
if (this._appRef) {
|
||||
this._appRef.detachView(this);
|
||||
} else if (this._viewContainerRef && viewAttached(this._view)) {
|
||||
this._viewContainerRef.detach(this._viewContainerRef.indexOf(this));
|
||||
this._viewContainerRef = null;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class SomeComponent {
|
|||
}
|
||||
|
||||
{
|
||||
fixmeIvy('unknown') && describe('bootstrap', () => {
|
||||
describe('bootstrap', () => {
|
||||
let mockConsole: MockConsole;
|
||||
|
||||
beforeEach(() => { mockConsole = new MockConsole(); });
|
||||
|
@ -74,6 +74,7 @@ class SomeComponent {
|
|||
return MyModule;
|
||||
}
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should bootstrap a component from a child module',
|
||||
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
|
@ -102,6 +103,7 @@ class SomeComponent {
|
|||
expect(component.injector.get('hello')).toEqual('component');
|
||||
})));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should bootstrap a component with a custom selector',
|
||||
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
|
@ -133,7 +135,7 @@ class SomeComponent {
|
|||
describe('ApplicationRef', () => {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
||||
|
||||
it('should throw when reentering tick', () => {
|
||||
fixmeIvy('unknown') && it('should throw when reentering tick', () => {
|
||||
@Component({template: '{{reenter()}}'})
|
||||
class ReenteringComponent {
|
||||
reenterCount = 1;
|
||||
|
@ -174,7 +176,7 @@ class SomeComponent {
|
|||
});
|
||||
});
|
||||
|
||||
it('should be called when a component is bootstrapped',
|
||||
fixmeIvy('unknown') && it('should be called when a component is bootstrapped',
|
||||
inject([ApplicationRef], (ref: ApplicationRef) => {
|
||||
createRootEl();
|
||||
const compRef = ref.bootstrap(SomeComponent);
|
||||
|
@ -183,12 +185,15 @@ class SomeComponent {
|
|||
});
|
||||
|
||||
describe('bootstrap', () => {
|
||||
fixmeIvy('unknown') &&
|
||||
it('should throw if an APP_INITIIALIZER is not yet resolved',
|
||||
withModule(
|
||||
{
|
||||
providers: [
|
||||
{provide: APP_INITIALIZER, useValue: () => new Promise(() => {}), multi: true}
|
||||
]
|
||||
providers: [{
|
||||
provide: APP_INITIALIZER,
|
||||
useValue: () => new Promise(() => {}),
|
||||
multi: true
|
||||
}]
|
||||
},
|
||||
inject([ApplicationRef], (ref: ApplicationRef) => {
|
||||
createRootEl();
|
||||
|
@ -206,6 +211,7 @@ class SomeComponent {
|
|||
defaultPlatform = _platform;
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should wait for asynchronous app initializers', async(() => {
|
||||
let resolve: (result: any) => void;
|
||||
const promise: Promise<any> = new Promise((res) => { resolve = res; });
|
||||
|
@ -216,15 +222,18 @@ class SomeComponent {
|
|||
}, 1);
|
||||
|
||||
defaultPlatform
|
||||
.bootstrapModule(
|
||||
createModule([{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]))
|
||||
.bootstrapModule(createModule(
|
||||
[{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]))
|
||||
.then(_ => { expect(initializerDone).toBe(true); });
|
||||
}));
|
||||
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||
fixmeIvy('unknown') &&
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule(
|
||||
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
|
||||
.bootstrapModule(createModule([
|
||||
{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}
|
||||
]))
|
||||
.then(() => expect(false).toBe(true), (e) => {
|
||||
expect(e).toBe('Test');
|
||||
// Error rethrown will be seen by the exception handler since it's after
|
||||
|
@ -233,18 +242,22 @@ class SomeComponent {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule([
|
||||
{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}
|
||||
]))
|
||||
.bootstrapModule(createModule([{
|
||||
provide: APP_INITIALIZER,
|
||||
useValue: () => Promise.reject('Test'),
|
||||
multi: true
|
||||
}]))
|
||||
.then(() => expect(false).toBe(true), (e) => {
|
||||
expect(e).toBe('Test');
|
||||
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
|
||||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should throw useful error when ApplicationRef is not configured', async(() => {
|
||||
@NgModule()
|
||||
class EmptyModule {
|
||||
|
@ -257,6 +270,7 @@ class SomeComponent {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should call the `ngDoBootstrap` method with `ApplicationRef` on the main module',
|
||||
async(() => {
|
||||
const ngDoBootstrap = jasmine.createSpy('ngDoBootstrap');
|
||||
|
@ -267,6 +281,7 @@ class SomeComponent {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should auto bootstrap components listed in @NgModule.bootstrap', async(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then((moduleRef) => {
|
||||
|
@ -275,6 +290,7 @@ class SomeComponent {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should error if neither `ngDoBootstrap` nor @NgModule.bootstrap was specified',
|
||||
async(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: false}))
|
||||
|
@ -286,11 +302,13 @@ class SomeComponent {
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should add bootstrapped module into platform modules list', async(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should bootstrap with NoopNgZone', async(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule({bootstrap: [SomeComponent]}), {ngZone: 'noop'})
|
||||
|
@ -307,6 +325,7 @@ class SomeComponent {
|
|||
createRootEl();
|
||||
defaultPlatform = _platform;
|
||||
}));
|
||||
fixmeIvy('unknown') &&
|
||||
it('should wait for asynchronous app initializers', async(() => {
|
||||
let resolve: (result: any) => void;
|
||||
const promise: Promise<any> = new Promise((res) => { resolve = res; });
|
||||
|
@ -318,30 +337,40 @@ class SomeComponent {
|
|||
|
||||
const compilerFactory: CompilerFactory =
|
||||
defaultPlatform.injector.get(CompilerFactory, null);
|
||||
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(
|
||||
createModule([{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]));
|
||||
const moduleFactory =
|
||||
compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||
[{provide: APP_INITIALIZER, useValue: () => promise, multi: true}]));
|
||||
defaultPlatform.bootstrapModuleFactory(moduleFactory).then(_ => {
|
||||
expect(initializerDone).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||
fixmeIvy('unknown') &&
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
const compilerFactory: CompilerFactory =
|
||||
defaultPlatform.injector.get(CompilerFactory, null);
|
||||
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
|
||||
const moduleFactory =
|
||||
compilerFactory.createCompiler().compileModuleSync(createModule([
|
||||
{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}
|
||||
]));
|
||||
expect(() => defaultPlatform.bootstrapModuleFactory(moduleFactory)).toThrow('Test');
|
||||
// Error rethrown will be seen by the exception handler since it's after
|
||||
// construction.
|
||||
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
|
||||
}));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
const compilerFactory: CompilerFactory =
|
||||
defaultPlatform.injector.get(CompilerFactory, null);
|
||||
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||
[{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}]));
|
||||
const moduleFactory =
|
||||
compilerFactory.createCompiler().compileModuleSync(createModule([{
|
||||
provide: APP_INITIALIZER,
|
||||
useValue: () => Promise.reject('Test'),
|
||||
multi: true
|
||||
}]));
|
||||
defaultPlatform.bootstrapModuleFactory(moduleFactory)
|
||||
.then(() => expect(false).toBe(true), (e) => {
|
||||
expect(e).toBe('Test');
|
||||
|
@ -427,6 +456,7 @@ class SomeComponent {
|
|||
expect(appRef.viewCount).toBe(0);
|
||||
});
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('should not allow to attach a view to both, a view container and the ApplicationRef',
|
||||
() => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
|
@ -448,7 +478,7 @@ class SomeComponent {
|
|||
});
|
||||
});
|
||||
|
||||
fixmeIvy('unknown') && describe('AppRef', () => {
|
||||
describe('AppRef', () => {
|
||||
@Component({selector: 'sync-comp', template: `<span>{{text}}</span>`})
|
||||
class SyncComp {
|
||||
text: string = '1';
|
||||
|
@ -535,18 +565,20 @@ class SomeComponent {
|
|||
});
|
||||
}
|
||||
|
||||
it('isStable should fire on synchronous component loading',
|
||||
fixmeIvy('unknown') && it('isStable should fire on synchronous component loading',
|
||||
async(() => { expectStableTexts(SyncComp, ['1']); }));
|
||||
|
||||
it('isStable should fire after a microtask on init is completed',
|
||||
fixmeIvy('unknown') && it('isStable should fire after a microtask on init is completed',
|
||||
async(() => { expectStableTexts(MicroTaskComp, ['11']); }));
|
||||
|
||||
it('isStable should fire after a macrotask on init is completed',
|
||||
fixmeIvy('unknown') && it('isStable should fire after a macrotask on init is completed',
|
||||
async(() => { expectStableTexts(MacroTaskComp, ['11']); }));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('isStable should fire only after chain of micro and macrotasks on init are completed',
|
||||
async(() => { expectStableTexts(MicroMacroTaskComp, ['111']); }));
|
||||
|
||||
fixmeIvy('unknown') &&
|
||||
it('isStable should fire only after chain of macro and microtasks on init are completed',
|
||||
async(() => { expectStableTexts(MacroMicroTaskComp, ['111']); }));
|
||||
|
||||
|
@ -569,7 +601,7 @@ class SomeComponent {
|
|||
});
|
||||
}
|
||||
|
||||
it('should be fired after app becomes unstable', async(() => {
|
||||
fixmeIvy('unknown') && it('should be fired after app becomes unstable', async(() => {
|
||||
const fixture = TestBed.createComponent(ClickComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
const zone: NgZone = TestBed.get(NgZone);
|
||||
|
|
Loading…
Reference in New Issue