web-dev-qa-db-fra.com

Angular 4: `ExpressionChangedAfterItHasBeenCheckedError: L'expression a changé après avoir été vérifiée

Chaque EventEmiiter de mon module enfant génère cette erreur et je ne trouve pas de solution pour cela.

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.

C'est ce qui déclenche mon EventEmitter:

ngOnChanges(changes: any) {
    if (changes.groupingTabValid) {
        if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue) {
            this.groupingTabValidChange.emit(this.groupingTabValid);
        }
    }
}

Voici le code HTML de mon composant "principal"

<year-overview-grouping [definitionDetails]="definitionDetails"
                        [fixedData]="fixedData"
                        [showValidation]="showValidation"
                        [groupingTabValid]="groupingTabValid"
                        (groupingTabValidChange)="setValidators('groupingTab', $event)">

</year-overview-grouping>

Qui appelle cette fonction

public setValidators(validator: string, e: boolean) {
    switch (validator) {
        case "groupingTab":             
            this.groupingTabValid = e;
            break;

        case "selectionTab":
            this.selectionTabValid = e;
            break;
    }

    if (this.groupingTabValid && this.selectionTabValid) {
        this.valid = true;
    } else {
        this.valid = false;
    }
}

1) Dans une explication simple, quelle est la cause de cette erreur?

2) Quelles mesures puis-je prendre pour résoudre ce problème?

15
Nicolas

Respecter la règle de flux de données unidirectionnelle

Essayez de reporter l'appel à émettre pour un tick avec un setTimeout

if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue) {
    setTimeout(() => this.groupingTabValidChange.emit(this.groupingTabValid), 0)
}
11
Draeken

Importez simplement ChangeDetectionStrategy puis ajoutez-le à votre composant

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-bla-bla-bla',
  templateUrl: './xxxxx'
})
4
Cengkuru Michael

Vous pouvez éviter d'utiliser setTimeout et informer Angular que des modifications sont sur le point de se produire après la vérification initiale.

Vous pouvez injecter ChangeDetectorRef dans votre composant et utiliser sa méthode markForCheck.

/* ... */

constructor(private cdr: ChangeDetectorRef) {}

/* ... */

if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue {
  this.cdr.markForCheck();
  this.groupingTabValidChange.emit(this.groupingTabValid);
}
2
Michal Pietraszko

Je commence tout juste à examiner moi-même ce type d'exception, donc aucun expert, mais supposons qu'il soit là pour arrêter les boucles de rétroaction.

Je ne vois pas de raison évidente pour laquelle vous voulez envoyer la valeur à l’enfant et la sauvegarder au parent, mais je suppose que l’enfant applique une logique supplémentaire . service plutôt qu'un composant enfant. 

Je réalise que le code de plunker n'a pas de boucle, mais le mécanisme angulaire semble assez simple - il suffit de revérifier les valeurs à la fin du cycle. Notez que groupingTabValid dans le code de votre question envoie directement des commentaires. 

En regardant le code de plongeur comme donné, structurellement, le changement d’âge peut être géré sur le parent. L'événement click serait (click)=changeAge(age.value) et la méthode (sur le parent) pour le gérer 

changeAge(inputAge) {
  this.newAge = inputAge;
  this.person.age = inputAge;
}

Voici une fourchette du plunker. Plunker modifié
Le composant enfant met toujours à jour person.age, mais ne provoque plus d'erreur, car la valeur est identique à celle définie sur le parent.

1
Richard Matsen