Enforcing type safety with Angular's global trackBy property directive

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
}

Answer â„–1

To specify the type of an item in a passing array, you can narrow it down by setting the 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);
  }
  ...

New Stackblitz Fork

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Customizing content-type header in Angular httpclient

I need help sending a block of ndjson to an API using Angular httpClient. The API requires each JSON object to be newline delineated, rather than accepting an array of objects. This means I have to send a string of JSON objects with newlines between them. ...

Establishing global date restrictions for the DatePicker component in Angular 8 using TypeScript across the entire application

I am currently learning Angular 8 and I am looking to globally set the minimum and maximum dates for a datepicker in my application. I would like to accomplish this by using format-datepicker.ts. Any suggestions on how I can achieve this? Min date: Jan 1, ...

The powerful combination of Visual Studio 2015, TypeScript, Cordova, Angular 2, and System

I am encountering an issue with the loading of external modules using systemJS. I have created a small sample project for VS2015. Feel free to check out the code here: https://github.com/dbiele/TypeScript-Cordova-SystemJS After building the project and at ...

Sanitizing a string through manual effort

On my webpage, users can input text into a textarea. However, I want to ensure that the text they provide is properly sanitized before being saved as a string. I am struggling to understand how to manually sanitize this data using DomSanitizationService. ...

Dependency mismatch in main package.json and sub package.json

Imagine you have a project structure in Typescript set up as follows: root/ api/ package.json web/ package.json ... package.json In the main package.json file located in the root directory, Typescript is installed as a dependency to make ...

Having trouble downloading both an HTML file and a PDF file at the same time by clicking two separate buttons in an Angular 8 web API controller

I am encountering an issue with downloading files from my web application. I have two buttons - one for downloading a PDF file and another for downloading an HTML file. The problem arises when clicking on the link to download the HTML file first, as it do ...

Using JavaScript to dynamically calculate the sum of selected column values in Angular Datatables

I have a table set up where, if a checkbox is checked, the amounts are automatically summed and displayed at the top. However, I am encountering issues with the code below as it is not providing the exact sum values. Can anyone suggest a solution to this p ...

Troubleshooting the Conflict between Angular Update and Peer Dependency: @angular/[email protected]

I've been attempting to upgrade my Angular 12 application to version 13 following the official upgrade guide, but have run into some trouble. The npm error messages I'm getting are not providing clear explanations of the issue at hand. Here&apos ...

Jasmine-core steers clear of incorporating angular-devkit/build-angular into its installation process

Upon running ng serve, an error related to angular-devkit has surfaced. As a result, I proceeded to install it, but encountered the following error: npm install @angular-devkit/build-angular npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to re ...

Sorting arrays in Typescript

Is there a way to alphabetically sort an array of objects in Typescript based on a specific field? The array I have currently looks like this - https://i.stack.imgur.com/fQ3PA.png I'm interested in sorting it alphabetically by the 'channel' ...

Struggling to identify the error while utilizing Jasmine's throwError function

I am relatively new to using Jasmine and have been experimenting with the toThrowError() function. However, I can't seem to get my test to pass successfully. In one of my functions, I purposely throw an error: test.service.ts test(list:{}){ if ...

Tips for connecting Angular events to <input> elements in HTML?

Within my Angular application, the value of a variable is manipulated by an HTML element <input> through two-way binding like this: <input [(ngModel)]=variableName (OnKeyup)="DoSomething" > However, the two-way binding with ...

Error parsing Angular2 with ngFor directive

I'm attempting to utilize the ngFor directive in an angular 2 (alpha 35) project, but I keep encountering the error EXCEPTION: Can't bind to 'ngforOf' since it isn't a known property of the '' element and there are no mat ...

What is preventing me from being able to spyOn() specific functions within an injected service?

Currently, I am in the process of testing a component that involves calling multiple services. To simulate fake function calls, I have been injecting services and utilizing spyOn(). However, I encountered an issue where calling a specific function on one ...

Unlocking $refs with the Composition API in Vue3 - A step-by-step guide

I am currently exploring how to access $refs in Vue 3 using the Composition API. In my template, I have two child components and I specifically need to obtain a reference to one of them: <template> <comp-foo /> <comp-bar ref="ta ...

Is it possible for a node and @angular/cli based application to function without an internet connection?

As a newcomer to the world of angular/cli and node, I am eager to build an application using Angular and deploy it to an environment where all external communication is blocked by a firewall. In this restricted setting, even accessing search engines is imp ...

Mocked observables are returned when testing an Angular service that includes parameters

I'm currently exploring various types of unit testing and find myself struggling with a test for a service once again. Here is the function in my service that I need to test: Just to clarify: this.setParams returns an object like {name: 'Test&ap ...

Switching from MOCK API to real API in Angular: The step-by-step guide

I am a beginner in Angular and I am currently working on connecting a sample app to my API gateway called ContactApp. Right now, it is functioning with a mock API but I want to switch to using a real API server. I followed all the steps from a tutorial on ...

Developing personalized modules in Angular version 6

I am diving into the world of Angular customization and I have a goal of developing a flexible module in Angular 6. My plan is to create this module and place it in the Node_modules folder so that it can be easily utilized by other teams within the organi ...

Instructions on enabling a search feature within a resolver using [nestjs/graphql]

Issue Hey everyone, I'm having trouble with implementing a search resolver. The resolver search is supposed to take a query as a parameter and then use the useSearch function to retrieve data. However, I keep getting an error, which is displayed at t ...