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
|
// @public
|
||||||
export interface TrackByFunction<T> {
|
export interface TrackByFunction<T> {
|
||||||
// (undocumented)
|
// (undocumented)
|
||||||
<U extends T>(index: number, item: U): any;
|
<U extends T>(index: number, item: T & U): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
|
|
|
@ -116,5 +116,5 @@ export interface OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrackByFunction<T> {
|
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();
|
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', () => {
|
it('should infer the context of NgFor', () => {
|
||||||
env.tsconfig({strictTemplates: true});
|
env.tsconfig({strictTemplates: true});
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
|
|
|
@ -167,7 +167,7 @@ export interface TrackByFunction<T> {
|
||||||
* @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.
|
||||||
*/
|
*/
|
||||||
<U extends T>(index: number, item: U): any;
|
<U extends T>(index: number, item: T&U): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue