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
This commit is contained in:
JoostK 2021-05-07 22:28:18 +02:00 committed by Jessica Janiuk
parent 9d290b4fef
commit 85c7f7691e
4 changed files with 46 additions and 3 deletions

View File

@ -952,7 +952,7 @@ export declare class TestabilityRegistry {
}
export declare interface TrackByFunction<T> {
(index: number, item: T): any;
<U extends T>(index: number, item: U): any;
}
export declare const TRANSLATIONS: InjectionToken<string>;

View File

@ -116,5 +116,5 @@ export interface OnDestroy {
}
export interface TrackByFunction<T> {
(index: number, item: T): any;
<U extends T>(index: number, item: U): any;
}

View File

@ -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: '<div *ngFor="let derived of derivedList; trackBy: trackByBase">{{derived.name}}</div>',
})
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', `

View File

@ -157,11 +157,17 @@ export interface IterableChangeRecord<V> {
* @publicApi
*/
export interface TrackByFunction<T> {
// 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;
<U extends T>(index: number, item: U): any;
}
/**