diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index 21137006a1..79aa358ef6 100644 --- a/goldens/public-api/core/core.d.ts +++ b/goldens/public-api/core/core.d.ts @@ -952,7 +952,7 @@ export declare class TestabilityRegistry { } export declare interface TrackByFunction { - (index: number, item: T): any; + (index: number, item: U): any; } export declare const TRANSLATIONS: InjectionToken; diff --git a/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts b/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts index 7632c14450..db86935d3d 100644 --- a/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts +++ b/packages/compiler-cli/src/ngtsc/testing/fake_core/index.ts @@ -116,5 +116,5 @@ export interface OnDestroy { } export interface TrackByFunction { - (index: number, item: T): any; + (index: number, item: U): any; } diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index ce9bc10924..8a34d3e07b 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -940,6 +940,43 @@ export declare class AnimationEvent { env.driveMain(); }); + // https://github.com/angular/angular/issues/40125 + it('should accept NgFor iteration when trackBy is used with a wider type', () => { + env.tsconfig({strictTemplates: true}); + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + interface Base { + id: string; + } + + interface Derived extends Base { + name: string; + } + + @Component({ + selector: 'test', + template: '
{{derived.name}}
', + }) + class TestCmp { + derivedList!: Derived[]; + + trackByBase(index: number, item: Base): string { + return item.id; + } + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + it('should infer the context of NgFor', () => { env.tsconfig({strictTemplates: true}); env.write('test.ts', ` diff --git a/packages/core/src/change_detection/differs/iterable_differs.ts b/packages/core/src/change_detection/differs/iterable_differs.ts index ec90548e33..bd2c7543cf 100644 --- a/packages/core/src/change_detection/differs/iterable_differs.ts +++ b/packages/core/src/change_detection/differs/iterable_differs.ts @@ -157,11 +157,17 @@ export interface IterableChangeRecord { * @publicApi */ export interface TrackByFunction { + // Note: the type parameter `U` enables more accurate template type checking in case a trackBy + // function is declared using a base type of the iterated type. The `U` type gives TypeScript + // additional freedom to infer a narrower type for the `item` parameter type, instead of imposing + // the trackBy's declared item type as the inferred type for `T`. + // See https://github.com/angular/angular/issues/40125 + /** * @param index The index of the item within the iterable. * @param item The item in the iterable. */ - (index: number, item: T): any; + (index: number, item: U): any; } /**