fix(ivy): ngtsc should include generic types on injectable definitions (#27037)
Analogously to directives, the `ngInjectableDef` field in .d.ts files is annotated with the type of service that it represents. If the service contains required generic type arguments, these must be included in the .d.ts file. PR Close #27037
This commit is contained in:
parent
7f221d8d2a
commit
159ab1c257
@ -74,6 +74,7 @@ function extractInjectableMetadata(
|
|||||||
const name = clazz.name.text;
|
const name = clazz.name.text;
|
||||||
const type = new WrappedNodeExpr(clazz.name);
|
const type = new WrappedNodeExpr(clazz.name);
|
||||||
const ctorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
const ctorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
||||||
|
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
||||||
if (decorator.args === null) {
|
if (decorator.args === null) {
|
||||||
throw new FatalDiagnosticError(
|
throw new FatalDiagnosticError(
|
||||||
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
||||||
@ -82,6 +83,7 @@ function extractInjectableMetadata(
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
typeArgumentCount,
|
||||||
providedIn: new LiteralExpr(null), ctorDeps,
|
providedIn: new LiteralExpr(null), ctorDeps,
|
||||||
};
|
};
|
||||||
} else if (decorator.args.length === 1) {
|
} else if (decorator.args.length === 1) {
|
||||||
@ -118,6 +120,7 @@ function extractInjectableMetadata(
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
typeArgumentCount,
|
||||||
ctorDeps,
|
ctorDeps,
|
||||||
providedIn,
|
providedIn,
|
||||||
useValue: new WrappedNodeExpr(meta.get('useValue') !)
|
useValue: new WrappedNodeExpr(meta.get('useValue') !)
|
||||||
@ -126,6 +129,7 @@ function extractInjectableMetadata(
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
typeArgumentCount,
|
||||||
ctorDeps,
|
ctorDeps,
|
||||||
providedIn,
|
providedIn,
|
||||||
useExisting: new WrappedNodeExpr(meta.get('useExisting') !)
|
useExisting: new WrappedNodeExpr(meta.get('useExisting') !)
|
||||||
@ -134,6 +138,7 @@ function extractInjectableMetadata(
|
|||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
typeArgumentCount,
|
||||||
ctorDeps,
|
ctorDeps,
|
||||||
providedIn,
|
providedIn,
|
||||||
useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps
|
useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps
|
||||||
@ -141,9 +146,9 @@ function extractInjectableMetadata(
|
|||||||
} else if (meta.has('useFactory')) {
|
} else if (meta.has('useFactory')) {
|
||||||
// useFactory is special - the 'deps' property must be analyzed.
|
// useFactory is special - the 'deps' property must be analyzed.
|
||||||
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
|
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 {
|
} else {
|
||||||
return {name, type, providedIn, ctorDeps};
|
return {name, type, typeArgumentCount, providedIn, ctorDeps};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new FatalDiagnosticError(
|
throw new FatalDiagnosticError(
|
||||||
|
@ -46,6 +46,24 @@ describe('ngtsc behavioral tests', () => {
|
|||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef<Service>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵInjectableDef<Service>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should compile Injectables with a generic service', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Store<T> {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
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<Store<any>>;');
|
||||||
|
});
|
||||||
|
|
||||||
it('should compile Components without errors', () => {
|
it('should compile Components without errors', () => {
|
||||||
env.tsconfig();
|
env.tsconfig();
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
|
@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade {
|
|||||||
export interface R3InjectableMetadataFacade {
|
export interface R3InjectableMetadataFacade {
|
||||||
name: string;
|
name: string;
|
||||||
type: any;
|
type: any;
|
||||||
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadataFacade[]|null;
|
ctorDeps: R3DependencyMetadataFacade[]|null;
|
||||||
providedIn: any;
|
providedIn: any;
|
||||||
useClass?: any;
|
useClass?: any;
|
||||||
|
@ -10,7 +10,7 @@ import {InjectFlags} from './core';
|
|||||||
import {Identifiers} from './identifiers';
|
import {Identifiers} from './identifiers';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory';
|
import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory';
|
||||||
import {mapToMapExpression} from './render3/util';
|
import {mapToMapExpression, typeWithParameters} from './render3/util';
|
||||||
|
|
||||||
export interface InjectableDef {
|
export interface InjectableDef {
|
||||||
expression: o.Expression;
|
expression: o.Expression;
|
||||||
@ -21,6 +21,7 @@ export interface InjectableDef {
|
|||||||
export interface R3InjectableMetadata {
|
export interface R3InjectableMetadata {
|
||||||
name: string;
|
name: string;
|
||||||
type: o.Expression;
|
type: o.Expression;
|
||||||
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadata[]|null;
|
ctorDeps: R3DependencyMetadata[]|null;
|
||||||
providedIn: o.Expression;
|
providedIn: o.Expression;
|
||||||
useClass?: o.Expression;
|
useClass?: o.Expression;
|
||||||
@ -33,10 +34,6 @@ export interface R3InjectableMetadata {
|
|||||||
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
let result: {factory: o.Expression, statements: o.Statement[]}|null = null;
|
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 = {
|
const factoryMeta = {
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
@ -100,8 +97,8 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
|||||||
|
|
||||||
const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression(
|
const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression(
|
||||||
{token, factory: result.factory, providedIn})]);
|
{token, factory: result.factory, providedIn})]);
|
||||||
const type = new o.ExpressionType(
|
const type = new o.ExpressionType(o.importExpr(
|
||||||
o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)]));
|
Identifiers.InjectableDef, [typeWithParameters(meta.type, meta.typeArgumentCount)]));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expression,
|
expression,
|
||||||
|
@ -45,6 +45,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
|||||||
const {expression, statements} = compileInjectable({
|
const {expression, statements} = compileInjectable({
|
||||||
name: facade.name,
|
name: facade.name,
|
||||||
type: new WrappedNodeExpr(facade.type),
|
type: new WrappedNodeExpr(facade.type),
|
||||||
|
typeArgumentCount: facade.typeArgumentCount,
|
||||||
providedIn: computeProvidedIn(facade.providedIn),
|
providedIn: computeProvidedIn(facade.providedIn),
|
||||||
useClass: wrapExpression(facade, USE_CLASS),
|
useClass: wrapExpression(facade, USE_CLASS),
|
||||||
useFactory: wrapExpression(facade, USE_FACTORY),
|
useFactory: wrapExpression(facade, USE_FACTORY),
|
||||||
|
@ -78,6 +78,7 @@ export interface R3PipeMetadataFacade {
|
|||||||
export interface R3InjectableMetadataFacade {
|
export interface R3InjectableMetadataFacade {
|
||||||
name: string;
|
name: string;
|
||||||
type: any;
|
type: any;
|
||||||
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadataFacade[]|null;
|
ctorDeps: R3DependencyMetadataFacade[]|null;
|
||||||
providedIn: any;
|
providedIn: any;
|
||||||
useClass?: any;
|
useClass?: any;
|
||||||
|
@ -23,9 +23,6 @@ import {convertDependencies, reflectDependencies} from './util';
|
|||||||
* `ngInjectableDef` onto the injectable type.
|
* `ngInjectableDef` onto the injectable type.
|
||||||
*/
|
*/
|
||||||
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
|
export function compileInjectable(type: Type<any>, 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;
|
let def: any = null;
|
||||||
|
|
||||||
// if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it
|
// if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it
|
||||||
@ -34,6 +31,7 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
|
|||||||
Object.defineProperty(type, NG_INJECTABLE_DEF, {
|
Object.defineProperty(type, NG_INJECTABLE_DEF, {
|
||||||
get: () => {
|
get: () => {
|
||||||
if (def === null) {
|
if (def === null) {
|
||||||
|
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
|
||||||
const meta: Injectable = srcMeta || {providedIn: null};
|
const meta: Injectable = srcMeta || {providedIn: null};
|
||||||
const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) ||
|
const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) ||
|
||||||
isUseValueProvider(meta) || isUseExistingProvider(meta);
|
isUseValueProvider(meta) || isUseExistingProvider(meta);
|
||||||
@ -42,6 +40,7 @@ export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
|
|||||||
const compilerMeta: R3InjectableMetadataFacade = {
|
const compilerMeta: R3InjectableMetadataFacade = {
|
||||||
name: type.name,
|
name: type.name,
|
||||||
type: type,
|
type: type,
|
||||||
|
typeArgumentCount: 0,
|
||||||
providedIn: meta.providedIn,
|
providedIn: meta.providedIn,
|
||||||
ctorDeps: reflectDependencies(type),
|
ctorDeps: reflectDependencies(type),
|
||||||
userDeps: undefined
|
userDeps: undefined
|
||||||
|
Loading…
x
Reference in New Issue
Block a user