SO MatTable with ReactiveForm and *ngFor rendering for columns: Error: Could not find column

SO MatTable with ReactiveForm and *ngFor rendering for columns: Error: Could not find column

我试图使用 *ngFor 指令呈现动态列,但我在 JS 控制台上收到此错误:

FlujoMensualComponent.html:13 ERROR Error: Could not find column with id "julio".
at getTableUnknownColumnError (table.js:890)
at table.js:1973
at Function.from (<anonymous>)
at MatTable._getCellTemplates (table.js:1965)
at MatTable._renderRow (table.js:1920)
at table.js:1779
at Array.forEach (<anonymous>)
at MatTable._forceRenderHeaderRows (table.js:1774)
at MatTable.ngAfterContentChecked (table.js:1251)
at callProviderLifecycles (core.js:32324)

这就是 flujo-mensual.component.html 模板的样子:

<div *ngIf="periodoFiscal" class="grid-container">
  <h1 class="mat-h1">Presupuesto Anual del Ejercicio {{ periodoFiscal.nombrePeriodoFiscal }}</h1>
  <div flex="100" class="generic-card-container material-cards-container">

  </div>
  <div class="example-container mat-elevation-z8">
    <div class="example-loading-shade" *ngIf="isLoading">
      <mat-spinner></mat-spinner>
    </div>
    <div class="example-table-container">
      <form class="form" [formGroup]="form">
        <ng-container formArrayName="proyectos">
          <table *ngIf="resultsLength > 0" mat-table [dataSource]="dataSource" class="example-table" matSort matSortActive="created"
            matSortDisableClear matSortDirection="desc">

            <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
            <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>

            <!-- Proyecto Column -->
            <ng-container matColumnDef="proyecto">
              <th mat-header-cell *matHeaderCellDef>Proyecto</th>
              <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
                <b>{{ row.proyecto.nombreProyecto }}</b>
                <input type="hidden" formControlName="_id" />
                <input type="hidden" formControlName="proyecto" />
                <input type="hidden" formControlName="periodoFiscalCoviar" />
              </td>
            </ng-container>

            <!-- Saldo Column -->
            <ng-container matColumnDef="saldo">
              <th mat-header-cell *matHeaderCellDef>Saldo</th>
              <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
                <b>$ 200000</b>
              </td>
            </ng-container>

            <!-- Total Column -->
            <ng-container matColumnDef="total">
              <th mat-header-cell *matHeaderCellDef>Total</th>
              <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
                <mat-form-field class="form-field">
                  <button class="mat-raised-button mat-primary action-button" (click)="verDato(i)">
                    Dato
                  </button>
                </mat-form-field>
              </td>
            </ng-container>

            <!-- Saldo No Ejecutado Column -->
            <ng-container *ngFor="let mes of NOMBRE_MESES" [matColumnDef]="mes | lowercase">
              <th mat-header-cell *matHeaderCellDef>{{ mes }}</th>
              <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
                <mat-form-field class="form-field">
                  $ <input matInput type="number" placeholder="Saldo No Ejecutado" min="0" max="100"
                           [formControlName]="mes | lowercase" (change)="onChange(i, $event)" />
                </mat-form-field>
              </td>
            </ng-container>

            <!-- Total Column -->
            <ng-container matColumnDef="acciones">
              <th mat-header-cell *matHeaderCellDef></th>
              <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
                <mat-form-field class="form-field">
                  <button class="mat-raised-button mat-primary action-button" (click)="aplicar(i)">
                    Aplicar
                  </button>
                </mat-form-field>
              </td>
            </ng-container>
          </table>
        </ng-container>
      </form>
    </div>

    <mat-paginator [length]="resultsLength" [pageSize]="30"></mat-paginator>
  </div>
</div>

<mat-dialog-actions class="botones-accion">
  <button class="mat-raised-button mat-primary action-button" (click)="save()">Guardar</button>
</mat-dialog-actions>

flujo-mensual.component.ts 的 TS 代码如下:

import { Component, ViewChild } from '@angular/core';
import { MatSnackBar, MatPaginator, MatSort } from '@angular/material';
import { IProyecto } from '../proyecto/IProyecto';
import { ErrorSnackBarComponent } from '../shared/custom-snack-bar/error-snack-bar/error-snack-bar.component';
import { FormGroup, FormBuilder, FormArray } from '@angular/forms';
import { IPeriodoFiscal } from '../periodo-fiscal/i-periodo-fiscal';
import { PeriodoFiscalService } from '../periodo-fiscal/services/periodo-fiscal.service';
import { FlujoMensualService } from './services/flujo-mensual.service';
import { finalize } from 'rxjs/operators';
import { IFlujoMensual } from './IFlujoMensual';
import { NOMBRE_MESES } from '../shared/constantes';


const CALC_CONTROLS: string[] = [
  'contribucionObligatoria',
  'saldoNoEjecutado',
]

const FLUJOS_MENSUALES: any = [
  {
    _id: "5e2ec70476d34500252ba487",
    proyecto: {
      _id: "5dddaa1f4deab23d70873dd1",
      nombreProyecto: "Nuevo Proyecto Fabrico",
    },
    periodoFiscalCoviar: {
      _id: "5dc8c3dc013aea0025f14848",
      nombrePeriodoFiscal: "2019 - 2020",
    },
    julio: 0,
    agosto: 0,
    septiembre: 0,
    octubre: 0,
    Noviembre: 0,
    Diciembre: 0,
    Enero: 0,
    Febrero: 0,
    Marzo: 0,
    Abril: 0,
    Mayo: 0,
    Junio: 0,
  },
  {
    _id: "5e2ec70476d34500252ba488",
    proyecto: {
      _id: "5dddaa1f4deab23d70873dd2",
      nombreProyecto: "Proyecto test",
    },
    periodoFiscalCoviar: {
      _id: "5dc8c3dc013aea0025f14848",
      nombrePeriodoFiscal: "2019 - 2020",
    },
    julio: 0,
    agosto: 0,
    septiembre: 0,
    octubre: 0,
    Noviembre: 0,
    Diciembre: 0,
    Enero: 0,
    Febrero: 0,
    Marzo: 0,
    Abril: 0,
    Mayo: 0,
    Junio: 0,
  },
]

@Component({
  selector: 'app-flujo-mensual',
  templateUrl: './flujo-mensual.component.html',
  styleUrls: ['./flujo-mensual.component.scss']
})
export class FlujoMensualComponent {

  form: FormGroup;
  periodoFiscal: IPeriodoFiscal = null;

  displayedColumns: string[] = ['proyecto', 'saldo', 'total'].concat(NOMBRE_MESES, ['acciones']).map(
    (col: string) => col.toLocaleLowerCase()
  );

  dataSource: Array<IProyecto>;
  isLoading = false;
  resultsLength = 0;

  @ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: false }) sort: MatSort;

  constructor(
    private fb: FormBuilder,
    private _flujoMensualService: FlujoMensualService,
    private _periodoFiscalService: PeriodoFiscalService,
    private _snackBar: MatSnackBar
  ) {
    this.form = this.fb.group({
      proyectos: this.fb.array([])
    });

    this.getProyectos();
  }

  save() {
    this.isLoading = true;

    this._flujoMensualService.upsertFlujosMensuales(this.form.getRawValue().proyectos).pipe(
      finalize(() => this.isLoading = false)
    ).subscribe((result: any) => {
      alert('¡El presupuesto anual ha sido guardado con éxito!')
      console.log('UPSERT PRESUPUESTOS ANUALES:', result);
    }, (error: Error) => {
      this.handleError(error);
    });
  }

  onChange(index: number, event: Event) {
    let sum: number = 0;
    const arrayControl = (this.form.get('proyectos') as FormArray).controls[index];

    CALC_CONTROLS.forEach((controlName: string) => {
      sum += arrayControl.get(controlName).value;
    });

    arrayControl.get('totalPresupuesto').setValue(sum);
  }

  verDato(index: number) {
    alert('INDEX: ' + index);
  }

  aplicar(index: number) {
    alert('INDEX: ' + index);
  }

  private setProyectosForm() {
    const proyectosCtrl = this.form.get('proyectos') as FormArray;
    this.dataSource.forEach((presupuesto: IFlujoMensual) => {
      proyectosCtrl.push(this.setProyectosFormArray(presupuesto))
    })
  };

  private setProyectosFormArray(presupuesto: IFlujoMensual) {
    return this.fb.group({
      _id: [presupuesto._id, []],
      proyecto: [presupuesto.proyecto._id, []],
      julio: [0, []],
      agosto: [0, []],
      septiembre: [0, []],
      octubre: [0, []],
      noviembre: [0, []],
      diciembre: [0, []],
      enero: [0, []],
      febrero: [0, []],
      marzo: [0, []],
      abril: [0, []],
      mayo: [0, []],
      junio: [0, []],
    });
  }

  private getProyectos() {
    this.isLoading = true;

    this._flujoMensualService.getAll().subscribe((presupuestos: Array<IFlujoMensual>) => {
      this.dataSource = FLUJOS_MENSUALES;
      this.resultsLength = FLUJOS_MENSUALES.length;
      console.log(this.dataSource);

      if (this.dataSource.length > 0) {
        this._periodoFiscalService.getPeriodoFiscalActual().subscribe(
          (response: any) => {
            this.periodoFiscal = response.periodoFiscalCoviar;
            this.isLoading = false;
            this.setProyectosForm();
          }, (error: Error) => {
            this.handleError(error);
          });
      }
    }, (error: Error) => {
      this.handleError(error);
    });
  }

  private handleError(error: Error) {
    this.isLoading = false;
    console.log(error);

    this._snackBar.openFromComponent(ErrorSnackBarComponent, {
      duration: 5000,
      data: error
    });

    this.getProyectos();
  }
}

这是NOMBRE_MESES的定义:

export const NOMBRE_MESES: string[] = [
  'Julio',
  'Agosto',
  'Septiembre',
  'Octubre',
  'Noviembre',
  'Diciembre',
  'Enero',
  'Febrero',
  'Marzo',
  'Abril',
  'Mayo',
  'Junio',
];

我不确定这里出了什么问题,因为列 headers 的尺寸与列内容相同,所以我可以找到错误。

你能帮我解决这个问题吗?谢谢!

您应该将 NOMBRE_MESES 分配给组件 TS 变量:

nombreMeses = NOMBRE_MESES.map(v => v.toLowerCase());

那你*ng这个变量;

...
<ng-container *ngFor="let mes of nombreMeses" [matColumnDef]="mes">
...

别忘了更改您的 FormControl:

$ <input matInput type="number" placeholder="Saldo No Ejecutado" min="0" 
    max="100" [formControlName]="mes" (change)="onChange(i, $event)" />

试试吧。

错误发生在 NOMBRE_MESES。我忘记将该数组常量分配给 public 属性。 *ngFor 没有渲染任何东西,因为要迭代的数组是空的!

最终代码:

flujo-mensual.component.ts:

meses: string[] = NOMBRE_MESES;

flujo-mensual.component.html:

<!-- Meses Columns -->
<ng-container *ngFor="let mes of meses" [matColumnDef]="mes | lowercase">
  <th mat-header-cell *matHeaderCellDef>{{ mes }}</th>
  <td mat-cell *matCellDef="let row; let i=index" [formGroupName]="i">
    <mat-form-field class="form-field">
      $ <input matInput type="number" placeholder="Saldo No Ejecutado" min="0" max="100"
                [formControlName]="mes | lowercase" (change)="onChange(i, $event)" />
    </mat-form-field>
  </td>
</ng-container>