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
|
||||
*/
|
||||
export function resolveForwardRef<T>(type: T): T {
|
||||
const fn: any = type;
|
||||
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
|
||||
fn.__forward_ref__ === forwardRef) {
|
||||
return fn();
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
return isForwardRef(type) ? type() : 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;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {Type} from '../../interface/type';
|
||||
import {isForwardRef, resolveForwardRef} from '../forward_ref';
|
||||
import {ɵɵinject} from '../injector_compatibility';
|
||||
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 {
|
||||
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);
|
||||
if (!def || def.factory === undefined) {
|
||||
return null;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {Injector} from '../di/injector';
|
||||
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 {
|
||||
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) ||
|
||||
getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
|
||||
if (!def || def.factory === undefined) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import {Component, Directive, Inject, Injectable, InjectionToken, Injector, NgModule, Optional, forwardRef} from '@angular/core';
|
||||
import {TestBed, async, inject} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('providers', () => {
|
||||
|
@ -319,6 +320,54 @@ describe('providers', () => {
|
|||
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', () => {
|
||||
|
|
|
@ -155,6 +155,9 @@
|
|||
{
|
||||
"name": "isFactoryProvider"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isTypeProvider"
|
||||
},
|
||||
|
|
|
@ -1043,6 +1043,9 @@
|
|||
{
|
||||
"name": "isFactory"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
{
|
||||
"name": "isJsObject"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue