diff --git a/aio/content/guide/template-typecheck.md b/aio/content/guide/template-typecheck.md
index 39b13dd0ec..0a5b35fc9e 100644
--- a/aio/content/guide/template-typecheck.md
+++ b/aio/content/guide/template-typecheck.md
@@ -120,6 +120,7 @@ In case of a false positive like these, there are a few options:
|`strictDomLocalRefTypes`|Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for ``.|
|`strictOutputEventTypes`|Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`.|
|`strictDomEventTypes`|Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`.|
+|`strictContextGenerics`|Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be `any`.|
If you still have issues after troubleshooting with these flags, you can fall back to full mode by disabling `strictTemplates`.
diff --git a/packages/compiler-cli/src/ngtsc/imports/index.ts b/packages/compiler-cli/src/ngtsc/imports/index.ts
index 43f63e7c28..6d0c1e9073 100644
--- a/packages/compiler-cli/src/ngtsc/imports/index.ts
+++ b/packages/compiler-cli/src/ngtsc/imports/index.ts
@@ -9,7 +9,7 @@
export {AliasStrategy, AliasingHost, FileToModuleAliasingHost, PrivateExportAliasingHost} from './src/alias';
export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAndRewriteCoreSymbol} from './src/core';
export {DefaultImportRecorder, DefaultImportTracker, NOOP_DEFAULT_IMPORT_RECORDER} from './src/default';
-export {AbsoluteModuleStrategy, FileToModuleHost, FileToModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy} from './src/emitter';
+export {AbsoluteModuleStrategy, FileToModuleHost, FileToModuleStrategy, ImportFlags, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy} from './src/emitter';
export {Reexport} from './src/reexport';
-export {ImportFlags, OwningModule, Reference} from './src/references';
+export {OwningModule, Reference} from './src/references';
export {ModuleResolver} from './src/resolver';
diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts
index d086b7f694..b577f28ad2 100644
--- a/packages/compiler-cli/src/ngtsc/program.ts
+++ b/packages/compiler-cli/src/ngtsc/program.ts
@@ -504,6 +504,7 @@ export class NgtscProgram implements api.Program {
// Pipes are checked in View Engine so there is no strictness flag.
checkTypeOfPipes: true,
strictSafeNavigationTypes: strictTemplates,
+ useContextGenericType: strictTemplates,
};
} else {
typeCheckingConfig = {
@@ -521,6 +522,7 @@ export class NgtscProgram implements api.Program {
checkTypeOfNonDomReferences: false,
checkTypeOfPipes: false,
strictSafeNavigationTypes: false,
+ useContextGenericType: false,
};
}
@@ -549,6 +551,9 @@ export class NgtscProgram implements api.Program {
if (this.options.strictAttributeTypes !== undefined) {
typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
}
+ if (this.options.strictContextGenerics !== undefined) {
+ typeCheckingConfig.useContextGenericType = this.options.strictContextGenerics;
+ }
// Execute the typeCheck phase of each decorator in the program.
const prepSpan = this.perfRecorder.start('typeCheckPrep');
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts
index d5ccb7785e..1032c4b2a5 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts
@@ -211,6 +211,15 @@ export interface TypeCheckingConfig {
* This is currently an unsupported feature.
*/
checkQueries: false;
+
+ /**
+ * Whether to use any generic types of the context component.
+ *
+ * If this is `true`, then if the context component has generic types, those will be mirrored in
+ * the template type-checking context. If `false`, any generic type parameters of the context
+ * component will be set to `any` during type-checking.
+ */
+ useContextGenericType: boolean;
}
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
index 049da807ee..b53b739753 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
@@ -57,7 +57,7 @@ export function generateTypeCheckBlock(
throw new Error(
`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
}
- const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName)];
+ const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName, env.config.useContextGenericType)];
const scopeStatements = scope.render();
const innerBody = ts.createBlock([
@@ -73,7 +73,7 @@ export function generateTypeCheckBlock(
/* modifiers */ undefined,
/* asteriskToken */ undefined,
/* name */ name,
- /* typeParameters */ ref.node.typeParameters,
+ /* typeParameters */ env.config.useContextGenericType ? ref.node.typeParameters : undefined,
/* parameters */ paramList,
/* type */ undefined,
/* body */ body);
@@ -925,12 +925,18 @@ class Scope {
* parameters listed (without their generic bounds).
*/
function tcbCtxParam(
- node: ClassDeclaration, name: ts.EntityName): ts.ParameterDeclaration {
+ node: ClassDeclaration, name: ts.EntityName,
+ useGenericType: boolean): ts.ParameterDeclaration {
let typeArguments: ts.TypeNode[]|undefined = undefined;
// Check if the component is generic, and pass generic type parameters if so.
if (node.typeParameters !== undefined) {
- typeArguments =
- node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined));
+ if (useGenericType) {
+ typeArguments =
+ node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined));
+ } else {
+ typeArguments =
+ node.typeParameters.map(() => ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
+ }
}
const type = ts.createTypeReferenceNode(name, typeArguments);
return ts.createParameter(
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts
index 6c127651d6..aecbb168cc 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts
@@ -163,6 +163,7 @@ export const ALL_ENABLED_CONFIG: TypeCheckingConfig = {
checkTypeOfNonDomReferences: true,
checkTypeOfPipes: true,
strictSafeNavigationTypes: true,
+ useContextGenericType: true,
};
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
@@ -217,6 +218,7 @@ export function tcb(
checkTypeOfPipes: true,
checkTemplateBodies: true,
strictSafeNavigationTypes: true,
+ useContextGenericType: true,
};
options = options || {
emitSpans: false,
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
index 50f9aa3e08..2caf51c9ae 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
@@ -326,6 +326,7 @@ describe('type check blocks', () => {
checkTypeOfNonDomReferences: true,
checkTypeOfPipes: true,
strictSafeNavigationTypes: true,
+ useContextGenericType: true,
};
describe('config.applyTemplateContextGuards', () => {
@@ -567,5 +568,20 @@ describe('type check blocks', () => {
expect(block).toContain('(((ctx).a) != null ? ((ctx).a)!.b : null as any)');
});
});
+
+ describe('config.strictContextGenerics', () => {
+ const TEMPLATE = `Test`;
+
+ it('should use the generic type of the context when enabled', () => {
+ const block = tcb(TEMPLATE);
+ expect(block).toContain('function Test_TCB(ctx: Test)');
+ });
+
+ it('should use any for the context generic type when disabled', () => {
+ const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, useContextGenericType: false};
+ const block = tcb(TEMPLATE, undefined, DISABLED_CONFIG);
+ expect(block).toContain('function Test_TCB(ctx: Test)');
+ });
+ });
});
});
diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts
index 11eed7c2ad..9ffb7ce65c 100644
--- a/packages/compiler-cli/src/transformers/api.ts
+++ b/packages/compiler-cli/src/transformers/api.ts
@@ -215,6 +215,19 @@ export interface CompilerOptions extends ts.CompilerOptions {
*/
strictDomEventTypes?: boolean;
+ /**
+ * Whether to include the generic type of components when type-checking the template.
+ *
+ * If no component has generic type parameters, this setting has no effect.
+ *
+ * If a component has generic type parameters and this setting is `true`, those generic parameters
+ * will be included in the context type for the template. If `false`, any generic parameters will
+ * be set to `any` in the template context type.
+ *
+ * Defaults to `false`, even if "fullTemplateTypeCheck" is set.
+ */
+ strictContextGenerics?: boolean;
+
// Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
// import module specifiers. This is false by default, and exists to support running ngtsc
// within Google. This option is internal and is used by the ng_module.bzl rule to switch
diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
index 74ce588220..33aa8eae48 100644
--- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
@@ -972,7 +972,8 @@ export declare class AnimationEvent {
});
it('should constrain types using type parameter bounds', () => {
- env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true});
+ env.tsconfig(
+ {fullTemplateTypeCheck: true, strictInputTypes: true, strictContextGenerics: true});
env.write('test.ts', `
import {CommonModule} from '@angular/common';
import {Component, Input, NgModule} from '@angular/core';