fix: don't instantiate providers with ngOnDestroy eagerly. (#15070)
BREAKING CHANGE: Perviously, any provider that had an ngOnDestroy lifecycle hook would be created eagerly. Now, only classes that are annotated with @Component, @Directive, @Pipe, @NgModule are eager. Providers only become eager if they are either directly or transitively injected into one of the above. This also makes all `useValue` providers eager, which should have no observable impact other than code size. EXPECTED IMPACT: Making providers eager was an incorrect behavior and never documented. Also, providers that are used by a directive / pipe / ngModule stay eager. So the impact should be rather small. Fixes #14552
This commit is contained in:

committed by
Chuck Jazdzewski

parent
0aad270267
commit
2c5a671341
@ -56,4 +56,6 @@ export function _initViewEngine() {
|
||||
]
|
||||
})
|
||||
export class ApplicationModule {
|
||||
// Inject ApplicationRef to make it eager...
|
||||
constructor(appRef: ApplicationRef) {}
|
||||
}
|
||||
|
@ -468,6 +468,9 @@ function callElementProvidersLifecycles(view: ViewData, elDef: NodeDef, lifecycl
|
||||
|
||||
function callProviderLifecycles(view: ViewData, index: number, lifecycles: NodeFlags) {
|
||||
const provider = asProviderData(view, index).instance;
|
||||
if (provider === NOT_CREATED) {
|
||||
return;
|
||||
}
|
||||
Services.setCurrentNode(view, index);
|
||||
if (lifecycles & NodeFlags.AfterContentInit) {
|
||||
provider.ngAfterContentInit();
|
||||
|
@ -907,6 +907,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
|
||||
@NgModule({providers: [SomeInjectable]})
|
||||
class SomeModule {
|
||||
// Inject SomeInjectable to make it eager...
|
||||
constructor(i: SomeInjectable) {}
|
||||
}
|
||||
|
||||
const moduleRef = createModule(SomeModule);
|
||||
@ -915,17 +917,33 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(destroyed).toBe(true);
|
||||
});
|
||||
|
||||
it('should instantiate providers with lifecycle eagerly', () => {
|
||||
it('should support ngOnDestroy for lazy providers', () => {
|
||||
let created = false;
|
||||
let destroyed = false;
|
||||
|
||||
class SomeInjectable {
|
||||
constructor() { created = true; }
|
||||
ngOnDestroy() {}
|
||||
ngOnDestroy() { destroyed = true; }
|
||||
}
|
||||
|
||||
createInjector([SomeInjectable]);
|
||||
@NgModule({providers: [SomeInjectable]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
let moduleRef = createModule(SomeModule);
|
||||
expect(created).toBe(false);
|
||||
expect(destroyed).toBe(false);
|
||||
|
||||
// no error if the provider was not yet created
|
||||
moduleRef.destroy();
|
||||
expect(created).toBe(false);
|
||||
expect(destroyed).toBe(false);
|
||||
|
||||
moduleRef = createModule(SomeModule);
|
||||
moduleRef.injector.get(SomeInjectable);
|
||||
expect(created).toBe(true);
|
||||
moduleRef.destroy();
|
||||
expect(destroyed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -342,34 +342,36 @@ export function main() {
|
||||
expect(created).toBe(true);
|
||||
});
|
||||
|
||||
it('should instantiate providers lazily', () => {
|
||||
TestBed.configureTestingModule({declarations: [SimpleDirective]});
|
||||
it('should support ngOnDestroy for lazy providers', () => {
|
||||
let created = false;
|
||||
TestBed.overrideDirective(
|
||||
SimpleDirective,
|
||||
{set: {providers: [{provide: 'service', useFactory: () => created = true}]}});
|
||||
let destroyed = false;
|
||||
|
||||
const el = createComponent('<div simpleDirective></div>');
|
||||
|
||||
expect(created).toBe(false);
|
||||
|
||||
el.children[0].injector.get('service');
|
||||
|
||||
expect(created).toBe(true);
|
||||
});
|
||||
|
||||
it('should instantiate providers with a lifecycle hook eagerly', () => {
|
||||
let created = false;
|
||||
class SomeInjectable {
|
||||
constructor() { created = true; }
|
||||
ngOnDestroy() {}
|
||||
ngOnDestroy() { destroyed = true; }
|
||||
}
|
||||
TestBed.configureTestingModule({declarations: [SimpleDirective]});
|
||||
TestBed.overrideDirective(SimpleDirective, {set: {providers: [SomeInjectable]}});
|
||||
|
||||
const el = createComponent('<div simpleDirective></div>');
|
||||
@Component({providers: [SomeInjectable], template: ''})
|
||||
class SomeComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [SomeComp]});
|
||||
|
||||
|
||||
let compRef = TestBed.createComponent(SomeComp).componentRef;
|
||||
expect(created).toBe(false);
|
||||
expect(destroyed).toBe(false);
|
||||
|
||||
// no error if the provider was not yet created
|
||||
compRef.destroy();
|
||||
expect(created).toBe(false);
|
||||
expect(destroyed).toBe(false);
|
||||
|
||||
compRef = TestBed.createComponent(SomeComp).componentRef;
|
||||
compRef.injector.get(SomeInjectable);
|
||||
expect(created).toBe(true);
|
||||
compRef.destroy();
|
||||
expect(destroyed).toBe(true);
|
||||
});
|
||||
|
||||
it('should instantiate view providers lazily', () => {
|
||||
|
@ -213,7 +213,13 @@ export class TestBed implements Injector {
|
||||
this._imports = [];
|
||||
this._schemas = [];
|
||||
this._instantiated = false;
|
||||
this._activeFixtures.forEach((fixture) => fixture.destroy());
|
||||
this._activeFixtures.forEach((fixture) => {
|
||||
try {
|
||||
fixture.destroy();
|
||||
} catch (e) {
|
||||
console.error('Error during cleanup of component', fixture.componentInstance);
|
||||
}
|
||||
});
|
||||
this._activeFixtures = [];
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user