nestdan
nestdan

Reputation: 81

How to create observables of click () events for every row in an Angular Material table and respond to all of them?

I was able to do it with an HTML button also with an Angular Material button But in an Angular Material table I only manage to do it in the first row First use fromEvent creating observables that emit event clicks when not getting the desired result then try it with Renderer2 It is not possible for me to find the way to be able to subscribe to each clik () event that is emitted in the table either using merge with @ViewChildren () I could not do it either inside a table of angular material DOM events in Anbular 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{
  buttonsClik!:() => 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','descargaPdfDoc'];
 dataSource = ELEMENT_DATA;
//another way I try with Renderer2 I try to catch not only the click () events of //the first row
 constructor(private renderer: Renderer2) { }
 ngOnDestroy(): void {
 this.buttonsClik();
 this.documentClick();
 }

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

 ngAfterViewInit() {
   this.pdfExterno$.subscribe()
   this.render();
   console.log(this.pdf);
   console.log(this.docx);
   console.log(this.mostrar);
    /* I can not with

  
   <div>
        <button  id="pdf"   #pdf (click)="(pdf.id) ;descargarPDF(row)" >PDF</button>
      </div>
      <div>
        <button   id="docx" #docx (click)="(docx.id);descargarDocx(row)" >DOCX </button>
      </div>
    </div>
*/
   merge(
fromEvent(this.pdf.nativeElement, 'click'),
fromEvent(this.docx.nativeElement, 'click'),

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

  /* I can not with
   docx$!: Observable<any>;
   this.docx$=fromEvent(this.docx.nativeElement, 'click')
.pipe(

  map(()=>new Date().toString()),
  tap(console.log)
  )
  this.docx$.subscribe(e=>console.log('docx',e))
  */
 }
 render(){
  this.documentClick = this.renderer.
  listen('document', 'click', (event:MouseEvent) =>{

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

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

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


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

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

  }

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

  descargarPDFFueraDeMatTable(){
    console.log('funciona')
  }

}
// html
<h1>Fuera de la Tabla sin problemas</h1>
<section>
  <div>
    <button #testBtn>Click me</button>
  </div>
  <div>

</div>
</section>
<button mat-raised-button
 (click)="descargarPDFFueraDeMatTable()"
 #pdfExterno>PDF</button>
<h1>Dentro de la tabla solo puede vincular a un Observable para la 1 fila</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="descargaPdfDoc">
  <th mat-header-cell *matHeaderCellDef mat-sort-header>Descarga</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) ;descargarPDF(row)" >PDF</button>
      </div>
      <div>
        <button   id="docx" #docx (click)="(docx.id);descargarDocx(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>

Upvotes: 0

Views: 1741

Answers (2)

BizzyBob
BizzyBob

Reputation: 14740

I'm not entirely sure of your intent, but it seems you are doing something much more complicated than what the title of your question suggests. Use of @ViewChild and Renderer2 are not necessary to:

"create observables of click events for every row in an Angular Material table and respond to all of them"

To create a stream of Events on click, you can simply push events through a Subject:

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

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

Instead of pushing the Event, you could push the PeriodicElement data if you wanted to:

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

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

It sort of looks like you are trying to create a stream of "render jobs". If that's the case, you could do something like this:

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

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

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

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

Here are a couple Stackblitz links you can play with:

Upvotes: 2

Eliseo
Eliseo

Reputation: 57919

It's unnecesary, but if you has,e.g. a button in a td

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

You can use, after give value to the dataSource

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

//After give value to dataSource
this.dataSource = new MatTableDataSource(ELEMENT_DATA);

//enclosed in a setTimeout
setTimeout(()=>{
  this.bts.forEach(x=>{
    fromEvent(x.nativeElement,'click').subscribe(res=>{
      console.log(res)
    })
  })
})

See that you need "enclosed" in a setTimeout to give time to Angular to draw the table

Upvotes: 1

Related Questions