diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 6dbfc1a5fd..d779bd4f41 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -74,6 +74,7 @@ function extractInjectableMetadata( const name = clazz.name.text; const type = new WrappedNodeExpr(clazz.name); const ctorDeps = getConstructorDependencies(clazz, reflector, isCore); + const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0; if (decorator.args === null) { throw new FatalDiagnosticError( ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called'); @@ -82,6 +83,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, providedIn: new LiteralExpr(null), ctorDeps, }; } else if (decorator.args.length === 1) { @@ -118,6 +120,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !) @@ -126,6 +129,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !) @@ -134,6 +138,7 @@ function extractInjectableMetadata( return { name, type, + typeArgumentCount, ctorDeps, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps @@ -141,9 +146,9 @@ function extractInjectableMetadata( } else if (meta.has('useFactory')) { // useFactory is special - the 'deps' property must be analyzed. const factory = new WrappedNodeExpr(meta.get('useFactory') !); - return {name, type, providedIn, useFactory: factory, ctorDeps, userDeps}; + return {name, type, typeArgumentCount, providedIn, useFactory: factory, ctorDeps, userDeps}; } else { - return {name, type, providedIn, ctorDeps}; + return {name, type, typeArgumentCount, providedIn, ctorDeps}; } } else { throw new FatalDiagnosticError( diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index c3d79bb7fc..d9d944025c 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -46,6 +46,24 @@ describe('ngtsc behavioral tests', () => { expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef;'); }); + it('should compile Injectables with a generic service', () => { + env.tsconfig(); + env.write('test.ts', ` + import {Injectable} from '@angular/core'; + + @Injectable() + export class Store {} + `); + + env.driveMain(); + + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('Store.ngInjectableDef ='); + const dtsContents = env.getContents('test.d.ts'); + expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef>;'); + }); + it('should compile Components without errors', () => { env.tsconfig(); env.write('test.ts', ` diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index 5665359c73..f0da45188b 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade { export interface R3InjectableMetadataFacade { name: string; type: any; + typeArgumentCount: number; ctorDeps: R3DependencyMetadataFacade[]|null; providedIn: any; useClass?: any; diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index e2b5845254..c2484b3ebc 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -10,7 +10,7 @@ import {InjectFlags} from './core'; import {Identifiers} from './identifiers'; import * as o from './output/output_ast'; import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory'; -import {mapToMapExpression} from './render3/util'; +import {mapToMapExpression, typeWithParameters} from './render3/util'; export interface InjectableDef { expression: o.Expression; @@ -21,6 +21,7 @@ export interface InjectableDef { export interface R3InjectableMetadata { name: string; type: o.Expression; + typeArgumentCount: number; ctorDeps: R3DependencyMetadata[]|null; providedIn: o.Expression; useClass?: o.Expression; @@ -33,10 +34,6 @@ export interface R3InjectableMetadata { export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { let result: {factory: o.Expression, statements: o.Statement[]}|null = null; - function makeFn(ret: o.Expression): o.Expression { - return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`); - } - const factoryMeta = { name: meta.name, type: meta.type, @@ -100,8 +97,8 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression( {token, factory: result.factory, providedIn})]); - const type = new o.ExpressionType( - o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)])); + const type = new o.ExpressionType(o.importExpr( + Identifiers.InjectableDef, [typeWithParameters(meta.type, meta.typeArgumentCount)])); return { expression, diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index e89c5d6691..85ffdd3613 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -45,6 +45,7 @@ export class CompilerFacadeImpl implements CompilerFacade { const {expression, statements} = compileInjectable({ name: facade.name, type: new WrappedNodeExpr(facade.type), + typeArgumentCount: facade.typeArgumentCount, providedIn: computeProvidedIn(facade.providedIn), useClass: wrapExpression(facade, USE_CLASS), useFactory: wrapExpression(facade, USE_FACTORY), diff --git a/packages/core/src/render3/jit/compiler_facade_interface.ts b/packages/core/src/render3/jit/compiler_facade_interface.ts index 5665359c73..f0da45188b 100644 --- a/packages/core/src/render3/jit/compiler_facade_interface.ts +++ b/packages/core/src/render3/jit/compiler_facade_interface.ts @@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade { export interface R3InjectableMetadataFacade { name: string; type: any; + typeArgumentCount: number; ctorDeps: R3DependencyMetadataFacade[]|null; providedIn: any; useClass?: any; diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts index 64a218d3a0..26f8b823e0 100644 --- a/packages/core/src/render3/jit/injectable.ts +++ b/packages/core/src/render3/jit/injectable.ts @@ -23,9 +23,6 @@ import {convertDependencies, reflectDependencies} from './util'; * `ngInjectableDef` onto the injectable type. */ export function compileInjectable(type: Type, srcMeta?: Injectable): void { - // Allow the compilation of a class with a `@Injectable()` decorator without parameters - const meta: Injectable = srcMeta || {providedIn: null}; - let def: any = null; // if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it @@ -34,6 +31,7 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { Object.defineProperty(type, NG_INJECTABLE_DEF, { get: () => { if (def === null) { + // Allow the compilation of a class with a `@Injectable()` decorator without parameters const meta: Injectable = srcMeta || {providedIn: null}; const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || isUseValueProvider(meta) || isUseExistingProvider(meta); @@ -42,6 +40,7 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { const compilerMeta: R3InjectableMetadataFacade = { name: type.name, type: type, + typeArgumentCount: 0, providedIn: meta.providedIn, ctorDeps: reflectDependencies(type), userDeps: undefined