在检查后,表达式发生了改变。先前的值为“ng-valid: true”。当前的值为“ng-valid: false”。

43

我在父组件中有一个Angular响应式表单,在子组件中有几个部分。

在子组件中,我有一个复选框——选中它时,会打开更多的字段,我希望它们都是必填项。

我正在使用setValidators但是出现错误

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}

这是ParentFormComponent.html的第3行。

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

这是我的代码:

<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>

并且在ts文件中:

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);
 }

你能在 StackBlitz 上提供最小可复现代码吗? - Kedar9444
8个回答

60

我遇到了同样的问题,通过使用AfterViewCheckedChangeDetectorRef解决了它:

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

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

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


2
能够解释一下这个修复的作用就更好了,谢谢! ngAfterViewChecked 在 Angular 完成对组件及其子组件进行变更检测后被调用。 这是一个很好的介绍:https://indepth.dev/posts/1058/a-gentle-introduction-into-change-detection-in-angular - ukie
非常棒的解决方案。对我来说非常有效。谢谢。 - AllJs
2
这是正确的解决方案。 - Md. Mahmud Hasan
15
即使这个方法解决了问题,每次调用ngAfterViewChecked()生命周期钩子时都调用detectChanges()并不是最优的。可以通过使用console.log查看ngAfterViewChecked()被调用的次数,更好的解决方案是仅在需要的时候调用detectChanges()(例如,在设置验证器之后)。 - José Antonio Postigo
2
正如@JoséAntonioPostigo上面提到的,ngAfterViewChecked()是不必要的。在我的情况下,在添加控件后立即调用detectChanges()就可以正常工作。 - Rosty
显示剩余2条评论

11

将父组件的ChangeDetectionStrategy更改为:changeDetection: ChangeDetectionStrategy.OnPush。 简单而清晰。

编辑:这实际上不是一个解决方案,它只会隐藏错误。请查看评论中发布的github链接。


你能详细解释一下吗?为什么要改变检测策略? - Bárbara Este
1
请在此处查看更多讨论:https://github.com/angular/angular/issues/23657#issuecomment-653939615 - mozpider
2
根据您分享的 Github 线程,这个“解决方案”实际上并没有解决错误,只是隐藏了它,并且可能会破坏很多东西。因此,在使用之前,请确保了解 ChangeDetectionStrategy.OnPush 对您意味着什么。 - Hayha
1
当时,它让我解决了那个错误,所以我发布了这个解决方案。但是我已经编辑了我的解决方案。你需要调试这个错误。 - mozpider

7

我遇到了同样的问题,虽然这不是一个好方法,但我找到的唯一解决方案是使用setTimeout。

ngAfterViewInit(): void {
 setTimeout(() => {
    this.form.patchValue({
    name: 'xyz',
    email: 'abc@gmail.com',
    phone: '0987654321'
   })
  }, );
}

3
我不知道这是否正确,但我通过更改以下内容解决了它:
<div *ngIf="db.checked" formGroupName="_monitorDB">

to

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

1
这不是正确的做法,因为您的元素是在表单中可用的,如果表单很大,这会导致性能问题。如果我提供的答案对您没有帮助,请告诉我,我将为您提供不使用rxweb方法的答案。 - Ajay Ojha
这对我很有用,当我使用复选框执行此过程时,我尝试显示或隐藏表单的一部分。 - Vah Run

1
我倾向于从不使用setTimeout,因为我认为这是一种不好的做法,但最近发现在创建自定义组件并与DOM交互时,有时是必要的。然而,当我使用它时,延迟为0ms。只需用没有延迟的setTimeout包装动作就足以将动作移动到执行队列的末尾,这将解决Angular和DOM之间的许多问题。TL,DR
setTimeout(() => actionThatCausesTheError(), 0);

谢谢,适合我,我有一个自定义表单,从后台动态列出一系列问题,然后我需要在获取答案后修补值。 - Ron Michael
谢谢,对我来说没问题。我有一个自定义表单,从后端动态列出了一系列问题,然后在获取答案后需要修补值。 - undefined

1

跟踪字段的更改位置并标记组件以进行变更检测...它可以在函数中或Angular生命周期钩子之一,如AfterViewChecked中。

constructor(
    private _cdr: ChangeDetectorRef
) {
}

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

将changeDetection更改为ChangeDetectionStrategy.OnPush也可以解决问题,但这可能会引起其他问题,您需要手动标记组件以进行检查。

1

添加changeDetection: ChangeDetectionStrategy.OnPush应该会有所帮助。

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

0

试试这个:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接