Guide to generating observables for click events on each row within an Angular Material table and effectively handling their responses

My journey to achieve this functionality started with an HTML button and also with an Angular Material button. However, when it came to implementing it in an Angular Material table, I faced challenges. Initially, I could only make it work on the first row using fromEvent to create observables that emit click events. When that didn't yield the desired result, I turned to Renderer2 for a solution. Despite my efforts, I struggled to find a way to subscribe to each click event emitted within the table, whether using merge with @ViewChildren() or handling DOM events directly inside an Angular Material Table.

//ts
import { Renderer2,AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { defer, fromEvent, Observable } from 'rxjs';
import { debounceTime, map, tap, switchMap } from 'rxjs/operators';

// modal de una tabla
export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
  {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
  {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
  {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
  {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
  {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
  {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
  {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
  {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];

/**
 * @title Basic use of `<table mat-table>`
 */

@Component({
  selector: 'app-material',
  templateUrl: './material.component.html',
  styleUrls: ['./material.component.scss']
})
export class MaterialComponent implements AfterViewInit , OnInit, OnDestroy{
  buttonsClick!: () => void;
  documentClick!: () => void;

 @ViewChild('testBtn', { static: true }) testBtn!: ElementRef<HTMLButtonElement>;

 event$ = defer(() => fromEvent(this.testBtn.nativeElement, 'click')).pipe(
   map(() => new Date().toString()),
   tap(console.log)
 )
 
 @ViewChild('pdfExterno',{ read: ElementRef }) pdfExterno!: ElementRef<HTMLButtonElement>;

 @ViewChild('docx',{ read: ElementRef }) docx!: ElementRef<HTMLButtonElement>;

 pdfExterno$ = defer(() => fromEvent(this.pdfExterno.nativeElement, 'click')).pipe(
   map(() => new Date().toString()),
   tap(console.log)
  )
  @ViewChild('pdf',{ read: ElementRef }) pdf!: ElementRef<HTMLButtonElement>;
 @ViewChild('mostrar',{ read: ElementRef }) mostrar!: ElementRef<HTMLButtonElement>;

 displayedColumns: string[] = ['position', 'name', 'weight', 'symbol','downloadPdfDoc'];
 dataSource = ELEMENT_DATA;
 // Another approach involved trying with Renderer2 to capture all click events, not just those of the first row
 constructor(private renderer: Renderer2) { }
 ngOnDestroy(): void {
 this.buttonsClick();
 this.documentClick();
 }

 ngOnInit(): void {
   this.event$.subscribe();
 }

 ngAfterViewInit() {
   this.pdfExterno$.subscribe()
   this.render();
   
   merge(
fromEvent(this.pdf.nativeElement, 'click'),
fromEvent(this.docx.nativeElement, 'click'),

).subscribe((event: Event | null)=> {
  console.log('from Merge', event?.target);
});

  
 }
 render(){
  this.documentClick = this.renderer.
  listen('document', 'click', (event: MouseEvent) => {

    console.log('From render docx', event)
   } )

 this.buttonsClick = this.renderer.
  listen(this.docx.nativeElement, 'click', (event: MouseEvent) => {

    console.log('From render docx', event)
   } )


  this.renderer.listen(this.pdf.nativeElement, 'click', (event) => {
    console.log('From render pdf', event)
  })
  this.renderer.listen(this.mostrar.nativeElement, 'click', (event) => {
    console.log('From Render show', event)
  })
}
  onDocumentClick(e: any): boolean | void {
    throw new Error('Method not implemented.');
  }
downloadPDF(row:any){
  console.log('pdf',row)
}

downloadDocx(row:any){
  console.log('docx',row)

  }

  onRowClicked(row:any): void{
    console.log('mat-row',row)
  }

  downloadPDFFromOutsideMatTable(){
    console.log('it works')
  }

}
// html
<h1>Without any issues outside the table</h1>
<section>
  <div>
    <button #testBtn>Click me</button>
  </div>
</section>
<button mat-raised-button
 (click)="downloadPDFFromOutsideMatTable()"
 #pdfExterno>PDF</button>
<h1>Within the table, I could only bind to an Observable for the 1st row</h1>
<div class="container text-center">
  <table mat-table [dataSource]="dataSource" #mytable class="mat-elevation-z8">


    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <th mat-header-cell *matHeaderCellDef> No. </th>
      <td mat-cell *matCellDef="let element"> {{element.position}} </td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>

    <!-- Weight Column -->
    <ng-container matColumnDef="weight">
      <th mat-header-cell *matHeaderCellDef> Weight </th>
      <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
    </ng-container>
      <!-- Symbol Column -->
<ng-container matColumnDef="symbol">
  <th mat-header-cell *matHeaderCellDef> Symbol </th>
  <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
</ng-container>
<ng-container matColumnDef="downloadPdfDoc">
  <th mat-header-cell *matHeaderCellDef mat-sort-header>Download</th>
  <td mat-cell *matCellDef="let row" class="u-text-align-center" (click)="$event.stopPropagation()">
    <div class="row">
      <div>
        <button id="pdf" #pdf (click)="(pdf.id); downloadPDF(row)">PDF</button>
      </div>
      <div>
        <button id="docx" #docx (click="(docx.id); downloadDocx(row)">DOCX </button>
      </div>
    </div>
  </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" #mostrar (click)="onRowClicked(row);$event.stopPropagation()">
</tr>
  </table>

</div>

Answer №1

It seems like you may be overcomplicating things based on the title of your question. You don't necessarily need to use @ViewChild and Renderer2 for what you are trying to achieve. Instead of creating observables for click events in an Angular Material table, you can simply push events through a Subject to create a stream of Events:

private rowClick$ = new Subject<Event>();

public onRowClick(event: Event) {
  this.rowClick$.next(event);
}

If you prefer, you can push the data itself (in this case, the PeriodicElement):

private rowClick$ = new Subject<PeriodicElement>();

public onRowClick(data: PeriodicElement) {
  this.rowClick$.next(data);
}

If you're looking to create a stream of "render jobs", you could implement something like this:

  private renderJob$ = new Subject<RenderJob>();

  downloadPDF(row: PeriodicElement) {
    this.renderJob$.next({ type: 'pdf', data: row });
  }

  downloadDocx(row: PeriodicElement) {
    this.renderJob$.next({ type: 'docx', data: row });
  }

  ngOnInit() {
    this.renderJob$.subscribe(
      ({ type, data }) => console.log(`[render job received] type: ${type}`, data)
    );
  }

Feel free to check out these Stackblitz links for further exploration:

Answer №2

It may not be necessary, but if you have a button inside a table cell

<td mat-cell *matCellDef="let element">
     <button #bt>{{element.weight}}</button>
</td>

You can use the following code after assigning a value to the dataSource

@ViewChildren('bt') buttons:QueryList<ElementRef>

// Assigning a value to the dataSource
this.dataSource = new MatTableDataSource(ELEMENT_DATA);

// Wrapping in a setTimeout function
setTimeout(()=>{
  this.buttons.forEach(button=>{
    fromEvent(button.nativeElement,'click').subscribe(response=>{
      console.log(response)
    })
  })
})

Note that it is important to wrap the code in a setTimeout function to allow Angular time to render the table properly.

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

Connect to dynamically generated div on separate page

I am facing an issue with my navigation bar drop down menu that displays event titles fetched from the database. I want users to be able to click on a specific event and have the page scroll to the dynamically generated content for that event. However, it ...

Displaying an iframe on an AR marker with the help of CSS3DRenderer and jsartoolkit

I am trying to achieve an interesting effect by overlaying a html iframe on top of an augmented reality marker. However, I seem to be facing some issues with the CSS3DRenderer not producing the same result as the WebGLRenderer and I'm struggling to pi ...

Ways to extract a value from an object with no properties using jQuery

In order to perform statistical calculations like Averages (Mean, Median, Mode) on data extracted from Datatables, I need the automatic calculation to remain accurate even when the table is filtered. While I have managed to obtain the desired values, extra ...

What is the process for downloading the array of images' responses?

Currently, my setup involves Angular 5 on the front end and Spring Boot on the backend. I am facing the challenge of downloading the JSON response of images. Can anyone provide guidance on how to achieve this? ...

Tips for retrieving Angular routing data from external sources outside of an Angular application

Is there a way to automatically generate a sitemap for each module during build time? The project structure is as follows: - cli - client -- Module A -- Routing A -- Module B -- Routing B -- Module C -- Routing C - server I am ...

Is there a way to handle an error or display N/A when an object is not found in the JSON data I am working with?

Today's games lineup and broadcasting channels can be displayed using this JSON format. However, I am encountering the following error: TypeError: /home/ubuntu/workspace/sportsapp/views/results.ejs:8 6| <% data["games"].forEach(function(game){ ...

What could be the reason for the variable's type being undefined in typescript?

After declaring the data type of a variable in TypeScript and checking its type, it may show as undefined if not initialized. For example: var a:number; console.log(a); However, if you initialize the variable with some data, then the type will be display ...

Transfer to the following port within an Express server application

Currently, I have a server running on localhost:3000. However, it is already occupied. How can I redirect to the next available port number when running a separate server? ...

Trigger a series of functions upon clicking with ReactJS

Need some assistance with an alert message functionality. I have a button labeled Checkout, and when clicked, it should clear the cart and display an alert. At present, the individual functions work as expected - calling emptyCart() works fine, and calling ...

Undefined variable when initializing with ng-init

As a newcomer to AngularJS (and JavaScript in general), I'm currently facing an issue that I could use some help with. Below is the HTML template I am using: <ion-view view-title="Playlists"> <ion-content> <ion-list> ...

How to make a GET request to a Node server using Angular

I am currently running a node server on port 8000 app.get('/historical/:days' ,(req,res,next){..}) My question is how to send a request from an Angular app (running on port 4200) in the browser to this node server. Below is my attempt: makeReq ...

Text in SVG file misaligned at the edge

After creating an SVG with a base64 background image and two text areas for top and bottom texts, I encountered an issue on Internet Explorer and Edge. The problem is that the bottom text is aligned to the left instead of the center, and its position is in ...

Component not being returned by function following form submission in React

After spending a few weeks diving into React, I decided to create a react app that presents a form. The goal is for the user to input information and generate a madlib sentence upon submission. However, I am facing an issue where the GenerateMadlib compone ...

The size of the <DIV> element turns out to be larger than originally anticipated

First things first, check out this HTML template: <div id="content"> <header>...</header> <div id="pages"> <div class="page">...</div> <div class="page">...</div> <div clas ...

The onhashchange function is limited in its ability to completely track changes in the hash

I set up an onhashchange event listener on the page in the following way: window.addEventListener('hashchange', () => console.log('hash change!')) This listener can detect hash changes that I manually enter: However, I am not see ...

Error: the specified item is not a valid function

As a newcomer to the world of JavaScript, I am eager to learn and explore new concepts. Currently, my focus is on centralizing the code for accessing my MySQL database within my Express JS server using promises. Below is an attempt I have made in achieving ...

Customizing the appearance of pagination numbers in ReactJS by altering their color

I am currently working on a pagination ReactJS App. Check out the app here: https://codepen.io/claudio-bitar/pen/VERORW One specific feature I would like to implement is changing the color of the current page number in the pagination. For example, if th ...

Develop a feature within a standard plugin that allows users to add, remove, or refresh content easily

I have developed a simple plugin that builds tables: ; (function ($, window, document, undefined) { // Define the plugin name and default options var pluginName = "tableBuilder", defaults = { }; // Plugin constructor func ...

Search for the text using jQuery and then conceal it

I am attempting to conceal specific text within paragraphs, however, the issue is that the script identifies the entire paragraph and removes it completely. My goal is to only remove the text that has been identified. Check out the demo here <div cla ...

What causes the namespace to shift when utilizing npm for installing a library?

I've been attempting to integrate whammy.js into a project. The initial line in the source code is window.Whammy = (function(){ yet, after running npm i and inspecting node_modules, I discovered global.Whammy = (function(){ https://github.com/anti ...