typescript - Angular global trackBy-egenskap direktiv med strikt typkontroll


Jag vill skapa en unik trackBy-direktiv genom att skicka ett egenskapsnamn för att spåra ngFor-elementet, detta är koden:

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;
  }
}

Användning :

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' },
  ];
}

detta är ONLINE-DEMON

Koden fungerar, men jag vill försäkra mig om att egenskapen som skickas är en nyckel till objektet i samlingen och om den inte är det, låt felet bli ett kompilationsfel, t.ex.:

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' },
  ];
}

sidonotering, min tsconfig.json:

"angularCompilerOptions": {
  "fullTemplateTypeCheck": true,
  "strictTemplates": true,
  "strictInjectionParameters": true
}

Svar #1

Du kan begränsa typen av ett objekt i en passerande array genom att definiera ngForOf @Input-egenskap:

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);
  }
  ...

Gafflad Stackblitz