ExpressionChangedAfterithasbeencheckedError uitgelegd

Leg me uit waarom ik deze foutmelding blijf: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Natuurlijk haal ik het alleen in de Dev-modus, het gebeurt niet op mijn productie-build, maar het is erg vervelend en ik begrijp gewoon niet de voordelen van een foutmelding in mijn dev-omgeving die niet zal verschijnen op prik – begrijpelijk vanwege mijn gebrek aan begrip.

Meestal is de oplossing eenvoudig genoeg, ik wikkel gewoon de fout die code in een settime-out als volgt wikkelt:

setTimeout(()=> {
    this.isLoading = true;
}, 0);

of kracht detecteren wijzigingen met een constructor als volgt: constructor(private cd: ChangeDetectorRef) {}:

this.isLoading = true;
this.cd.detectChanges();

Maar waarom sta ik deze fout voort? Ik wil het begrijpen, zodat ik deze hacky-oplossingen in de toekomst kan vermijden.


1, Autoriteit 100%

Ik had een soortgelijk probleem. Kijkend naar de Lifecycle hooks documentatie , ik heb ngAfterViewInitnaar ngAfterContentIniten het werkte.


2, Autoriteit 80%

Deze fout geeft een echt probleem aan in uw toepassing, daarom is het logisch om een ​​uitzondering te gooien.

In devModeWijzig detectie Voegt een extra draai toe na elke reguliere wijzigingsdetectie-run om te controleren of het model is gewijzigd.

Als het model is gewijzigd tussen de reguliere en de aanvullende wijze-detectie-draai, geeft dit aan dat

  • Wijzigingsdetectie zelf heeft een verandering veroorzaakt
  • Een methode of getter retourneert elke keer dat het
  • wordt genoemd.

die beide slecht zijn, omdat het niet duidelijk is om door te gaan omdat het model misschien nooit kan stabiliseren.

Als het hoektoewijzingen de detectie wijzigen totdat het model stabiliseert, kan het voor altijd worden uitgevoerd.
Als hoekig geen wijzigingsdetectie uitvoert, wordt de weergave mogelijk niet de huidige status van het model weergegeven.

Zie ook Wat is verschil tussen productie- en ontwikkelingsmodus in Angular2?


3, Autoriteit 71%

Veel begrip kwamen eenmaal eenmaal begreep ik de hoekige levenscyclus haken en hun relatie met verandering detectie .

Ik probeerde hoekig te worden om een ​​wereldwijde vlag bij te werken die is gebonden aan de *ngIfvan een element, en ik probeerde die vlag in de ngOnInit()Levenscyclushaak van een andere component.

Volgens de documentatie wordt deze methode genoemd nadat Angular al wordt gedetecteerd:

eenmaal, na de eerste NGonchanges ().

Dus bijwerken van de vlag in de binnenkant van ngOnChanges()start geen wijzigingsdetectie. Nadat de detectie is, zodra de detectie van nature is geactiveerd, is de waarde van de vlag gewijzigd en wordt de fout gegooid.

In mijn geval heb ik dit gewijzigd:

constructor(private globalEventsService: GlobalEventsService) {
}
ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

Hieraan:

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}
ngOnInit() {
}

en het probleem is opgelost 🙂


Antwoord 4, autoriteit 47%

Angular voert wijzigingsdetectie uit en wanneer wordt vastgesteld dat sommige waarden die aan de onderliggende component zijn doorgegeven, zijn gewijzigd, geeft Angular de volgende fout:

ExpressionChangedAfterItHasBeenCheckedErrorklik voor meer

Om dit te corrigeren kunnen we de AfterContentCheckedlife cycle hook gebruiken en

import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';
  constructor(
  private cdref: ChangeDetectorRef) { }
  ngAfterContentChecked() {
    this.cdref.detectChanges();
  }

Antwoord 5, autoriteit 40%

Ik gebruik ng2-carouselamos(Angular 8 en Bootstrap 4)

Door deze stappen te volgen is mijn probleem opgelost:

  1. Implementeer AfterViewChecked
  2. Voeg constructor(private changeDetector : ChangeDetectorRef ) {}
  3. Vervolgens ngAfterViewChecked(){ this.changeDetector.detectChanges(); }

Antwoord 6, autoriteit 26%

Bijwerken

Ik raad ten zeerste aan om eerst te beginnen met de zelfreactie van het OP: denk goed na over wat er gedaan kan worden in de constructorversus wat moet worden gedaan in ngOnChanges().

Origineel

Dit is meer een kanttekening dan een antwoord, maar het kan iemand helpen. Ik stuitte op dit probleem toen ik probeerde de aanwezigheid van een knop af te laten hangen van de staat van het formulier:

<button *ngIf="form.pristine">Yo</button>

Voor zover ik weet, leidt deze syntaxis ertoe dat de knop wordt toegevoegd aan en verwijderd uit het DOM op basis van de voorwaarde. Wat op zijn beurt leidt tot de ExpressionChangedAfterItHasBeenCheckedError.

De oplossing in mijn geval (hoewel ik niet beweer dat ik de volledige implicaties van het verschil begrijp), was om in plaats daarvan display: nonete gebruiken:

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>

Antwoord 7, autoriteit 24%

Er waren interessante antwoorden, maar ik leek er geen te vinden die aan mijn behoeften voldeed, de dichtstbijzijnde is van @chittrang-mishra die alleen naar één specifieke functie verwijst en niet naar verschillende schakelaars zoals in mijn app.

Ik wilde [hidden]niet gebruiken om te profiteren van *ngIfdat niet eens deel uitmaakt van de DOM, dus ik vond de volgende oplossing die misschien niet het beste voor iedereen omdat het de fout onderdrukt in plaats van deze te corrigeren, maar in mijn geval waarin ik weet dat het eindresultaat correct is, lijkt het goed voor mijn app.

Wat ik deed was AfterViewCheckedimplementeren, constructor(private changeDetector : ChangeDetectorRef ) {}toevoegen en vervolgens

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}

Ik hoop dat dit anderen helpt, zoals vele anderen mij hebben geholpen.


Antwoord 8, autoriteit 20%

Volg de onderstaande stappen:

1.
Gebruik ‘ChangeDetectorRef’ door het als volgt uit @angular/core te importeren:

import{ ChangeDetectorRef } from '@angular/core';

2.
Implementeer het als volgt in constructor():

constructor(   private cdRef : ChangeDetectorRef  ) {}

3.
Voeg de volgende methode toe aan uw functie die u aanroept bij een gebeurtenis zoals een klik op de knop. Het ziet er dus zo uit:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}

9, Autoriteit 14%

Ik kreeg met hetzelfde probleem aangezien de waarde in een van de array in mijn component veranderde. Maar in plaats van de veranderingen op de verandering van de waarde te detecteren, heb ik de Detectie-strategie van de Component Change gewijzigd naar onPush(die wijzigingen op objectverandering en niet op waardewijziging zullen detecteren).

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})

10, Autoriteit 12%

Verwijzend naar het artikel https: //blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckedError-error-e3fd9ce7dbb4

Dus de mechanica achter wijzigingsdetectie werkt zo dat zowel wijzigingsdetectie als verificatiesamenvattingen synchroon worden uitgevoerd. Dat betekent dat als we eigenschappen asynchroon bijwerken, de waarden niet worden bijgewerkt wanneer de verificatielus wordt uitgevoerd en we geen ExpressionChanged...-fout krijgen. De reden dat we deze fout krijgen, is dat Angular tijdens het verificatieproces andere waarden ziet dan wat het heeft geregistreerd tijdens de wijzigingsdetectiefase. Dus om dat te voorkomen….

1) Gebruik changeDetectorRef

2) gebruik setTimeOut. Hiermee wordt uw code in een andere VM uitgevoerd als een macrotaak. Angular ziet deze wijzigingen niet tijdens het verificatieproces en u krijgt die foutmelding niet.

setTimeout(() => {
        this.isLoading = true;
    });

3) Als u uw code echt op dezelfde VM wilt uitvoeren, gebruikt u zoals

Promise.resolve(null).then(() => this.isLoading = true);

Hiermee wordt een microtaak gemaakt. De wachtrij voor microtaken wordt verwerkt nadat de huidige synchrone code is uitgevoerd, dus de update van de eigenschap vindt plaats na de verificatiestap.


Antwoord 11, autoriteit 4%

@HostBindingkan een verwarrende bron van deze fout zijn.

Stel bijvoorbeeld dat u de volgende hostbinding in een component hebt

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;

Laten we voor de eenvoud zeggen dat deze eigenschap wordt bijgewerkt via de volgende invoereigenschap:

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}

In de bovenliggende component stelt u deze programmatisch in ngAfterViewInit

@ViewChild(ImageCarousel) carousel: ImageCarousel;
ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}

Dit is wat er gebeurt:

  • Uw ouder component wordt gemaakt
  • De ImageCarousel component wordt gecreëerd, en toegewezen aan carousel(via ViewChild)
  • We kunnen geen toegang carouseltotdat ngAfterViewInit()(het zal zijn null)
  • We kennen de configuratie, die sets style_groupBG = 'red'
  • Dit op zijn beurt sets background: redop de host ImageCarousel component
  • Dit onderdeel wordt ‘eigendom’ van je ouders component, dus als het controleert veranderingen die het vindt een verandering op carousel.style.backgrounden is niet slim genoeg om te weten dat dit niet een probleem dus het gooit de uitzondering.

Een oplossing is om een ​​andere wrapper div insider ImageCarousel introduceren en zet de achtergrondkleur op dat, maar dan heb je geen enkele van de voordelen te krijgen van het gebruik van HostBinding(zoals het toestaan ​​van de ouder om de controle de volledige grenzen van het object).

De betere oplossing in de bovenliggende component te detectChanges () nadat de configuratie toe te voegen.

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}

Dit kan heel duidelijk set uit te kijken als dit, en heel vergelijkbaar met andere antwoorden, maar er is een subtiel verschil.

Neem het geval waar je niet @HostBindingpas later hoeft toe te voegen tijdens de ontwikkeling. Plotseling deze fout krijg je en het lijkt niet logisch.


12, Autoriteit 3%

Geprobeerd de meeste van de voorgestelde oplossingen hiervoor. Alleen dit werkte voor mij in dit scenario. Ik was met behulp * ngIf tot onbepaalde progressieve bar hoekige materiaal op basis van api gesprekken te schakelen en het werd gooien ExpressionChangedAfterItHasBeenCheckedError.

In het betreffende component:

constructor(
    private ngZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
) {}
ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
        this.appService.appLoader$.subscribe(value => {
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        });
    });
}

De truc is om de wijzigingsdetectie van het hoekcomponent te omzeilen met behulp van NGZONE.

PS: niet zeker of dit een elegante oplossing is, maar het gebruik van AfterGontentChecked en AftviewCyncheerde Lifecycle Hook is verplicht Performance-problemen op te heffen, omdat uw applicatie groter wordt omdat deze talloze keren wordt geactiveerd.


13, Autoriteit 2%

Hier is mijn gedachten over wat er gebeurt. Ik heb de documentatie niet gelezen, maar ben er zeker van dat dit deel uitmaakt van waarom de fout wordt getoond.

*ngIf="isProcessing()" 

Bij gebruik van * NGIF verandert het fysiek de DOM door het element toe te voegen of te verwijderen telkens wanneer de toestand verandert. Dus als de toestand verandert voordat het wordt weergegeven aan de weergave (die zeer mogelijk is in de wereld van Angular), wordt de fout gegooid. Zie uitleg Hier tussen ontwikkelings- en productiemodi.

[hidden]="isProcessing()"

Bij gebruik van [hidden]verandert het niet fysiek de DOM, maar verbergt u alleen de elementvan de weergave, hoogstwaarschijnlijk met CSSaan de achterkant. Het element is er nog steeds in de DOM, maar niet zichtbaar, afhankelijk van de waarde van de voorwaarde. Dat is de reden waarom de fout niet zal plaatsvinden bij gebruik van [hidden].


14

Debugging Tips

Deze fout kan behoorlijk verwarrend zijn en het is gemakkelijk om een ​​verkeerde aanname te maken over precies wanneer het optreedt. Ik vind het nuttig om veel debugging-verklaringen als deze in de getroffen componenten op de juiste plaatsen toe te voegen. Dit helpt de stroom te begrijpen.

In de bovenliggende uitspraken als deze (de exacte reeks ‘uitdrukkingsfanged’ is belangrijk), maar behalve dat dit slechts voorbeelden zijn:

   console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');

In het kind / diensten / timer callbacks:

   console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');

Als u detectChangesHandmatig inloggen toevoegen voor dat ook voor:

   console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();

Dan in chromen debugger, filter gewoon door ‘expressionchanges’. Dit toont u precies de stroom en volgorde van alles dat is ingesteld, en ook precies op welk punt hoekig gooit de fout.

U kunt ook op de grijze koppelingen klikken om breakpoints in te zetten.

Een ander ding om op te letten Als u op soortgelijke eigenschappen in uw toepassing hebt genoemd (zoals style.background) Zorg ervoor dat u degene die u denkt te debuggen – door deze te instellen op een obscure kleur waarde.


15

Een oplossing die voor mij werkte met rxjs

import { startWith, tap, delay } from 'rxjs/operators';
// Data field used to populate on the html
dataSource: any;
....
ngAfterViewInit() {
  this.yourAsyncData.
      .pipe(
          startWith(null),
          delay(0),
          tap((res) => this.dataSource = res)
      ).subscribe();
}

Antwoord 16

In mijn geval had ik een asynchrone eigenschap in LoadingServicemet een BehaviouralSubject isLoading

Het [hidden]-model gebruiken werkt, maar *ngIfmislukt

   <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>
    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>

Antwoord 17

Ik had dit soort fouten in Ionic3 (die Angular 4 gebruikt als onderdeel van zijn technologiestack).

Voor mij deed het dit:

<ion-icon [name]="getFavIconName()"></ion-icon>

Dus ik probeerde het type van een ion-iconvoorwaardelijk te veranderen van een pinaan een remove-circle, per modus waarop een scherm actief was.

Ik vermoed dat ik in plaats daarvan een *ngIfmoet toevoegen.


Antwoord 18

Mijn probleem was duidelijk toen ik *ngIftoevoegde, maar dat was niet de oorzaak. De fout werd veroorzaakt door het model in de {{}}-tags te wijzigen en later te proberen het gewijzigde model weer te geven in de *ngIf-instructie. Hier is een voorbeeld:

<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>

Om het probleem op te lossen, heb ik de plaats waar ik changeMyModelValue()heb aangeroepen, gewijzigd in een plaats die logischer was.

In mijn situatie wilde ik dat changeMyModelValue()werd aangeroepen wanneer een onderliggende component de gegevens veranderde. Hiervoor moest ik een gebeurtenis maken en uitzenden in de onderliggende component zodat de ouder deze kon verwerken (door changeMyModelValue()aan te roepen. zie https://angular.io/guide/component-interaction#parent-listens-for-child-event


Antwoord 19

Voor mijn probleem las ik github– “ExpressionChangedAfterItHasBeenCheckedError bij het wijzigen van een component waarde ‘niet-model’ in afterViewInit” en besloot het ngModel toe te voegen

<input type="hidden" ngModel #clientName />

Mijn probleem is opgelost, ik hoop dat iemand er iets aan heeft.


Antwoord 20

Ik hoop dat dit iemand helpt die hier komt:
We doen serviceaanroepen in ngOnInitop de volgende manier en gebruiken een variabele displayMainom de montage van de elementen op de DOM te regelen.

component.ts

 displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }

en component.html

<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>

Antwoord 21

Ik kreeg deze fout omdat ik een variabele in component.html gebruikte die niet in component.ts was gedeclareerd. Nadat ik het onderdeel in HTML had verwijderd, was deze fout verdwenen.


Antwoord 22

Voor iedereen die hiermee worstelt. Hier is een manier om deze fout correct te debuggen: https://blog.angular-university.io/ hoek-debugging/

In mijn geval heb ik deze fout inderdaad verwijderd door deze [verborgen] hack te gebruiken in plaats van *ngIf…

Maar de link die ik gaf stelde me in staat om THE GUILTY*ngIf 🙂

te vinden

Veel plezier.


Antwoord 23

Mijn probleem was dat ik een Ngbmodal-pop-up opende bij het laden van dit object dat werd gewijzigd nadat het was gecontroleerd. Ik heb het kunnen oplossen door de modale pop-up in setTimeout te openen.

setTimeout(() => {
  this.modalReference = this.modalService.open(this.modal, { size: "lg" });
});

Antwoord 24

Ik had dit probleem met tussen RxJS/Observables en statische nepgegevens. In het begin gebruikte mijn applicatie statische nepgegevens, arrays van gegevens in mijn geval

De html zag er zo uit:

*ngFor="let x of myArray?.splice(0, 10)"

Dus het idee was om maximaal 10 elementen uit myArrayweer te geven. splice()neemt een kopie van de originele array. Voor zover ik weet dit is prima in Angular.

Toenheb ik de gegevensstroom gewijzigd in Waarneembaar patroon, omdat mijn ‘echte’ gegevens afkomstig zijn van Akita(een bibliotheek voor staatsbeheer). Dit betekent dat mijn html werd:

*ngFor="let x of (myArray$ | async)?.splice(0, 10)"

Waar Myarray $ is [WAS] Type Observable<MyData[]>, en deze gegevensmanipulatie in de sjabloon is waar de fout is veroorzaakt. Doe het niet zo met RXJS-objecten.


25

Ik kreeg deze fout omdat ik de REDUX-acties in Modal en Modal niet verzendt, werd op dat moment niet geopend. Ik stuurde acties het moment waarop modale component invoer ontvangt. Dus ik heb er in SetTime-out gezet om ervoor te zorgen dat modal wordt geopend en vervolgens acties worden ingedepaal.


26

De oplossing … Services en RXJS … Evenement Emitters and Property Binding Beide Gebruik RXJS..U Je bent beter om het zelf te implementeren, meer controle, gemakkelijker te debuggen. Vergeet niet dat Event-emitters RXJS gebruiken. Maak eenvoudigweg een service en binnen een waarneembare, laat elke component zich abonneren op THA Observer en ofwel nieuwe waarde of COSEUME-waarde doorgeven


27

Ik worstelde al een tijdje met dit probleem, voornamelijk tijdens het uitvoeren van tests in mijn dev-omgeving. Ik was in staat om het probleem op te lossen met een eenvoudige settime-out rond een reactie van de service-oproep!

Other episodes