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:
parent
4da805243a
commit
40a0666651
|
@ -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();
|
/** Checks whether a function is wrapped by a `forwardRef`. */
|
||||||
} else {
|
export function isForwardRef(fn: any): fn is() => any {
|
||||||
return type;
|
return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
|
||||||
}
|
fn.__forward_ref__ === forwardRef;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -155,6 +155,9 @@
|
||||||
{
|
{
|
||||||
"name": "isFactoryProvider"
|
"name": "isFactoryProvider"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "isForwardRef"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "isTypeProvider"
|
"name": "isTypeProvider"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1043,6 +1043,9 @@
|
||||||
{
|
{
|
||||||
"name": "isFactory"
|
"name": "isFactory"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "isForwardRef"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "isJsObject"
|
"name": "isJsObject"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue