Piyush Jain
Piyush Jain

Reputation: 1986

Angular How to pass data from Directive to Parent component

My requirement is something like, I have a reusable table which I wanna use on multiple places. I am passing table data and columns wherever I am using this table.

Now in some parent components I wanna get data when user click on table row or double click on table row.

For that I have create a directive using @HostListener.

When I am using directive on table row I can get clicked row data in directive but when I am emitting this data I am not getting it in parent.

table.component.html

<section class="example-section">

  <div class="cdx-table-wrapper cdx-table-wrapper-fixed">
    <table mat-table [dataSource]="tableData" class="mat-elevation-z8">
      <!--- Note that these columns can be defined in any order.
            The actual rendered columns are set as a property on the row definition" -->

      <ng-container *ngFor="let disCol of tableColumns; let colIndex = index" matColumnDef="{{disCol}}">
        <th mat-header-cell *matHeaderCellDef>{{disCol}}</th>
        <td mat-cell *matCellDef="let element">{{element[disCol]}}</td>
      </ng-container>

      <tr class="persues-table-row" mat-header-row *matHeaderRowDef="tableColumns"></tr>
      <tr class="persues-table-row" mat-row *matRowDef="let row; columns: tableColumns;" appTableEvent [rowData]="row"></tr>
    </table>
  </div>
</section>

table-events.directive.ts

import { Directive, HostListener, Input, Output, EventEmitter } from '@angular/core';

@Directive({
  selector: '[appTableEvent]'
})
export class TableEventDirective {
  @Input() rowData;

  @Output()
  clickHandler: EventEmitter<any> = new EventEmitter();

  constructor() { }

  @HostListener('click', ['$event'])
  onClickHandler(event: MouseEvent) {
    this.clickHandler.emit(this);
  }
}

parent component

<app-table
  [tableData]="dataSourceBase"
  [tableColumns]="displayedColumnsBase"
  (clickHandler)="onRowClick($event)">
</app-table>

parent component ts

onRowClick(event) {
  console.log('onRowClick', event)
}

I am not getting data in onRowClick() method.

Now there are some cases when I just wanna use table to just show data, In that case I don't wanna apply directive.

Thanks in advance

Upvotes: 2

Views: 3242

Answers (2)

Gaurang Dhorda
Gaurang Dhorda

Reputation: 3387

Working Demo in this StackBlitz Link

Here, you can use service and rxjs - Behaviorsubject. For this you have to create one service and then in this service define your Behaviorsubject and all common communicate related stuff. You have to inject this one service in component and in directive to. so that, dont have to @Output() multiple level and one benifit of this you can communicate with any parent level of component regardless of chaining of events.

table.component.html

<table class="table table-dark table-striped table-hover">
   <thead>
     <tr>
       <th scope="col">ID</th>
       <th scope="col">Company Name</th>
       <th scope="col">Brand</th>
       <th scope="col">Engine Type</th>
     </tr>
   </thead>
   <tbody>
      <tr *ngFor="let row of tableService.tableData; let i = index" appTableRow [rowData]="row">
        <th scope="row">{{row.id}}</th>
        <td>{{row.companyName}}</td>
        <td>{{row.brand}}</td>
        <td>{{row.engineType}}</td>
      </tr>
   </tbody>
</table>

<div *ngIf="selectedRow | async as rowSelect" class="alert alert-primary">
  <pre>
    {{rowSelect | json}}
  </pre>
</div>

table.component.ts

export class TableComponent implements OnInit {
  selectedRow;
  constructor(private tableService: TableDataService) { }

  ngOnInit() {
    this.selectedRow = this.tableService.getTableRow();
  }
}

directive.ts

@Directive({
  selector: '[appTableRow]'
}) 
export class TableRowDirective {
   @Input() rowData;

   constructor(private tableRowSerivce: TableDataService) { }

   @HostListener('click', ['$event'])
      onClickHandler(event: MouseEvent) {
         this.tableRowSerivce.tableRowData.next(this.rowData);
      }
}

table-service.ts

@Injectable()  
export class TableDataService {

  tableData = [{
   id: 1,
   companyName: 'Audi',
   brand: 'A4',
   engineType: 'Diesel'
  },{
   id: 2,
   companyName: 'Rolls-Roys',
   brand: 'R4',
   engineType: 'Diesel'
  },{
   id: 3,
   companyName: 'Hyundai',
   brand: 'Kona',
   engineType: 'Electric'
  },{
   id: 4,
   companyName: 'Ford',
   brand: 'Endevour',
   engineType: 'Diesel'
 }];

  tableRowData : BehaviorSubject<any> = new BehaviorSubject<any>({});
  tableRowData$ = this.tableRowData.asObservable();

  constructor() { }

  getTableRow(): Observable<any>{
    return this.tableRowData$;
  }
}

Upvotes: 1

Poul Kruijt
Poul Kruijt

Reputation: 71961

You need to create an HostListener on your table component as well:

@Component({
  selector: 'app-table'
})
export class TableComponent {
  @Output()
  clickHandler: EventEmitter<MouseEvent> = new EventEmitter();
}

and have this emit from your appTableEvent directive:

<tr class="persues-table-row" mat-row
    *matRowDef="let row; columns: tableColumns;" [rowData]="row"
    appTableEvent (clickHandler)="clickHandler.emit($event)">
</tr>

If you plan on having multiple of these cases, you can also go for the following solution. You would still need the clickHandler on your TableComponent like I showed before, but you can update your directive like this:

@Directive({
  selector: '[appTableEvent]'
})
export class TableEventDirective {
  @Input() rowData;

  constructor(@Host() private table: TableComponent) { }

  @HostListener('click', ['$event'])
  onClickHandler(event: MouseEvent) {
    this.table.clickHandler.emit(this);
  }
}

This way you don't have to add the click handler to the <tr> element

Upvotes: 1

Related Questions