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>
我试图使用 *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>