Livnat Menashe
Livnat Menashe

Reputation: 911

Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'

I have angular reactive form in parent component and sections inside childrens component.

Inside the child component I have a checkbox - when its checked - more fields open and I want them all to be required.

I am using setValidators but I'm getting error

ParentFormComponent.html:3 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'. at viewDebugError (core.js:7601) at expressionChangedAfterItHasBeenCheckedError (core.js:7589) at checkBindingNoChanges (core.js:7691) at checkNoChangesNodeInline (core.js:10560) at checkNoChangesNode (core.js:10541) at debugCheckNoChangesNode (core.js:11144) at debugCheckRenderNodeFn (core.js:11098) at Object.eval [as updateRenderer] (ParentFormComponent.html:3) at Object.debugUpdateRenderer [as updateRenderer] (core.js:11087) at checkNoChangesView (core.js:10442)

ParentFormComponent.html:3 ERROR CONTEXT DebugContext_ {view: Object, nodeIndex: 2, nodeDef: Object, elDef: Object, elView: Object}

this is the line of ParentFormComponent.html:3

 <form [formGroup]="parentForm" (ngSubmit)="submitForm()">

Here is my code:

<label class="container">DB
    <input #db type="checkbox" name="db" (change)="checkValue(db.name, db.checked)"> 
    <span class="checkmark"></span>
</label>

<div *ngIf="db.checked" formGroupName="_monitorDB">
    <mat-form-field>
        <input matInput placeholder="Server name" formControlName="ServerName">
    </mat-form-field>

    <mat-form-field>
        <input matInput placeholder="DataBase name" formControlName="DbName">
    </mat-form-field>

    <mat-form-field>
        <input matInput placeholder="table name" formControlName="DB_tableName">
    </mat-form-field>

    <mat-form-field>
        <input matInput placeholder="port" formControlName="DbPort">
    </mat-form-field>

    <mat-form-field>
        <input matInput placeholder="Query" formControlName="Query">
    </mat-form-field>

    <mat-form-field>
        <input matInput placeholder="Permissions" formControlName="Premissions">
    </mat-form-field>

</div>

and in the ts file:

checkValue(name:string, event: any){
    if (event == true){
      this.test.push(name);
      if (name =="db"){
         this.childForm.get('_monitorDB').get('ServerName').setValidators([Validators.required]);
         this.childForm.get('_monitorDB').get('DbName').setValidators([Validators.required]);
         this.childForm.get('_monitorDB').get('DB_tableName').setValidators([Validators.required]);
         this.childForm.get('_monitorDB').get('DbPort').setValidators([Validators.required]);
         this.childForm.get('_monitorDB').get('Query').setValidators([Validators.required]);
         this.childForm.get('_monitorDB').get('Premissions').setValidators([Validators.required]);

      }

    }

    else{
      const index: number = this.test.indexOf(name);
      if (index !== -1) {
          this.test.splice(index, 1);
          if (name =="db"){
            this.childForm.get('_monitorDB').get('ServerName').clearValidators();
            this.childForm.get('_monitorDB').get('ServerName').updateValueAndValidity();
            this.childForm.get('_monitorDB').get('DbName').clearValidators();
            this.childForm.get('_monitorDB').get('DbName').updateValueAndValidity();
            this.childForm.get('_monitorDB').get('DB_tableName').clearValidators();
            this.childForm.get('_monitorDB').get('DB_tableName').updateValueAndValidity();
            this.childForm.get('_monitorDB').get('DbPort').clearValidators();
            this.childForm.get('_monitorDB').get('DbPort').updateValueAndValidity();
            this.childForm.get('_monitorDB').get('Query').clearValidators();
            this.childForm.get('_monitorDB').get('Query').updateValueAndValidity();
            this.childForm.get('_monitorDB').get('Premissions').clearValidators();
            this.childForm.get('_monitorDB').get('Premissions').updateValueAndValidity();
          }
      }      
    }

     this.checkboxArr.emit(this.test);
 }

Upvotes: 46

Views: 77842

Answers (8)

Javi Marz&#225;n
Javi Marz&#225;n

Reputation: 1340

I tend to never use setTimeout as I think it's a bad practice, but I have lately found that when creating custom components and interacting with the DOM, this is sometimes necessary.

However, when I've used it, it has been with a delay of 0ms. Just wrapping the action in a setTimeout with no delay will be enough to move the action to the end of the execution queue, which will solve numerous issues with the interaction between Angular and the DOM.

TL,DR

setTimeout(() => actionThatCausesTheError(), 0);

Upvotes: 1

EGN
EGN

Reputation: 2572

Track where the field is changed and mark the component for changeDetection... it could be in a function or one of Angular lifecycle hooks like AfterViewChecked

constructor(
    private _cdr: ChangeDetectorRef
) {
}

// mark for check where change is happening
this._cdr.markForCheck();
this._cdr.detectChanges();

Changing changeDetection to ChangeDetectionStrategy.OnPush will also solve the problem but it might cause other problems, you will need to manually mark the component for check.

Upvotes: 1

mozpider
mozpider

Reputation: 364

Change the ChangeDetectionStrategy on the parent component to : changeDetection: ChangeDetectionStrategy.OnPush. Simple and clean.

Edit This actually not a solution, it will just hide the error. Please take a look at the github link posted in the comments.

Upvotes: 11

Justin Joseph
Justin Joseph

Reputation: 3899

I faced the same issue and I fixed it by using AfterViewChecked and ChangeDetectorRef:

import { AfterViewChecked, ChangeDetectorRef } from '@angular/core'

export class ClassName implements AfterViewChecked {
  constructor(private readonly changeDetectorRef: ChangeDetectorRef) {}

  ngAfterViewChecked(): void {
    this.changeDetectorRef.detectChanges();
  }
}

Upvotes: 65

San Jaisy
San Jaisy

Reputation: 17078

Adding changeDetection: ChangeDetectionStrategy.OnPush should do a trick

@Component({
  selector: 'app-test-center-location-step3',
  templateUrl: './test-center-location-step3.component.html',
  styleUrls: ['./test-center-location-step3.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

Upvotes: 2

Mostafa Saadatnia
Mostafa Saadatnia

Reputation: 878

try this one:

ngOnInit():void
{
   this._monitorDB.patchValue({
    ServerName: 'ServerName',
    DbName: 'DbName',
    DB_tableName: 'DB_tableName',
    DbPort: 'DbPort',
    Query: 'Query',
    Premissions:'Premissions'
   });
}

Upvotes: 0

DINESH Adhikari
DINESH Adhikari

Reputation: 1366

I am facing same issue I know it's not a way but the only solution I've found is setTimeout

ngAfterViewInit(): void {
 setTimeout(() => {
    this.form.patchValue({
    name: 'xyz',
    email: '[email protected]',
    phone: '0987654321'
   })
  }, );
}

Upvotes: 7

Livnat Menashe
Livnat Menashe

Reputation: 911

I don't know if it's the right thing to do but i solved it by changing:

<div *ngIf="db.checked" formGroupName="_monitorDB">

to

<div [hidden]="!db.checked" formGroupName="_monitorDB">

Upvotes: 3

Related Questions