diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index a96cc61ad0..36909e4226 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -143,6 +143,7 @@ export function extractDirectiveMetadata( inputs: {...inputsFromMeta, ...inputsFromFields}, outputs: {...outputsFromMeta, ...outputsFromFields}, queries, selector, type: new WrappedNodeExpr(clazz.name !), + typeArgumentCount: (clazz.typeParameters || []).length, typeSourceSpan: null !, usesInheritance, }; return {decoratedElements, decorator: directive, metadata}; diff --git a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts index b5353da37c..f54aaef524 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts @@ -305,8 +305,14 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } } - visitExpressionType(type: ExpressionType, context: Context): any { - return type.value.visitExpression(this, context); + visitExpressionType(type: ExpressionType, context: Context): string { + const exprStr = type.value.visitExpression(this, context); + if (type.typeParams !== null) { + const typeSegments = type.typeParams.map(param => param.visitType(this, context)); + return `${exprStr}<${typeSegments.join(',')}>`; + } else { + return exprStr; + } } visitArrayType(type: ArrayType, context: Context): string { diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index dd58b7fecd..46a592c770 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -47,7 +47,11 @@ export class BuiltinType extends Type { } export class ExpressionType extends Type { - constructor(public value: Expression, modifiers: TypeModifier[]|null = null) { super(modifiers); } + constructor( + public value: Expression, modifiers: TypeModifier[]|null = null, + public typeParams: Type[]|null = null) { + super(modifiers); + } visitType(visitor: TypeVisitor, context: any): any { return visitor.visitExpressionType(this, context); } @@ -1240,6 +1244,9 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor visitBuiltinType(type: BuiltinType, context: any): any { return this.visitType(type, context); } visitExpressionType(type: ExpressionType, context: any): any { type.value.visitExpression(this, context); + if (type.typeParams !== null) { + type.typeParams.forEach(param => this.visitType(param, context)); + } return this.visitType(type, context); } visitArrayType(type: ArrayType, context: any): any { return this.visitType(type, context); } @@ -1496,8 +1503,9 @@ export function importType( } export function expressionType( - expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType { - return new ExpressionType(expr, typeModifiers); + expr: Expression, typeModifiers: TypeModifier[] | null = null, + typeParams: Type[] | null = null): ExpressionType { + return new ExpressionType(expr, typeModifiers, typeParams); } export function typeofExpr(expr: Expression) { diff --git a/packages/compiler/src/output/ts_emitter.ts b/packages/compiler/src/output/ts_emitter.ts index ad01cf1a67..e82a3258e0 100644 --- a/packages/compiler/src/output/ts_emitter.ts +++ b/packages/compiler/src/output/ts_emitter.ts @@ -361,6 +361,11 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor visitExpressionType(ast: o.ExpressionType, ctx: EmitterVisitorContext): any { ast.value.visitExpression(this, ctx); + if (ast.typeParams !== null) { + ctx.print(null, '<'); + this.visitAllObjects(type => this.visitType(type, ctx), ast.typeParams, ctx, ','); + ctx.print(null, '>'); + } return null; } diff --git a/packages/compiler/src/render3/util.ts b/packages/compiler/src/render3/util.ts index dd5fe5214e..ae619ef2da 100644 --- a/packages/compiler/src/render3/util.ts +++ b/packages/compiler/src/render3/util.ts @@ -36,3 +36,14 @@ export function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`); } + +export function typeWithParameters(type: o.Expression, numParams: number): o.ExpressionType { + let params: o.Type[]|null = null; + if (numParams > 0) { + params = []; + for (let i = 0; i < numParams; i++) { + params.push(o.DYNAMIC_TYPE); + } + } + return o.expressionType(type, null, params); +} diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 8ebcc44fb8..a315990f5e 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -25,6 +25,11 @@ export interface R3DirectiveMetadata { */ type: o.Expression; + /** + * Number of generic type parameters of the type itself. + */ + typeArgumentCount: number; + /** * A source span for the directive type. */ diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 96d7167adc..0905821b33 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -20,11 +20,12 @@ import {ParseSourceSpan, typeSourceSpan} from '../../parse_util'; import {CssSelector, SelectorMatcher} from '../../selector'; import {BindingParser} from '../../template_parser/binding_parser'; import {OutputContext, error} from '../../util'; - import * as t from '../r3_ast'; import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory'; import {Identifiers as R3} from '../r3_identifiers'; import {Render3ParseResult} from '../r3_template_transform'; +import {typeWithParameters} from '../util'; + import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; import {BindingScope, TemplateDefinitionBuilder} from './template'; import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; @@ -93,9 +94,10 @@ export function compileDirectiveFromMetadata( // string literal, which must be on one line. const selectorForType = (meta.selector || '').replace(/\n/g, ''); - const type = new o.ExpressionType(o.importExpr( - R3.DirectiveDef, - [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(selectorForType))])); + const type = new o.ExpressionType(o.importExpr(R3.DirectiveDef, [ + typeWithParameters(meta.type, meta.typeArgumentCount), + new o.ExpressionType(o.literal(selectorForType)) + ])); return {expression, type}; } @@ -167,9 +169,10 @@ export function compileComponentFromMetadata( const selectorForType = (meta.selector || '').replace(/\n/g, ''); const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); - const type = new o.ExpressionType(o.importExpr( - R3.ComponentDef, - [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(selectorForType))])); + const type = new o.ExpressionType(o.importExpr(R3.ComponentDef, [ + typeWithParameters(meta.type, meta.typeArgumentCount), + new o.ExpressionType(o.literal(selectorForType)) + ])); return {expression, type}; } @@ -252,6 +255,7 @@ function directiveMetadataFromGlobalMetadata( return { name, type: outputCtx.importExpr(directive.type.reference), + typeArgumentCount: 0, typeSourceSpan: typeSourceSpan(directive.isComponent ? 'Component' : 'Directive', directive.type), selector: directive.selector, diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 70ba78624d..de24c9035d 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -155,6 +155,7 @@ function directiveMetadata(type: Type, metadata: Directive): R3DirectiveMet return { name: type.name, type: new WrappedNodeExpr(type), + typeArgumentCount: 0, selector: metadata.selector !, deps: reflectDependencies(type), host, inputs: {...inputsFromMetadata, ...inputsFromType},