Reputation: 51
I have a mat table with a filter that uses chips to filter multiple criterias.
When I go to filter the first time and save the criteria,I get the error ExpressionChangedAfterItHasBeenCheckedError. It says it went from undefined to a number.
I've seen in multiple treads saying that I need to change the way I initialize the table and data to be loaded to the table. I've added ngAfterContentChecked() with this.ref.detectChanges(); and it gets rid of the error, but not the wrong behavior.
I start with ngOnInit getting the data from a service.
What should I do to get the correct behavior?
See the code attached!
thanks!
DetentionValidation.component.ts
import { AfterViewInit, Component, ViewChild, OnInit } from '@angular/core';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { SelectionModel } from '@angular/cdk/collections';
import { DetentionValidationService } from './detentionValidation.service';
import { Subscription } from 'rxjs';
import { centerInterface } from './centers-data.model';
import { StopsInterface } from './stops-data.model';
import { ChangeDetectorRef } from '@angular/core';
import { parse } from 'dotenv';
//data interfaces
interface operation {
value: string;
viewValue: string;
}
// export interface StopsInterface {
// StopNbr: number;
// Wgt: number;
// }
export interface SearchItem {
name: string;
}
@Component({
selector: 'app-detentionValidation',
templateUrl: './detentionValidation.component.html',
styleUrls: ['./detentionValidation.component.css'],
})
export class DetentionValidationComponent implements AfterViewInit, OnInit {
isLoading = true;
selectedCenter!: string;
selectedOperation!: string;
operations: operation[] = [
{ value: 'Pickup', viewValue: 'Pickup' },
{ value: 'Delivery', viewValue: 'Delivery' },
];
centers: centerInterface[] = [];
stops: StopsInterface[] = [];
private allDataSubs: Subscription;
//which rows are selected
selection = new SelectionModel<StopsInterface>(true, []);
public globalFilter = '';
public searchItems: SearchItem[] = [];
public reset_data = this.stops;
displayedColumns: string[] = [
'WAND_STOP_NBR',
'UNIT_NBR',
'AGGR_SHP_WGT',
'select',
];
dataSource = new MatTableDataSource(this.stops);
stopsDataSource = [];
selectable = true;
removable = true;
readonly separatorKeysCodes: number[] = [COMMA, ENTER];
public searchTerms: string[] = [];
constructor(
private _liveAnnouncer: LiveAnnouncer,
public detentionValidationService: DetentionValidationService,
private ref: ChangeDetectorRef
) {}
@ViewChild(MatSort, { static: false }) set content(sort: MatSort) {
this.dataSource.sort = sort;
}
ngAfterViewInit() {}
async ngOnInit() {
const response = await this.detentionValidationService.getCenters();
//loading Service centers for drop down
this.detentionValidationService
.getCentersUpdateListener()
.subscribe((centers: centerInterface[]) => {
this.centers = centers;
// this.isLoading = false;
});
//loading all data
this.detentionValidationService
.getStopsUpdateListener()
.subscribe((stops: StopsInterface[]) => {
stops.forEach((element) => {
this.stops.push({
WAND_STOP_NBR: element.WAND_STOP_NBR,
UNIT_NBR: element.UNIT_NBR,
AGGR_SHP_WGT: element.AGGR_SHP_WGT,
});
console.log(element.WAND_STOP_NBR);
});
this.stopsDataSource = this.stops;
this.dataSource.data = this.stopsDataSource;
// this.dataSource.filterPredicate = this.customFilterPredicate();
this.isLoading = false;
});
}
ngAfterContentChecked() {
this.dataSource.sort = this.dataSource.sort;
this.dataSource.filterPredicate = this.customFilterPredicate();
this.ref.detectChanges();
}
onStopToggled(stop: StopsInterface) {
this.selection.toggle(stop);
// console.log(this.selection.selected);
}
isAllSelected() {
return (
// console.log(this.dataSource.filteredData),
this.selection.selected?.length == this.dataSource.filteredData.length
);
}
toggleAll() {
if (this.isAllSelected()) {
this.selection.clear();
} else {
this.selection.select(...this.dataSource.filteredData);
}
}
add(event: MatChipInputEvent): void {
const input = event.chipInput.inputElement;
const value = event.value;
// Add new search term
if ((value || '').trim()) {
this.searchItems.push({ name: value.trim() });
}
// Reset the input value
if (input) {
input.value = '';
}
this.searchTerms = this.searchItems.map(function (searchItem) {
return searchItem.name;
});
this.globalFilter = '';
// console.log('search terms', this.searchTerms);
}
remove(item: SearchItem): void {
const index = this.searchItems.indexOf(item);
if (index >= 0) {
this.searchItems.splice(index, 1);
this.searchTerms = this.searchItems.map(function (searchItem) {
return searchItem.name;
});
this.dataSource.data = [...this.reset_data];
this.dataSource.filter = JSON.stringify(this.searchTerms);
}
}
applyFilter(filterValue: string) {
console.log(filterValue);
this.globalFilter = filterValue;
this.dataSource.filter = JSON.stringify(this.searchTerms);
}
customFilterPredicate() {
const myFilterPredicate = (
data: StopsInterface,
filter: string
): boolean => {
var globalMatch = !this.globalFilter;
if (this.globalFilter) {
// search all text fields
globalMatch =
data.WAND_STOP_NBR.toString()
.trim()
.toLowerCase()
.indexOf(this.globalFilter.toLowerCase()) !== -1 ||
data.UNIT_NBR.toString()
.trim()
.toLowerCase()
.indexOf(this.globalFilter.toLowerCase()) !== -1 ||
data.AGGR_SHP_WGT.toString()
.trim()
.toLowerCase()
.indexOf(this.globalFilter.toLowerCase()) !== -1;
}
if (!globalMatch) {
return false;
}
let parsedSearchTerms = JSON.parse(filter);
let isMatchToAllTerms = true;
for (const term of parsedSearchTerms) {
isMatchToAllTerms =
isMatchToAllTerms &&
(data.WAND_STOP_NBR.toString()
.toLowerCase()
.trim()
.indexOf(term.toLowerCase()) !== -1 ||
data.UNIT_NBR.toString()
.toLowerCase()
.trim()
.indexOf(term.toLowerCase()) !== -1 ||
data.AGGR_SHP_WGT.toString()
.toLowerCase()
.trim()
.indexOf(term.toLowerCase()) !== -1);
}
return isMatchToAllTerms;
};
return myFilterPredicate;
}
announceSortChange(sortState: Sort) {
if (sortState.direction) {
this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`);
console.log('sorting');
} else {
this._liveAnnouncer.announce('Sorting cleared');
}
}
}
DetentionValidation.component.html
<body>
<div
style="
position: absolute;
height: 80%;
width: 80%;
align-items: center;
display: flex;
justify-content: space-around;
align-content: space-around;
"
>
<mat-spinner *ngIf="isLoading">
<span class="sr-only">Loading...</span>
</mat-spinner>
</div>
<div *ngIf="!isLoading">
<div>
<form>
<div>
<h2>Detention Processing Validation</h2>
<div style="display: flex">
<div style="padding-right: 2%">
<mat-form-field appearance="fill">
<mat-label>Select Service Center</mat-label>
<mat-select [(ngModel)]="selectedCenter" name="car">
<mat-option
*ngFor="let center of centers"
[value]="center.ORIG_LOC_CD"
>
{{ center.ORIG_LOC_CD }}
</mat-option>
</mat-select>
</mat-form-field>
<p>Selected SC: {{ selectedCenter }}</p>
</div>
<div>
<mat-form-field appearance="fill">
<mat-label>Pickup/Delivery</mat-label>
<mat-select [(ngModel)]="selectedOperation" name="operation">
<mat-option
*ngFor="let operation of operations"
[value]="operation.value"
>
{{ operation.viewValue }}
</mat-option>
</mat-select>
</mat-form-field>
<p>Selected Operation: {{ selectedOperation }}</p>
</div>
<div class="fedex-button-row">
<button mat-raised-button color="primary">Submit</button>
</div>
</div>
</div>
</form>
</div>
<!-- table div -->
<div class="stops-table-div">
<mat-chip-list #chipList>
<mat-chip
*ngFor="let item of searchItems"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(item)"
>
{{ item.name }}
<mat-icon matChipRemove *ngIf="removable"> cancel </mat-icon>
</mat-chip>
</mat-chip-list>
<mat-form-field>
<input
matInput
[ngModel]="globalFilter"
(ngModelChange)="applyFilter($event)"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)"
placeholder="Search (press enter for multiple)"
class="form-control input-md"
appearance="fill"
/>
</mat-form-field>
<table
mat-table
class="stops-table mat-elevation-z8"
[dataSource]="dataSource"
matSort
(matSortChange)="announceSortChange($event)"
>
<ng-container matColumnDef="WAND_STOP_NBR">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
sortActionDescription="Sort by WAND_STOP_NBR"
>
Stop Nbr
</th>
<td mat-cell *matCellDef="let stop">{{ stop.WAND_STOP_NBR }}</td>
</ng-container>
<ng-container matColumnDef="UNIT_NBR">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
sortActionDescription="Sort by UNIT_NBR"
>
Unit Nbr
</th>
<td mat-cell *matCellDef="let stop">{{ stop.UNIT_NBR }}</td>
</ng-container>
<ng-container matColumnDef="AGGR_SHP_WGT">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
sortActionDescription="Sort by AGGR_SHP_WGT"
>
Weight
</th>
<td mat-cell *matCellDef="let stop">{{ stop.AGGR_SHP_WGT }}</td>
</ng-container>
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox
color="primary"
[checked]="selection.hasValue() && isAllSelected()"
(change)="toggleAll()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let stop">
<mat-checkbox
color="primary"
(change)="onStopToggled(stop)"
[checked]="selection.isSelected(stop)"
>
</mat-checkbox>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let stop; columns: displayedColumns"></tr>
</table>
</div>
</div>
</body>
Upvotes: 1
Views: 2782
Reputation: 399
From https://angular.io/errors/NG0100
If the issue exists within ngAfterViewInit, the recommended solution is to use a constructor or ngOnInit to set initial values, or use ngAfterContentInit for other value bindings.
So, you can try to
[...]
export class DetentionValidationComponent implements AfterContentInit {
[...]
ngAfterContentInit () {
//insert content of your ngOnInit
}
In every case, this is just a dev error, in production there is no additional check and no errors throws
Upvotes: 3