I am looking to create a custom trackBy directive that allows me to specify a property name for tracking ngFor items. Here is the code snippet:
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}" is trackBy'ed by property "${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 example:
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' },
];
}
You can view the ONLINE DEMO here.
The current implementation works, but I want to ensure that the property passed is a valid key of the collection item. If it isn't, I would like to generate a compile-time error, as shown in the following example:
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' },
];
}
As a side note, my tsconfig.json file includes the following configuration:
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictTemplates": true,
"strictInjectionParameters": true
}