fix(ivy): error when using forwardRef in Injectable's useClass (#30532)

Fixes Ivy throwing an error when something is passed in as a `forwardRef` into `@Injectable`'s `useClass` option. The error was being thrown, because we were trying to get the provider factory off of the wrapper function, rather than the value itself.

This PR resolves FW-1335.

PR Close #30532
This commit is contained in:
Kristiyan Kostadinov 2019-05-18 10:10:19 +02:00 committed by Andrew Kushnir
parent 4da805243a
commit 40a0666651
6 changed files with 80 additions and 7 deletions

View File

@ -57,11 +57,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
* @publicApi * @publicApi
*/ */
export function resolveForwardRef<T>(type: T): T { export function resolveForwardRef<T>(type: T): T {
const fn: any = type; return isForwardRef(type) ? type() : type;
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef) {
return fn();
} else {
return type;
} }
/** Checks whether a function is wrapped by a `forwardRef`. */
export function isForwardRef(fn: any): fn is() => any {
return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef;
} }

View File

@ -7,6 +7,7 @@
*/ */
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {isForwardRef, resolveForwardRef} from '../forward_ref';
import {ɵɵinject} from '../injector_compatibility'; import {ɵɵinject} from '../injector_compatibility';
import {getInjectableDef, getInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs'; import {getInjectableDef, getInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs';
@ -26,6 +27,14 @@ export const angularCoreDiEnv: {[name: string]: Function} = {
function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T)|null { function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T)|null {
const typeAny = type as any; const typeAny = type as any;
if (isForwardRef(type)) {
return (() => {
const factory = getFactoryOf<T>(resolveForwardRef(typeAny));
return factory ? factory() : null;
}) as any;
}
const def = getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny); const def = getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
if (!def || def.factory === undefined) { if (!def || def.factory === undefined) {
return null; return null;

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
import {InjectionToken} from '../di/injection_token'; import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility'; import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility';
@ -633,6 +634,14 @@ export class NodeInjector implements Injector {
*/ */
export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null { export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
const typeAny = type as any; const typeAny = type as any;
if (isForwardRef(type)) {
return (() => {
const factory = ɵɵgetFactoryOf<T>(resolveForwardRef(typeAny));
return factory ? factory() : null;
}) as any;
}
const def = getComponentDef<T>(typeAny) || getDirectiveDef<T>(typeAny) || const def = getComponentDef<T>(typeAny) || getDirectiveDef<T>(typeAny) ||
getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny); getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
if (!def || def.factory === undefined) { if (!def || def.factory === undefined) {

View File

@ -9,6 +9,7 @@
import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core'; import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core';
import {TestBed, async, inject} from '@angular/core/testing'; import {TestBed, async, inject} from '@angular/core/testing';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing'; import {onlyInIvy} from '@angular/private/testing';
describe('providers', () => { describe('providers', () => {
@ -319,6 +320,54 @@ describe('providers', () => {
expect(fixture.componentInstance.myService.dep.value).toBe('one'); expect(fixture.componentInstance.myService.dep.value).toBe('one');
}); });
it('should support forward refs in useClass when impl version is also provided', () => {
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
abstract class SomeProvider {
}
@Injectable()
class SomeProviderImpl extends SomeProvider {
}
@Component({selector: 'my-app', template: ''})
class App {
constructor(public foo: SomeProvider) {}
}
TestBed.configureTestingModule(
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProviderImpl}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl);
});
onlyInIvy('VE bug (see FW-1454)')
.it('should support forward refs in useClass when token is provided', () => {
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
abstract class SomeProvider {
}
@Injectable()
class SomeProviderImpl extends SomeProvider {
}
@Component({selector: 'my-app', template: ''})
class App {
constructor(public foo: SomeProvider) {}
}
TestBed.configureTestingModule(
{declarations: [App], providers: [{provide: SomeProvider, useClass: SomeProvider}]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.componentInstance.foo).toBeAnInstanceOf(SomeProviderImpl);
});
}); });
describe('flags', () => { describe('flags', () => {

View File

@ -155,6 +155,9 @@
{ {
"name": "isFactoryProvider" "name": "isFactoryProvider"
}, },
{
"name": "isForwardRef"
},
{ {
"name": "isTypeProvider" "name": "isTypeProvider"
}, },

View File

@ -1043,6 +1043,9 @@
{ {
"name": "isFactory" "name": "isFactory"
}, },
{
"name": "isForwardRef"
},
{ {
"name": "isJsObject" "name": "isJsObject"
}, },