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
This commit is contained in:
parent
970d7f7c6f
commit
51156f3f07
|
@ -1258,7 +1258,7 @@ export class TestabilityRegistry {
|
|||
// @public
|
||||
export interface TrackByFunction<T> {
|
||||
// (undocumented)
|
||||
<U extends T>(index: number, item: U): any;
|
||||
<U extends T>(index: number, item: T & U): any;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
|
|
@ -116,5 +116,5 @@ export interface OnDestroy {
|
|||
}
|
||||
|
||||
export interface TrackByFunction<T> {
|
||||
<U extends T>(index: number, item: U): any;
|
||||
<U extends T>(index: number, item: T&U): any;
|
||||
}
|
||||
|
|
|
@ -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: '<div *ngFor="let item of anyList; trackBy: trackByBase">{{item.name}}</div>',
|
||||
})
|
||||
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: '<div *ngFor="let item of unrelatedList; trackBy: trackByBase">{{item.name}}</div>',
|
||||
})
|
||||
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<UnrelatedType>'.`);
|
||||
});
|
||||
|
||||
it('should infer the context of NgFor', () => {
|
||||
env.tsconfig({strictTemplates: true});
|
||||
env.write('test.ts', `
|
||||
|
|
|
@ -167,7 +167,7 @@ export interface TrackByFunction<T> {
|
|||
* @param index The index of the item within the iterable.
|
||||
* @param item The item in the iterable.
|
||||
*/
|
||||
<U extends T>(index: number, item: U): any;
|
||||
<U extends T>(index: number, item: T&U): any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue