I want make a unique trackBy directive by passing a property name for track the ngFor item, this is the code:
import { NgForOf } from '@angular/common';
import { Directive, Host, Input } from '@angular/core';
@Directive({
selector: '[ngForTrackByProp]',
})
export class NgForTrackByPropDirective<T> {
private propertyName: keyof T;
constructor(@Host() public ngForOf: NgForOf<T>) {
this.ngForOf.ngForTrackBy = this.trackBy.bind(this);
}
trackBy(index: number, item: T) {
if (!this.propertyName) {
throw new Error(`Property name not defined`);
}
if (typeof item[this.propertyName] === 'undefined') {
throw new Error(`Property "${this.propertyName}" is undefined`);
}
const value = item[this.propertyName];
console.log(
`Item "${index}" trackByProp "${this.propertyName}" with value "${value}"`
);
return value;
}
@Input()
set ngForTrackByProp(value: keyof T) {
this.propertyName = value;
}
static ngTemplateContextGuard<T>(
dir: NgForTrackByPropDirective<T>,
ctx: any
): ctx is NgForTrackByPropDirective<T> {
return true;
}
}
Usage:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<ul *ngFor="let item of list; trackByProp: 'id'">
<li>{{ item.id }} {{ item.name }}</li>
</ul>`,
})
export class AppComponent {
list = [
{ id: 0, name: 'foo' },
{ id: 1, name: 'bar' },
{ id: 2, name: 'baz' },
];
}
this the ONLINE DEMO
The code works, but I want to make sure the property passed is a key of the item of the collection and in case it isn't, let the error be a compile error, eg.:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<ul *ngFor="let item of list; trackByProp: 'XXXXXXXXXXXXX'">
<li>{{ item.id }} {{ item.name }}</li>
</ul>`,
})
export class AppComponent {
list = [
{ id: 0, name: 'foo' },
{ id: 1, name: 'bar' },
{ id: 2, name: 'baz' },
];
}
side note, my tsconfig.json:
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictTemplates": true,
"strictInjectionParameters": true
}
CodePudding user response:
You can narrow the type of an item in passing array by defining ngForOf
@Input
property:
import { NgIterable, ... } from '@angular/core';
...
export class NgForTrackByPropDirective<T> {
@Input() ngForOf: NgIterable<T>;
constructor(@Host() public ngForOfDir: NgForOf<T>) {
this.ngForOfDir.ngForTrackBy = this.trackBy.bind(this);
}
...