Search code examples
angularangular-materialangular-material-table

Pass parameters to a child component used inside a mat-table row template


I have a Material table with expanded detail rows, inspired by that example (4 columns data + 1 action column for expanding rows).

Inside the detail rows template, there is a chart defined in another component. That component needs an object as input parameter which is the result of a function that use the detail row template matCellDef object as parameter.

The problem is the chart component received undefined as input data.

main.component.html with the table

<table mat-table
         [dataSource]="accounts" multiTemplateDataRows
         class="mat-elevation-z8">

    <ng-container matColumnDef="crypto">
      <th mat-header-cell *matHeaderCellDef>Crypto</th>
      <td mat-cell *matCellDef="let account">
        <div>
          <div>{{ account.code }}</div>
          <div class="crypto-label">{{ account.libel }}</div>
        </div>
      </td>
    </ng-container>

    <ng-container matColumnDef="price">
      <th mat-header-cell *matHeaderCellDef>Price</th>
      <td mat-cell *matCellDef="let account">
        {{ account.price | currency:'EUR':'symbol':'1.2-2' }}
      </td>
    </ng-container>

    <ng-container matColumnDef="holdings">
      <th mat-header-cell *matHeaderCellDef>Holdings</th>
      <td mat-cell *matCellDef="let account">
        <div>
          <div>{{ account.balance | cryptoBalanceFormat }}</div>
          <div class="amount-price">12 345,99 € </div>
        </div>
      </td>
    </ng-container>

    <ng-container matColumnDef="last24h">
      <th mat-header-cell *matHeaderCellDef>24H</th>
      <td mat-cell *matCellDef="let account">
        <div>
          <div class="trend-icon"><mat-icon>trending_up</mat-icon></div>
          <div class="crypto-label">3,23%</div>
        </div>
      </td>
    </ng-container>

    <ng-container matColumnDef="expand">
      <th mat-header-cell *matHeaderCellDef aria-label="row actions">&nbsp;</th>
      <td mat-cell *matCellDef="let element">
        <button mat-icon-button aria-label="expand row" (click)="(expandedElement = expandedElement === element ? null : element); $event.stopPropagation()">
          <mat-icon *ngIf="expandedElement !== element">keyboard_arrow_down</mat-icon>
          <mat-icon *ngIf="expandedElement === element">keyboard_arrow_up</mat-icon>
        </button>
      </td>
    </ng-container>

    <!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
    <ng-container matColumnDef="expandedDetail">
      <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumnsWithExpand.length">
        <div class="example-element-detail"
             [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">

          <app-account-chart [datas]="getChartOptions(element)"></app-account-chart>

        </div>
      </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumnsWithExpand"></tr>
    <tr mat-row *matRowDef="let element; columns: displayedColumnsWithExpand; "
        class="example-element-row"
        [class.example-expanded-row]="expandedElement === element"
        (click)="expandedElement = expandedElement === element ? null : element">
    </tr>
    <tr mat-row *matRowDef="let element; columns: ['expandedDetail']" class="detail-row"></tr>
  </table>

main.component.ts

export class CryptoComponent implements OnInit{
  accounts!: CryptoAccount[];
  displayedColumns = ['crypto', 'price', 'holdings', "last24h"];
  displayedColumnsWithExpand = [...this.displayedColumns, 'expand'];
  displaySpinner = false;
  expandedElement: any;

  constructor(
    private readonly cryptoService: CryptoService
    ) {}

  ngOnInit(): void {
    this.getAccounts();
  }

  getAccounts(askRefresh = false) {
    /* get datas */
  }

  getChartOptions(account: CryptoAccount): ChartData {
    return {
      legends: ["Mars", "Avril" ,"Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre", "Janvier", "Février"],
      values: [140, 232, 101, 264, 90, 340, 250, 238, 111, 222, 342, 123]
    };
  }
}

chart.component.ts

export class AccountChartComponent {

  @Input() datas!: any;
  chartOption!: EChartsOption;

  constructor() {
    this.initGrid();
  }

  initGrid(): void {

    console.log(this.datas);

    this.chartOption = {
      tooltip: {
        trigger: "axis",
        axisPointer: {
          type: "cross",
          label: {
            backgroundColor: "#6a7985"
          }
        }
      },
      grid: {
        left: "3%",
        right: "4%",
        bottom: "3%",
        containLabel: true
      },
      xAxis: [
        {
          type: "category",
          boundaryGap: false,
          axisLabel: {
            color: 'white',
          },
          data: ["Mars", "Avril" ,"Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre", "Janvier", "Février"]
        }
      ],
      yAxis: [
        {
          type: "value",
          axisLabel: {
            color: 'white',
          },
        }
      ],
      series: [
        {
          name: "Nom_Crypto",
          type: "line",
          stack: "Total",
          smooth: true,
          lineStyle: {
            width: 0
          },
          showSymbol: false,
          areaStyle: {
            opacity: 0.8,
            color: new graphic.LinearGradient(0, 0, 0, 1, [
              {
                offset: 0,
                color: 'rgb(55, 162, 255)'
              },
              {
                offset: 1,
                color: 'rgb(116, 21, 219)'
              }
            ])
          },
          emphasis: {
            focus: "series"
          },
          data: [140, 232, 101, 264, 90, 340, 250, 238, 111, 222, 342, 123]
        }
      ]
    };
  }
}

The value of datas is undefined

I think I've read that we can't pass parameters to child components from ng-container because it's not in the dom (or something like that ...)

So my question is how can i do that 🙃 ?


Solution

  • You call this.initGrid() in the constructor. Move it to OnInit(or better to OnChange), and it'll help you

    Simple demo: https://stackblitz.com/edit/angular-u6ag8d?file=src/main.component.html