From 51156f3f0798fe5bc6f69cf38ba2f2947aab89aa Mon Sep 17 00:00:00 2001 From: JoostK Date: Mon, 28 Jun 2021 21:35:03 +0200 Subject: [PATCH] fix(core): allow proper type inference when `ngFor` is used with a `trackBy` function (#42692) In #41995 the type of `TrackByFunction` was changed such that the declaration of a `trackBy` function did not cause the item type to be widened to the `trackBy`'s item type, which may be a supertype of the iterated type. This has introduced situations where the template type checker is now reporting errors for cases where a `trackBy` function is no longer assignable to `TrackByFunction`. This commit fixes the error by also including the item type `T` in addition to the constrained type parameter `U`, allowing TypeScript to infer an appropriate `T`. Fixes #42609 PR Close #42692 --- goldens/public-api/core/core.md | 2 +- .../src/ngtsc/testing/fake_core/index.ts | 2 +- .../test/ngtsc/template_typecheck_spec.ts | 72 +++++++++++++++++++ .../differs/iterable_differs.ts | 2 +- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/goldens/public-api/core/core.md b/goldens/public-api/core/core.md index 10291b1fc2..8c85de6074 100644 --- a/goldens/public-api/core/core.md +++ b/goldens/public-api/core/core.md @@ -1258,7 +1258,7 @@ export class TestabilityRegistry { // @public export interface TrackByFunction { // (undocumented) - (index: number, item: U): any; + (index: number, item: T & U): any; } // @public 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 db86935d3d..07af668366 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: U): any; + (index: number, item: T&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 9543acfcc9..1cc1497288 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -1086,6 +1086,78 @@ export declare class AnimationEvent { env.driveMain(); }); + // https://github.com/angular/angular/issues/42609 + it('should accept NgFor iteration when trackBy is used with an `any` array', () => { + env.tsconfig({strictTemplates: true}); + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + interface ItemType { + id: string; + } + + @Component({ + selector: 'test', + template: '
{{item.name}}
', + }) + class TestCmp { + anyList!: any[]; + + trackByBase(index: number, item: ItemType): string { + return item.id; + } + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + + it('should reject NgFor iteration when trackBy is incompatible with item type', () => { + env.tsconfig({strictTemplates: true}); + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + interface ItemType { + id: string; + } + + interface UnrelatedType { + name: string; + } + + @Component({ + selector: 'test', + template: '
{{item.name}}
', + }) + class TestCmp { + unrelatedList!: UnrelatedType[]; + + trackByBase(index: number, item: ItemType): string { + return item.id; + } + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText) + .toContain(`is not assignable to type 'TrackByFunction'.`); + }); + 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 bd2c7543cf..5a5fe7bb94 100644 --- a/packages/core/src/change_detection/differs/iterable_differs.ts +++ b/packages/core/src/change_detection/differs/iterable_differs.ts @@ -167,7 +167,7 @@ export interface TrackByFunction { * @param index The index of the item within the iterable. * @param item The item in the iterable. */ - (index: number, item: U): any; + (index: number, item: T&U): any; } /**