Angular Form 没有为我的表单中的第一个字段设置 FormControl

问题描述 投票:0回答:1

我已经为这个问题苦苦挣扎了几个星期,但无法弄清楚是什么原因造成的。 (角度版本:16.1.4)

我用BehaviorSubject填充这个表单组件,当打印到控制台时,表单和数据库返回所有正确的值,但是,对于html模板中首先出现的任何字段,表单都会抛出以下错误。

ERROR Error: Cannot find control with name: 'empNum'
    at _throwError (forms.mjs:3150:11)
    at setUpControl (forms.mjs:2933:13)
    at FormGroupDirective.addControl (forms.mjs:4782:9)
    at FormControlName._setUpControl (forms.mjs:5370:43)
    at FormControlName.ngOnChanges (forms.mjs:5315:18)
    at FormControlName.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:2849:14)
    at callHookInternal (core.mjs:3841:14)
    at callHook (core.mjs:3872:9)
    at callHooks (core.mjs:3823:17)
    at executeInitAndCheckHooks (core.mjs:3773:9)
ERROR TypeError: Cannot read properties of null (reading 'invalid')
    at HistoryFormComponent_Template (history-form.component.ts:19:15)
    at ReactiveLViewConsumer.runInContext (core.mjs:10414:13)
    at executeTemplate (core.mjs:10954:22)
    at refreshView (core.mjs:12475:13)
    at detectChangesInView (core.mjs:12639:9)
    at detectChangesInComponent (core.mjs:12615:5)
    at detectChangesInChildComponents (core.mjs:12653:9)
    at refreshView (core.mjs:12525:13)
    at detectChangesInView (core.mjs:12639:9)
    at detectChangesInComponent (core.mjs:12615:5)
ERROR TypeError: Cannot read properties of null (reading 'invalid')
    at HistoryFormComponent_Template (history-form.component.ts:19:15)
    at ReactiveLViewConsumer.runInContext (core.mjs:10414:13)
    at executeTemplate (core.mjs:10954:22)
    at refreshView (core.mjs:12475:13)
    at detectChangesInView (core.mjs:12639:9)
    at detectChangesInComponent (core.mjs:12615:5)
    at detectChangesInChildComponents (core.mjs:12653:9)
    at refreshView (core.mjs:12525:13)
    at detectChangesInView (core.mjs:12639:9)
    at detectChangesInComponent (core.mjs:12615:5)

我可以切换 html 模板中字段的顺序,但表单中的第一个字段总是抛出此错误。当它不是第一个字段时,“empNum”控件将正确设置,并且初始值将按预期填充。我的预感是正在发生一些奇怪的异步行为,但我不知道如果是这种情况如何解决这个问题。

相同的代码结构正用于数据库中其他集合上的其他表单,并且在这些表单上一切都按预期工作。

这是该组件的完整代码:

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { History } from '../history';
import { ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-history-form',
  template: `

    <!-- FORM -->
    <form [formGroup]="historyForm" class="history-form" autocomplete="off" (ngSubmit)="submitForm()">

        <div class="form-floating mb-3">
          <input class="form-control" type="number" id="empNum" formControlName="empNum" placeholder="Employee Number">
          <label for="empNum">Employee Number</label>
        </div>

        <div *ngIf="empNum.invalid && (empNum.dirty || empNum.touched)" class="alert alert-danger">
          <div *ngIf="empNum.errors?.['required']">
            Employee number is required.
          </div>
        </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="text" id="jobNum" formControlName="jobNum" placeholder="Job Number" required>
        <label for="jobNum">Job Number</label>
      </div>

      <div *ngIf="jobNum.invalid && (jobNum.dirty || jobNum.touched)" class="alert alert-danger">
        <div *ngIf="jobNum.errors?.['required']">
          Job number is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="text" id="partNum" formControlName="partNum" placeholder="Part Number" required>
        <label for="partNum">Part Number</label>
      </div>

      <div *ngIf="partNum.invalid && (partNum.dirty || partNum.touched)" class="alert alert-danger">
        <div *ngIf="partNum.errors?.['required']">
          Part number is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="text" id="machArea" formControlName="machArea" placeholder="Machine / Area" required>
        <label for="machArea">Machine / Area</label>
      </div>

      <div *ngIf="machArea.invalid && (machArea.dirty || machArea.touched)" class="alert alert-danger">
        <div *ngIf="machArea.errors?.['required']">
          Machine / Area is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="text" id="action" formControlName="action" placeholder="Action" required>
        <label for="action">Action</label>
      </div>

      <div *ngIf="action.invalid && (action.dirty || action.touched)" class="alert alert-danger">
        <div *ngIf="action.errors?.['required']">
          Action is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="tTime" formControlName="tTime" placeholder="Total Time" required>
        <label for="tTime">Total Time</label>
      </div>

      <div *ngIf="tTime.invalid && (tTime.dirty || tTime.touched)" class="alert alert-danger">
        <div *ngIf="tTime.errors?.['required']">
          Total Time is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="qtyReq" formControlName="qtyReq" placeholder="Quantity Required" required>
        <label for="qtyReq">Quantity Required</label>
      </div>

      <div *ngIf="qtyReq.invalid && (qtyReq.dirty || qtyReq.touched)" class="alert alert-danger">
        <div *ngIf="qtyReq.errors?.['required']">
          Quantity is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="qtyConf" formControlName="qtyConf" placeholder="Quantity Conforming" required>
        <label for="qtyConf">Quantity Conforming</label>
      </div>

      <div *ngIf="qtyConf.invalid && (qtyConf.dirty || qtyConf.touched)" class="alert alert-danger">
        <div *ngIf="qtyConf.errors?.['required']">
          Quantity Conforming is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="qtyNonConf" formControlName="qtyNonConf" placeholder="Quantity Non-Conforming" required>
        <label for="qtyNonConf">Quantity Non-Conforming</label>
      </div>

      <div *ngIf="qtyNonConf.invalid && (qtyNonConf.dirty || qtyNonConf.touched)" class="alert alert-danger">
        <div *ngIf="qtyNonConf.errors?.['required']">
          Quantity Non-conforming is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="opNum" formControlName="opNum" placeholder="Operation Number" required>
        <label for="opNum">Operation Number</label>
      </div>

      <div *ngIf="opNum.invalid && (opNum.dirty || opNum.touched)" class="alert alert-danger">
        <div *ngIf="opNum.errors?.['required']">
          Operation Number is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="machCost" formControlName="machCost" placeholder="Machine Cost" required>
        <label for="machCost">Machine Cost</label>
      </div>

      <div *ngIf="machCost.invalid && (machCost.dirty || machCost.touched)" class="alert alert-danger">
        <div *ngIf="machCost.errors?.['required']">
          Machine Cost is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="laborCost" formControlName="laborCost" placeholder="Labor Cost" required>
        <label for="laborCost">Labor Cost</label>
      </div>

      <div *ngIf="laborCost.invalid && (laborCost.dirty || laborCost.touched)" class="alert alert-danger">
        <div *ngIf="laborCost.errors?.['required']">
          Labor Cost is required.
        </div>
      </div>


      <div class="form-floating mb-3">
        <input class="form-control" type="number" id="totalCost" formControlName="totalCost" placeholder="Total Cost" required>
        <label for="totalCost">Total Cost</label>
      </div>

      <div *ngIf="totalCost.invalid && (totalCost.dirty || totalCost.touched)" class="alert alert-danger">
        <div *ngIf="totalCost.errors?.['required']">
          Total Cost is required.
        </div>
      </div>


      <!-- DATE TIME PICKER -->
      <div class="form-floating mb-3">
        <input
          type="datetime-local"
          class="form-control"
          formControlName="dateTime"
          placeholder="Select Date and Time"
          id="dateTime"
          />
        <label for="dateTime">Select Date and Time</label>
      </div>

      <div *ngIf="dateTime.invalid && (totalCost.dirty || totalCost.touched)" class="alert alert-danger">
        <div *ngIf="totalCost.errors?.['required']">
          Date is required.
        </div>
      </div>

      <button class="btn btn-primary" type="submit" [disabled]="historyForm.invalid">Submit</button>

</form>


`,
  styles: [
    //  `.history-form {
    //   max-width: 560px;
    //   margin-left: auto;
    //   margin-right: auto;
    //  }`
  ]

})
export class HistoryFormComponent implements OnInit {
  @Input()
  initialState: BehaviorSubject<History> = new BehaviorSubject(<History>{});

  @Output()
  formValuesChanged = new EventEmitter<History>();

  @Output()
  formSubmitted = new EventEmitter<History>();

  historyForm: FormGroup = new FormGroup({});

  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {}

  get empNum() { return this.historyForm.get('empNum')!; }
  get jobNum() { return this.historyForm.get('jobNum')!; }
  get partNum() { return this.historyForm.get('partNum')!; }
  get machArea() { return this.historyForm.get('machArea')!; }
  get action() { return this.historyForm.get('action')!; }
  get tTime() { return this.historyForm.get('tTime')!; }
  get qtyReq() { return this.historyForm.get('qtyReq')!; }
  get qtyConf() { return this.historyForm.get('qtyConf')!; }
  get qtyNonConf() { return this.historyForm.get('qtyNonConf')!; }
  get opNum() { return this.historyForm.get('opNum')!; }
  get machCost() { return this.historyForm.get('machCost')!; }
  get laborCost() { return this.historyForm.get('laborCost')!; }
  get totalCost() { return this.historyForm.get('totalCost')!; }
  get dateTime() { return this.historyForm.get('dateTime')!; } 

  // get dateTime() {
  //   const dateTime = String(this.historyForm.get('dateTime')!);
  //   return (new Date(dateTime).toISOString);
  // }

  ngOnInit() {
    this.initialState.subscribe(history => {
      this.historyForm = this.fb.group({
        empNum: [history.empNum, [Validators.required]],
        jobNum: [history.jobNum, [Validators.required]],
        partNum: [history.partNum, [Validators.required]],
        machArea: [history.machArea, [Validators.required]],
        action: [history.action, [Validators.required]],
        tTime: [history.tTime, [Validators.required]],
        qtyReq: [history.qtyReq, [Validators.required]],
        qtyConf: [history.qtyConf, [Validators.required]],
        qtyNonConf: [history.qtyNonConf, [Validators.required]],
        opNum: [history.opNum, [Validators.required]],
        machCost: [history.machCost, [Validators.required]],
        laborCost: [history.laborCost, [Validators.required]],
        totalCost: [history.totalCost, [Validators.required]],
        // to get correct initial dateTime format, must convert to JS Date object, then to ISO String,
        // then slice off the timezone portion. 
        // pushing back to database: + ".000+00:00" to the date string.
        dateTime: [new Date(history.dateTime).toISOString().slice(0, 19), [Validators.required]],
      });

      // Manually trigger change detection
      this.cdr.detectChanges();

      // console.log(this.historyForm.value);
      // console.log(this.empNum.value, this.jobNum.value, this.opNum.value)
      // const empNumState = this.historyForm.get('empNum')
      // console.log(empNumState);

    });

    this.historyForm.valueChanges.subscribe((val) => { this.formValuesChanged.emit(val); });
  }


  submitForm() {
    console.log(this.historyForm.value);
    this.formSubmitted.emit(this.historyForm.value);
  }
}

非常感谢任何可能的修复或提示。我是 Angular 的新手,所以我确信我可能遗漏了一些东西。

我尝试在 ngOnInit() 中手动触发更改检测,但没有成功。我还注释掉了 html 中的所有验证检查,这也没有改变任何内容。

html angular typescript forms angular-reactive-forms
1个回答
0
投票

通过在 FormBuilder 构造函数中初始化空 FormGroup 控件解决了此问题。

  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
    this.historyForm = this.fb.group({
      empNum: ["", [Validators.required]],
      jobNum: ["", [Validators.required]],
      partNum: ["", [Validators.required]],
      machArea: ["", [Validators.required]],
      action: ["", [Validators.required]],
      tTime: ["", [Validators.required]],
      qtyReq: ["", [Validators.required]],
      qtyConf: ["", [Validators.required]],
      qtyNonConf: ["", [Validators.required]],
      opNum: ["", [Validators.required]],
      machCost: ["", [Validators.required]],
      laborCost: ["", [Validators.required]],
      totalCost: ["", [Validators.required]],
      dateTime: ["", [Validators.required]],
    });
  }

感谢永顺指出这一点!

© www.soinside.com 2019 - 2024. All rights reserved.