From 200cc31df4a194221694bd853265a05e870a246a Mon Sep 17 00:00:00 2001 From: JoostK Date: Fri, 7 May 2021 22:28:18 +0200 Subject: [PATCH] fix(common): infer correct type when `trackBy` is used in `ngFor` (#41995) When a `trackBy` function is used that accepts a supertype of the iterated array's type, the loop variable would undesirably be inferred as the supertype instead of the array's item type. This commit adds an inferred type parameter to `TrackByFunction` to allow an extra degree of freedom, enabling the loop value to be inferred as the most narrow type. Fixes #40125 PR Close #41995 --- goldens/public-api/core/core.d.ts | 2 +- .../src/ngtsc/testing/fake_core/index.ts | 2 +- .../test/ngtsc/template_typecheck_spec.ts | 37 +++++++++++++++++++ .../differs/iterable_differs.ts | 8 +++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index 21137006a197a..79aa358ef659b 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 7632c14450dfd..db86935d3d91a 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 ce9bc10924749..8a34d3e07b10f 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 ec90548e33181..bd2c7543cff4d 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; } /**