Reputation: 61
I'm experiencing a slight issue with Angular and its change detection. I've got a very simple form that allows for additional input containers to be added. Yet, every time I click the add button I get an ExpressionChangedAfterItHasBeenCheckedError
thrown in the console.
When using a standard ngFor the error is thrown in the console, but the new input is still displayed.
However, when using Angular's new @for
option I get the error thrown in the console, but it also isn't displayed.
In both scenarios, I made sure to call a detectChanges
(I also tried markForCheck
), but it made no difference.
public properties: Map<number, string> = new Map<number, string>();
public addProperty() {
const id: number = this.properties.size ?
Array.from(this.properties.keys()).reduce((a, b) => a > b ? a : b) + 1 : 1;
this.properties.set(id, 'placeholder');
this.changeDetectorRef.detectChanges();
}
<button class="btn btn-primary" (click)="addProperty()">+</button>
<div class="d-flex flex-column">
<ng-container *ngFor="let attribute of properties.entries()">
<span>{{ attribute[0] }}</span>
</ng-container>
</div>
I'd greatly appreciate any insight into the issue, thanks in advance.
I've tried using both a ngFor
and Angular's new @for
option, the only difference between the two is that when using the @for
the new data isn't displayed in addition to the console error.
I've also attempted calling the change detector manually but it had no impact.
Upvotes: 3
Views: 180
Reputation: 58011
Angular ngFor
is designed for arrays in particular, so I guess change detection is getting confused due to map store by reference. You can convert it into an array before inputing to the ngFor
which also gets rid of the change detection issue. Please find below a working example!
You can so access the properties.keys()
or properties.values()
if you specifically need just the key or the value!
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<button class="btn btn-primary" (click)="addProperty()">+</button>
<div class="d-flex flex-column">
{{properties.entries() | json}}
<ng-container *ngFor="let attribute of mapEntriesToArray;trackBy: trackByFn ">
<span>{{ attribute[0] }}</span>
</ng-container>
</div>
`,
})
export class App {
public properties: Map<number, string> = new Map<number, string>();
constructor(private changeDetectorRef: ChangeDetectorRef) {}
get mapEntriesToArray() {
return Array.from(this.properties.entries());
}
public addProperty() {
const id: number = this.properties.size
? Array.from(this.properties.keys()).reduce((a, b) => (a > b ? a : b)) + 1
: 1;
this.properties.set(id, 'placeholder');
}
trackByFn(index: number, item: any) {
return index;
}
}
bootstrapApplication(App);
Upvotes: 1