feat(core): support `forwardRef` in `providedIn` of `Injectable` declaration (#41426)
Adds support for using a `forwardRef` inside of the `providedIn` of an `Injectable` declaration. Fixes #41205. PR Close #41426
This commit is contained in:
parent
acebe92bad
commit
f7c294ee0f
|
@ -2303,6 +2303,22 @@ describe('ngc transformer command-line', () => {
|
|||
`);
|
||||
expect(source).toMatch(/new Service\(i0\.ɵɵinject\(exports\.TOKEN\)\);/);
|
||||
});
|
||||
|
||||
it('compiles an injectable using `forwardRef` inside `providedIn`', () => {
|
||||
const source = compileService(`
|
||||
import {Injectable, forwardRef} from '@angular/core';
|
||||
import {Module} from './module';
|
||||
|
||||
@Injectable({
|
||||
providedIn: forwardRef(() => Module),
|
||||
})
|
||||
export class Service {}
|
||||
`);
|
||||
|
||||
expect(source).toMatch(/ɵprov = .+\.ɵɵdefineInjectable\(/);
|
||||
expect(source).toMatch(/ɵprov.*token: Service/);
|
||||
expect(source).toMatch(/ɵprov.*providedIn: .+\.Module/);
|
||||
});
|
||||
});
|
||||
|
||||
it('libraries should not break strictMetadataEmit', () => {
|
||||
|
|
|
@ -183,6 +183,41 @@ function allTests(os: string) {
|
|||
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;');
|
||||
});
|
||||
|
||||
it('should compile Injectables with providedIn using forwardRef without errors', () => {
|
||||
env.write('test.ts', `
|
||||
import {Injectable, NgModule, forwardRef} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Dep {}
|
||||
|
||||
@Injectable({ providedIn: forwardRef(() => Mod) })
|
||||
export class Service {
|
||||
constructor(dep: Dep) {}
|
||||
}
|
||||
|
||||
@NgModule()
|
||||
export class Mod {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('Dep.ɵprov =');
|
||||
expect(jsContents).toContain('Service.ɵprov =');
|
||||
expect(jsContents).toContain('Mod.ɵmod =');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'Service.ɵfac = function Service_Factory(t) { return new (t || Service)(i0.ɵɵinject(Dep)); };');
|
||||
expect(jsContents).toContain('providedIn: forwardRef(function () { return Mod; }) })');
|
||||
expect(jsContents).not.toContain('__decorate');
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Dep>;');
|
||||
expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDef<Service>;');
|
||||
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Dep, never>;');
|
||||
expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration<Service, never>;');
|
||||
expect(dtsContents).toContain('i0.ɵɵFactoryDeclaration<Mod, never>;');
|
||||
});
|
||||
|
||||
it('should compile @Injectable with an @Optional dependency', () => {
|
||||
env.write('test.ts', `
|
||||
import {Injectable, Optional as Opt} from '@angular/core';
|
||||
|
|
|
@ -166,7 +166,7 @@ export class StaticInjector implements Injector {
|
|||
// This means we have never seen this record, see if it is tree shakable provider.
|
||||
const injectableDef = getInjectableDef(token);
|
||||
if (injectableDef) {
|
||||
const providedIn = injectableDef && injectableDef.providedIn;
|
||||
const providedIn = injectableDef && resolveForwardRef(injectableDef.providedIn);
|
||||
if (providedIn === 'any' || providedIn != null && providedIn === this.scope) {
|
||||
records.set(
|
||||
token,
|
||||
|
|
|
@ -420,10 +420,12 @@ export class R3Injector {
|
|||
private injectableDefInScope(def: ɵɵInjectableDef<any>): boolean {
|
||||
if (!def.providedIn) {
|
||||
return false;
|
||||
} else if (typeof def.providedIn === 'string') {
|
||||
return def.providedIn === 'any' || (def.providedIn === this.scope);
|
||||
}
|
||||
const providedIn = resolveForwardRef(def.providedIn);
|
||||
if (typeof providedIn === 'string') {
|
||||
return providedIn === 'any' || (providedIn === this.scope);
|
||||
} else {
|
||||
return this.injectorDefTypes.has(def.providedIn);
|
||||
return this.injectorDefTypes.has(providedIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean
|
|||
}
|
||||
|
||||
function targetsModule(ngModule: NgModuleData, def: ɵɵInjectableDef<any>): boolean {
|
||||
const providedIn = def.providedIn;
|
||||
const providedIn = resolveForwardRef(def.providedIn);
|
||||
return providedIn != null &&
|
||||
(providedIn === 'any' || providedIn === ngModule._def.scope ||
|
||||
moduleTransitivelyPresent(ngModule, providedIn));
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {DebugElement__PRE_R3__, DebugEventListener, DebugNode__PRE_R3__, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {Injector} from '../di';
|
||||
import {Injector, resolveForwardRef} from '../di';
|
||||
import {getInjectableDef, InjectableType, ɵɵInjectableDef} from '../di/interface/defs';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {Type} from '../interface/type';
|
||||
|
@ -282,7 +282,7 @@ function applyProviderOverridesToNgModule(def: NgModuleDefinition): NgModuleDefi
|
|||
});
|
||||
def.modules.forEach(module => {
|
||||
providerOverridesWithScope.forEach((override, token) => {
|
||||
if (getInjectableDef(token)!.providedIn === module) {
|
||||
if (resolveForwardRef(getInjectableDef(token)!.providedIn) === module) {
|
||||
hasOverrides = true;
|
||||
hasDeprecatedOverrides = hasDeprecatedOverrides || override.deprecatedBehavior;
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ function applyProviderOverridesToNgModule(def: NgModuleDefinition): NgModuleDefi
|
|||
if (providerOverridesWithScope.size > 0) {
|
||||
let moduleSet = new Set<any>(def.modules);
|
||||
providerOverridesWithScope.forEach((override, token) => {
|
||||
if (moduleSet.has(getInjectableDef(token)!.providedIn)) {
|
||||
if (moduleSet.has(resolveForwardRef(getInjectableDef(token)!.providedIn))) {
|
||||
let provider = {
|
||||
token: token,
|
||||
flags:
|
||||
|
|
|
@ -1951,6 +1951,34 @@ describe('di', () => {
|
|||
const platformService = childInjector.get(PlatformService);
|
||||
expect(platformService.injector.get(ɵINJECTOR_SCOPE)).toBe('platform');
|
||||
});
|
||||
|
||||
it('should create a provider that uses `forwardRef` inside `providedIn`', () => {
|
||||
@Injectable()
|
||||
class ProviderDep {
|
||||
getNumber() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({providedIn: forwardRef(() => Module)})
|
||||
class Provider {
|
||||
constructor(private _dep: ProviderDep) {}
|
||||
value = this._dep.getNumber() + 2;
|
||||
}
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {
|
||||
constructor(public provider: Provider) {}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [Comp], exports: [Comp], providers: [ProviderDep]})
|
||||
class Module {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({imports: [Module]});
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
expect(fixture.componentInstance.provider.value).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('service injection', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {ResourceLoader} from '@angular/compiler';
|
||||
import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDef as InjectableDef} from '@angular/core';
|
||||
import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, Injector, InjectorType, LOCALE_ID, ModuleWithComponentFactories, ModuleWithProviders, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, resolveForwardRef, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵgetInjectableDef as getInjectableDef, ɵNG_COMP_DEF as NG_COMP_DEF, ɵNG_DIR_DEF as NG_DIR_DEF, ɵNG_INJ_DEF as NG_INJ_DEF, ɵNG_MOD_DEF as NG_MOD_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDef as InjectableDef} from '@angular/core';
|
||||
|
||||
import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading';
|
||||
|
||||
|
@ -176,19 +176,19 @@ export class R3TestBedCompiler {
|
|||
|
||||
const injectableDef: InjectableDef<any>|null =
|
||||
typeof token !== 'string' ? getInjectableDef(token) : null;
|
||||
const isRoot = injectableDef !== null && injectableDef.providedIn === 'root';
|
||||
const overridesBucket = isRoot ? this.rootProviderOverrides : this.providerOverrides;
|
||||
const providedIn = injectableDef === null ? null : resolveForwardRef(injectableDef.providedIn);
|
||||
const overridesBucket =
|
||||
providedIn === 'root' ? this.rootProviderOverrides : this.providerOverrides;
|
||||
overridesBucket.push(providerDef);
|
||||
|
||||
// Keep overrides grouped by token as well for fast lookups using token
|
||||
this.providerOverridesByToken.set(token, providerDef);
|
||||
if (injectableDef !== null && injectableDef.providedIn !== null &&
|
||||
typeof injectableDef.providedIn !== 'string') {
|
||||
const existingOverrides = this.providerOverridesByModule.get(injectableDef.providedIn);
|
||||
if (injectableDef !== null && providedIn !== null && typeof providedIn !== 'string') {
|
||||
const existingOverrides = this.providerOverridesByModule.get(providedIn);
|
||||
if (existingOverrides !== undefined) {
|
||||
existingOverrides.push(providerDef);
|
||||
} else {
|
||||
this.providerOverridesByModule.set(injectableDef.providedIn, [providerDef]);
|
||||
this.providerOverridesByModule.set(providedIn, [providerDef]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue