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:
parent
9d290b4fef
commit
85c7f7691e
|
@ -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>;
|
||||
|
|
|
@ -116,5 +116,5 @@ export interface OnDestroy {
|
|||
}
|
||||
|
||||
export interface TrackByFunction<T> {
|
||||
(index: number, item: T): any;
|
||||
<U extends T>(index: number, item: U): any;
|
||||
}
|
||||
|
|
|
@ -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', `
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue