Angular: Tailoring the Context Menu

Currently, I am utilizing a helpful tutorial to implement a custom context menu feature. The main issue I am facing is that when a user interacts with the list items, I want the correct index of each item to be displayed. However, at the moment, clicking on any item only shows the last index in the list instead of the specific index of the clicked item. For example, if my list contains 3 items, clicking on any item displays '2' as the index! I need guidance on how to ensure that the correct index of each list item is shown.

Below is the code snippet I am currently working with:

<div *ngFor="let item of items; let i = index" (contextmenu)="displayContextMenu($event); false">
    <span> {{item}}</span>
    <app-context-menu
        *ngIf="rightClickMenuItems.length > 0 && isDisplayContextMenu"
        [ngStyle]="getRightClickMenuStyle()"        
        [contextMenuItems]="rightClickMenuItems"
        (onContextMenuItemClick)="handleMenuItemClick($event, i)"
    ></app-context-menu>
</div>

ts file:

items = ["item0","item1","item2"];

isDisplayContextMenu!: boolean;
rightClickMenuItems: Array<ContextMenu> = [];

rightClickMenuPositionX!: number;
rightClickMenuPositionY!: number;

displayContextMenu(event: any) {
  this.isDisplayContextMenu = true;
  this.rightClickMenuItems = [
    {
      menuText: 'Print index',
      menuEvent: 'Handle print index',
    },
  ];

  this.rightClickMenuPositionX = event.clientX;
  this.rightClickMenuPositionY = event.clientY;
}

handleMenuItemClick(event: any, index: number) {
  switch (event.data) {
    case this.rightClickMenuItems[0].menuEvent:
      this.printIndex(index);
      break;
  }
}

printIndex(index: number){
  alert(index);
}

@HostListener('document:click')
  documentClick(): void {
  this.isDisplayContextMenu = false;
}

getRightClickMenuStyle() {
  return {
    position: 'fixed',
    left: `${this.rightClickMenuPositionX}px`,
    top: `${this.rightClickMenuPositionY}px`
  }
}

Context menu component:

.html:

<ng-container>
    <div>
        <div *ngFor="let menuItem of contextMenuItems; index as i"
            (click)="onContextMenuClick($event, menuItem.menuEvent)">
            {{ menuItem.menuText }}
        </div>
    </div>
</ng-container>

.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { ContextMenu } from '../../_models/contextMenu.model';
import { NgIf, NgFor } from '@angular/common';

@Component({
    selector: 'app-context-menu',
    templateUrl: './context-menu.component.html',
    styleUrls: ['./context-menu.component.css'],
    standalone: true,
    imports: [NgIf, NgFor]
})

export class ContextMenuComponent {
    @Input() contextMenuItems!: Array<ContextMenu>;
    @Output() onContextMenuItemClick: EventEmitter<any> = new EventEmitter<any>();
    onContextMenuClick(event: any, data: any): any {
        this.onContextMenuItemClick.emit({
            event,
            data,
        });
    }
}

model:

export interface ContextMenu {
    menuText: any;
    menuEvent: any;
}

Answer №1

  1. Consider moving the context-menu outside of the *ngFor loop since a single context menu is sufficient for the entire table.
  2. To determine the selected index, utilize data-id data attributes to identify the specific item that was chosen.

code

<div *ngFor="let item of items; let i = index" (contextmenu)="displayContextMenu($event); false"
    [attr.data-id]="item">
    <span> {{item}}</span>
  1. To pinpoint which item was selected, access the attribute containing this information and store the selected index either in the main component or within the context menu itself.

    const id = event.target.parentElement.dataset.id ? event.target.parentElement.dataset.id : event.target.dataset.id; const foundIndex = this.items.findIndex((x) => x === id); if (foundIndex > -1) { this.index = foundIndex;

Finally, display the index by using an alert!

this.printIndex(this.index);

main.ts

import { CommonModule } from '@angular/common';
import { Component, HostListener } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import {
  ContextMenu,
  ContextMenuComponent,
} from './context-menu/context-menu.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, ContextMenuComponent],
  template: `
    <div *ngFor="let item of items; let i = index" (contextmenu)="displayContextMenu($event); false"
    [attr.data-id]="item">
    <span> {{item}}</span>
</div>
    <app-context-menu
        *ngIf="rightClickMenuItems.length > 0 && isDisplayContextMenu"
        [ngStyle]="getRightClickMenuStyle()"
        [contextMenuItems]="rightClickMenuItems"
        (onContextMenuItemClick)="handleMenuItemClick($event)"
    ></app-context-menu>
  `,
})
export class App {
  items = ['item0', 'item1', 'item2'];
  index = -1;

  isDisplayContextMenu!: boolean;
  rightClickMenuItems: Array<ContextMenu> = [];

  rightClickMenuPositionX!: number;
  rightClickMenuPositionY!: number;

  displayContextMenu(event: any) {
    const id = event.target.parentElement.dataset.id
      ? event.target.parentElement.dataset.id
      : event.target.dataset.id;
    const foundIndex = this.items.findIndex((x) => x === id);
    if (foundIndex > -1) {
      this.index = foundIndex;
      this.isDisplayContextMenu = true;
      this.rightClickMenuItems = [
        {
          menuText: 'Print index',
          menuEvent: 'Handle print index',
        },
      ];

      this.rightClickMenuPositionX = event.clientX;
      this.rightClickMenuPositionY = event.clientY;
    }
  }

  handleMenuItemClick(event: any) {
    switch (event.data) {
      case this.rightClickMenuItems[0].menuEvent:
        this.printIndex(this.index);
        break;
    }
  }

  printIndex(index: number) {
    alert(index);
  }

  @HostListener('document:click')
  documentClick(): void {
    this.isDisplayContextMenu = false;
  }

  getRightClickMenuStyle() {
    return {
      position: 'fixed',
      left: `${this.rightClickMenuPositionX}px`,
      top: `${this.rightClickMenuPositionY}px`,
    };
  }
}

bootstrapApplication(App);

context menu

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { NgIf, NgFor } from '@angular/common';

export interface ContextMenu {
  menuText: any;
  menuEvent: any;
}

@Component({
  selector: 'app-context-menu',
  template: `
  <ng-container>
  <div>
    <div
      *ngFor="let menuItem of contextMenuItems; index as i"
      (click)="onContextMenuClick($event, menuItem.menuEvent)"
    >
      {{ menuItem.menuText }}
    </div>
  </div>
</ng-container>

  `,
  standalone: true,
  imports: [NgIf, NgFor],
})
export class ContextMenuComponent {
  private id!: string;
  @Input() contextMenuItems!: Array<ContextMenu>;
  @Output() onContextMenuItemClick: EventEmitter<any> = new EventEmitter<any>();
  onContextMenuClick(event: any, data: any): any {
    this.onContextMenuItemClick.emit({
      event,
      data,
    });
  }
}

Visit stackblitz for more details.

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

HTML various button designs - such as a cogwheel

I need a button on my Angular/Electron project that resembles a gear icon. I came across these resources: here and here. However, when I tried to implement them, they didn't work as expected. Currently, the button looks like this: <button class= ...

Transform the subscription into a string

When I use the http method to retrieve a single user, the output logged in the console looks like this: this.usersService.getOneUser().subscribe(data => { console.log(data) }); email: "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data- ...

Guide to customizing action button color on MatSnackbar?

Currently, I am encountering an issue with the snackbar in my Angular 9 project. Despite adding CSS styles to both the component.scss file and the global style.scss file, the action button on the snackbar displays the same background and text color. In an ...

What can be done to prevent the angular material select from overflowing the screen?

I have integrated an Angular Material Select component into my application, which can be found here: https://material.angular.io/components/select/overview. The issue I am facing is that when the select element is positioned near the bottom of the screen a ...

The horizontal scrollbar in PrimeNG's p-tree is cleverly concealed until I reach the bottom of the div and start scrolling

I'm facing an issue where the horizontal scrollbar in my p-tree component is hidden until I scroll all the way down. I've tried various CSS modifications, but nothing seems to work. It's surprising that I couldn't find anyone else with ...

Issue with angular 8 radio button not being selected

Here is the HTML code I am working with: <div *ngFor="let option of systemEquipmentGroup"> <h5>{{option.optionGroupName}}</h5> <div> <label style="display: block;" * ...

What methods are available to prevent redundant types in Typescript?

Consider an enum scenario: enum AlertAction { RESET = "RESET", RESEND = "RESEND", EXPIRE = "EXPIRE", } We aim to generate various actions, illustrated below: type Action<T> = { type: T; payload: string; }; ty ...

What is the best way to limit input to only numbers and special characters?

Here is the code snippet I am working with: <input style="text-align: right;font-size: 12px;" class='input' (keyup.enter)="sumTotal($event)" type="text" [ngModel]="field.value" (focusin)="focusin()" (focusout)="format()" (keyup.ente ...

Fetch the JSON data and pass it to the ITest

Objective: Retrieve data from a JSON file and store it in the "test: ITest[]" array, then display it using console.log Challenge: The code is not functioning correctly. What element am I overlooking? Background: I am new to Angular Thank you! Ser ...

Angular 17 component fails to detect signal updates

When I set the value of a signal from component A using a service, it returns null when attempting to access the signal from component B (not the child). I recall this working in Angular 16, did something change in Angular 17? Service @Injectable({ pr ...

Tips for resolving the issue with the 'search input field in the header' across all pages in angular 5 with typescript

I currently have a search field in the header that retrieves a list of records when you type a search term and click on one of them. The search function utilizes debounceTime to reduce API requests, but I'm encountering an issue where the search doesn ...

AngularJS component downgraded without change detection

Currently, I am utilizing Angular's downgradeComponent for performance optimization purposes. You can find more information about it here: https://angular.io/api/upgrade/static/downgradeComponent. The Angular component I am working with is defined as ...

Angular 2 - Changes in component properties not reflected in view

I'm currently delving into Angular 2 and as far as I know, interpolated items in the view are supposed to automatically update when their corresponding variable changes in the model. However, in the following code snippet, I'm not observing this ...

Unable to locate module, encountered a webpack alias issue while using typescript and react

I'm currently in the process of setting up aliases in webpack. My goal is to make importing components in App.js easier by replacing: ./components/layout/Header/Header with: @components/layout/Header/Header This way, I can avoid potential issues w ...

After defining the NEXTAUTH_URL and NEXTAUTH_SECRET variables, the getServerSession(authOptions) function in NextJS is returning null

I've been attempting to set up OAuth with the Google provider for my Next.js 13 web application. Unfortunately, I'm encountering an issue where getServerSession(authOptions) is returning null. Despite trying various solutions such as setting NEXT ...

What is the method to update reference models in mongodb by generating documents within a different model?

In my API, I have three models: Patient, Doctor, and Reviews. The Reviews model is referenced in the Doctor model, with the intention that a patient can post a review for a specific doctor using Review.create(). However, even after the review document is c ...

Develop a TypeScript utility function for Prisma

Having trouble inferring the correct type for my utility function: function customUtilityFunction< P, R, A extends unknown[] >( prismaArgs /* Make "where" field optional as it is already defined inside findUnique method below */, fn: ( pris ...

Troubleshooting Angular 2: Issues with http.delete() Function

deleteTask function in this code is currently experiencing issues. All other methods are functioning properly and the URL is also working correctly. Your assistance would be greatly appreciated. constructor(private http:Http) { console.log('task ...

What is the proper way to utilize queries in BlitzJS?

I am attempting to extract data from a table by filtering based on the relationship with the Blitzjs framework. However, I am facing difficulties using queries as it seems to be the only option available. Every time I try to call the quer ...

How can I configure React Router V6 to include multiple :id parameters in a Route path, with some being optional?

Currently, I am utilizing react-router@6 and have a Route that was previously used in V5. The route is for vehicles and always requires one parameter (:id = vehicle id), but it also has an optional second parameter (:date = string in DD-MM-YYYY format): &l ...