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; } /**