fix(ivy): support sub-class services that only inherit @Injectable (#30388)

In View engine it is possible to instantiate a service that that has no
`@Injectable` decorator as long as it satisfies one of:

1) It has no dependencies and so a constructor with no parameters.
This is already supported in Ivy.
2) It has no constructor of its own and sub-classes a service which has
dependencies but has its own `@Injectable` decorator. This second
scenario was broken in Ivy.

In Ivy, previous to this commit, if a class to be instantiated did not have
its own `@Injectable` decorator and did not provide a constructor of
its own, then it would be created using `new` with no arguments -
i.e. falling back to the first scenario.

After this commit Ivy correctly uses the `ngInjectableDef` inherited
from the super-class to provide the `factory` for instantiating the
sub-class.

FW-1314

PR Close #30388
This commit is contained in:
Pete Bacon Darwin
2019-05-13 19:43:11 +01:00
committed by Jason Aden
parent 35c1750fcc
commit 1a0e500eea
4 changed files with 113 additions and 24 deletions

View File

@ -10,7 +10,7 @@ import {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, Injector, Input, LOCALE_ID, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef} from '@angular/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {onlyInIvy} from '@angular/private/testing';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
describe('di', () => {
describe('no dependencies', () => {
@ -370,7 +370,7 @@ describe('di', () => {
<div dirA #dir="dirA">{{ dir.dirB.value }}</div>
</ng-template>
</div>
<div dirB value="insertion">
<div structuralDir [tmp]="foo"></div>
<!-- insertion point -->
@ -858,6 +858,40 @@ describe('di', () => {
const divElement = fixture.nativeElement.querySelector('div');
expect(divElement.textContent).toEqual('MyService');
});
it('should support sub-classes with no @Injectable decorator', () => {
@Injectable()
class Dependency {
}
@Injectable()
class SuperClass {
constructor(public dep: Dependency) {}
}
// Note, no @Injectable decorators for these two classes
class SubClass extends SuperClass {}
class SubSubClass extends SubClass {}
@Component({template: ''})
class MyComp {
constructor(public myService: SuperClass) {}
}
TestBed.configureTestingModule({
declarations: [MyComp],
providers: [{provide: SuperClass, useClass: SubSubClass}, Dependency]
});
const warnSpy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(MyComp);
expect(fixture.componentInstance.myService.dep instanceof Dependency).toBe(true);
if (ivyEnabled) {
expect(warnSpy).toHaveBeenCalledWith(
`DEPRECATED: DI is instantiating a token "SubSubClass" that inherits its @Injectable decorator but does not provide one itself.\n` +
`This will become an error in v10. Please add @Injectable() to the "SubSubClass" class.`);
}
});
});
describe('inject', () => {