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> {
|
export declare interface TrackByFunction<T> {
|
||||||
(index: number, item: T): any;
|
<U extends T>(index: number, item: U): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare const TRANSLATIONS: InjectionToken<string>;
|
export declare const TRANSLATIONS: InjectionToken<string>;
|
||||||
|
|
|
@ -116,5 +116,5 @@ export interface OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrackByFunction<T> {
|
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();
|
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', () => {
|
it('should infer the context of NgFor', () => {
|
||||||
env.tsconfig({strictTemplates: true});
|
env.tsconfig({strictTemplates: true});
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
|
|
|
@ -157,11 +157,17 @@ export interface IterableChangeRecord<V> {
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface TrackByFunction<T> {
|
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 index The index of the item within the iterable.
|
||||||
* @param item The item in 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