tegel icon indicating copy to clipboard operation
tegel copied to clipboard

[Bug report]: Popover based elements break when rendered with Angular @for

Open adarean5 opened this issue 6 months ago • 0 comments

Requirements before reporting

  • [X] No duplicated issue reported.
  • [X] I have checked the latest version if the bug exist there. See all available packages at npmJS.com
  • [X] I have followed the installation guide.

Package versions

@scania/tegel: 1.13.0 @scania/tegel-angular: 1.13.0

Browser

Chrome

Framework

Angular

Version

17

Reproduction steps

  • Create an array of objects. Each object should have an id field that uniquely identifies it.
  • Render an array of objects using Angular's @for directive. Each rendered item should contain a tds-popover-menu
  • Make sure to use the track function of the @for loop and track the items by their unique ID property
  • Change the order of items

Code example

import { Component } from "@angular/core";
import { TegelModule } from "@scania/tegel-angular";

@Component({
  selector: "example-product-list",
  standalone: true,
  imports: [TegelModule],
  template: `
    @for (product of products; track product.productId) {
      <span>{{ product.description }}</span>

      <tds-button size="sm" [attr.id]="product.productId">
        <tds-icon name="kebab" slot="icon"></tds-icon>
      </tds-button>

      <tds-popover-menu [selector]="'#' + product.productId">
        <tds-popover-menu-item>
          <button (click)="moveProductDown(product.productId)">
            Move down
          </button>
        </tds-popover-menu-item>
      </tds-popover-menu>
      <tds-divider></tds-divider>
    }
  `,
})
export class ExampleProductListComponent {
  public products = [
    {
      productId: "product-id-1",
      description: "Truck",
    },
    {
      productId: "product-id-2",
      description: "Bus",
    },
  ];

  public moveProductDown(productId: string): void {
    const productIndex = this.products.findIndex(
      product => product.productId === productId
    );

    if (productIndex >= this.products.length - 1) {
      return;
    }

    const newProducts = structuredClone(this.products);
    const matchingProduct = newProducts[productIndex];
    newProducts.splice(productIndex, 1);
    newProducts.splice(productIndex + 1, 0, matchingProduct);
    this.products = structuredClone(newProducts);
  }
}

Expected behavior

The component above renders two items. Each item contains a button that opens a popover menu. After opening the popover menu of the "Truck" item and clicking on the "Move down" button, both menu buttons should still open their respective tds-popover-menu.

Actual behavior

After opening the popover menu of the "Truck" item and clicking on the "Move down" button, the popover menu of the "Bus" item no longer opens when clicking on the menu button.

This only seems to happen when the track function of the @for loop is used to track items by their unique IDs. When using track $index instead of track product.productId this does not occur, but using track $index causes performance issues, since angular has to re-render the whole list instead of just the elements that changed.

adarean5 avatar Aug 08 '24 14:08 adarean5