简体   繁体   English

Angular — ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 (嵌套的FormArray)

[英]Angular — ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. (Nested FormArray)

Preface : I realize this may be a duplicate, but having read the detailed explanation of the error found here I still do not understand how my code would be invalidating the dirty checking performed in change detection. 前言 :我意识到这可能是重复的,但是阅读了此处错误的详细说明后,我仍然不明白我的代码将如何使更改检测中执行的脏检查无效。

I have a FormGroup that contains a FormArray. 我有一个包含FormArray的FormGroup。 I would like to nest the FormArray into a child component since it contains quite a bit of it's own specific business logic. 我想将FormArray嵌套到一个子组件中,因为它包含了很多自己的特定业务逻辑。

When I load the component in the browser, and when I run unit tests I receive the following exception: 在浏览器中加载组件时,以及在运行单元测试时,会收到以下异常:

ParentComponentA.html:2 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
    at viewDebugError (core.es5.js:8426)
    at expressionChangedAfterItHasBeenCheckedError (core.es5.js:8404)
    at checkBindingNoChanges (core.es5.js:8568)
    at checkNoChangesNodeInline (core.es5.js:12448)
    at checkNoChangesNode (core.es5.js:12414)
    at debugCheckNoChangesNode (core.es5.js:13191)
    at debugCheckRenderNodeFn (core.es5.js:13131)
    at Object.eval [as updateRenderer] (ParentComponentA.html:2)
    at Object.debugUpdateRenderer [as updateRenderer] (core.es5.js:13113)
    at checkNoChangesView (core.es5.js:1223

Parent Component A: 父组件A:

@Component({
  selector: 'app-parent-component-a',
  templateUrl: './parent-component-a.component.html',
  styleUrls: ['./parent-component-a.component.scss']
})
export class ParentComponentA implements OnInit, OnDestroy {
  activeMediaViewport: string; // Should match a value of MaterialMediaQueries enum
  mediaWatcher: Subscription;
  parentForm: FormGroup;
  childComponentDisplayMode: number; // Should match a value of ComponentDisplayModes enum

  constructor(private formBuilder: FormBuilder, private mediaQueryService: ObservableMedia) {
    const prepareComponentBFormControl = (): FormGroup => {
      return formBuilder.group({
        'code': '',
        'weight': '',
        'length': '',
        'width': '',
        'height': '',
      });
    };

    const prepareParentForm = (): FormGroup => {
      return formBuilder.group({
        // ... omitted other properties
        'childComponentList': formBuilder.array([prepareComponentBFormControl()])
      });
    };

  }

  ngOnInit() {
    this.initializeWatchers();
  }

  ngOnDestroy() {
    this.mediaWatcher.unsubscribe();
  }

  /**
   * Sets intervals and watchers that span the entire lifecycle of the component and captures their results to be used for deregistration.
   */
  private initializeWatchers(): void {
    this.mediaWatcher = this.mediaQueryService
      .subscribe(mediaChange => {
        this.activeMediaViewport = mediaChange.mqAlias;
        this.childComponentDisplayMode = this.calculateComponentDisplayMode(this.activeMediaViewport);
      });
  }
}

component A HTML markup attributes 组件HTML标记属性

<child-component-b [displayMode]="childComponentDisplayMode"
                   [nestedFormList]="childComponentList">
</child-component-b>

Child Component B: 子组件B:

@Component({
  selector: 'child-component-b',
  templateUrl: './child-component-b.component.html',
  styleUrls: ['./child-component-b.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponentB implements OnInit {

  @Input() displayMode: number; // Should match a value of ComponentDisplayModes enum
  @Input() nestedFormList: FormArray;

  mobileDisplays: Array<number>;
  largeDisplays: Array<number>;
  numberOfRowsToAdd: FormControl;

  constructor(private formBuilder: FormBuilder) {
    this.mobileDisplays = [ComponentDisplayModes.TABLET_PORTRAIT, ComponentDisplayModes.PHONE_LANDSCAPE, ComponentDisplayModes.PHONE_PORTRAIT];
    this.largeDisplays = [ComponentDisplayModes.DESKTOP, ComponentDisplayModes.TABLET_LANDSCAPE];
  }

  ngOnInit() {
    // including in this SO post since it references the @Input property
    this.numberOfRowsToAdd = new FormControl(this.defaultRowsToAdd, this.addBuisnessLogicValidator(this.nestedFormList)); 
  }

  private addBuisnessLogicValidator(nestedFormListRef: FormArray): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} => {
    const rowsRemaining = maxLinesAllowed - nestedFormListRef.length;
    const rowsToAdd = control.value;
    const isInvalid = isNaN(parseInt(rowsToAdd, 10)) || rowsToAdd < 0 || rowsToAdd > rowsRemaining;
    return isInvalid ? {'invalidRowCount': {value: control.value}} : null;
  };

} } }}

component B HTML markup attributes 组件B HTML标记属性

<div *ngFor="let listItem of nestedFormList.controls; index as index"
     [formGroup]="listItem">
</div>

I think maybe using *ngFor in the child component may "dirty" the view's value during change detection? 我认为也许在子组件中使用* ngFor可能会在更改检测期间“弄脏”视图的值?

It turns out that the problem was operator error (go figure). 事实证明,问题出在操作员错误(如图)。 I found out that this exception was thrown, because I had an artifact "required" attribute defined on the HTML <input> element without stating that it was required in the FormControl validator defintion using Angular's Validators.required static method. 我发现引发了此异常,因为我在HTML <input>元素上定义了一个工件“ required”属性,而没有说明使用Angular的Validators.required静态方法在FormControl验证器定义中是必需的。

Having them defined in one place but not the other caused the value to change between the first and second Change Detection routines. 将它们定义在一个位置而不是另一个位置会导致该值在第一和第二“更改检测”例程之间更改。

So... 所以...

          <input mdInput
               formControlName="weight"
               placeholder="Weight"
               type="text"
               aria-label="weight"
               maxlength="6"
               required>

The "required" and "maxlength" attributes need removed from the template and placed in the FormGroup definition, ie 需要从模板中删除“必需”和“最大长度”属性,并将其放置在FormGroup定义中,即

const prepareComponentBFormControl = (): FormGroup => {
      return formBuilder.group({
        'code': '',
        'weight': ['', Validators.required, Validators.maxlength(6)],
        'length': '',
        'width': '',
        'height': '',
      });
    };

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ERROR 错误:ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 Angular - ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Angular Angular ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 cdkdrag - Angular ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. cdkdrag 调用toISOString()会引发“ ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。” - Calling toISOString() thows “ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.” ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 以前的值:“未定义” - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined' Angular 4:`ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后发生了变化 - Angular 4: `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked Angular - ExpressionChangedAfterItHasBeenCheckedError:检查后表达式已更改 - Angular - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked ExpressionChangedAfterItHasBeenCheckedError:在角度检查表达式后,表达式已更改 - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked in angular ExpressionChangedAfterItHasBeenCheckedError:检查后表达式已更改。 以前的值:&#39;ngIf:true&#39;。 当前值:&#39;ngIf:false&#39; - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false' ExpressionChangedAfterItHasBeenCheckedError:检查后表达式已更改。 以前的值:“未定义”异常 - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined' Exception ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 在 mat-tab-group 上使用 Ngclass - ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. with Ngclass on mat-tab-group
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM