Angular 13 - 无法使用来自 API 的数据更新 formGroup

Angular 13 - Unable to update formGroup with data from API

我正在尝试制作一个表单来提交数据,然后用数据填充该表单以便在稍后阶段对其进行编辑。该页面可以是新页面,但最好保留表单布局和验证方式。

我已经尝试对每个条目使用 [(ngModel)],它适用于除可以多个条目的汽车列表之外的所有内容。此方法还给我一条消息,说它已被弃用,让我认为这不是方法: "It looks like you're using ngModel on the same form field as formControlName....."

附件是显示我遇到的问题的简化页面。如何使用 API 中的数据预填充输入?

car.component.html

<div class="center_container">
  <mat-card>
    <mat-card-content>
      <form class="input_form" [formGroup]="formValues">
        <p>
          <mat-form-field style="padding-right: 10px;" class="two-third-width">
            <mat-label>Name</mat-label>
            <input
              matInput
              placeholder="Seller name"
              autocomplete="off"
              formControlName="name"
            >
            <mat-icon class="mat-primary" matSuffix
                      matTooltip="[REQUIRED] Please input">info
            </mat-icon>
          </mat-form-field>

          <mat-form-field class="third-width">
            <mat-label>Margin</mat-label>
            <input
              matInput
              placeholder="Seller Margin..."
              autocomplete="off"
              formControlName="margin"
            >
            <mat-icon class="mat-primary" matSuffix
                      matTooltip="[REQUIRED] Please input">info
            </mat-icon>
          </mat-form-field>
        </p>

        <table class="full-width" formArrayName="cars">
          <tr *ngFor="let _ of cars.controls; index as i; let first=first">
            <ng-container [formGroupName]="i">
              <td>
                <mat-icon (click)="addRow()">add</mat-icon>
              </td>
              <td>
                <mat-icon *ngIf="!first" (click)="removeRow(i)">remove</mat-icon>
              </td>
              <td class="model">
                <mat-form-field class="full-width">
                  <mat-label>Model</mat-label>
                  <input
                    matInput
                    placeholder="Car model"
                    autocomplete="off"
                    formControlName="model"
                  >
                  <mat-icon class="mat-primary" matSuffix
                            matTooltip="[REQUIRED] Please input">info
                  </mat-icon>
                </mat-form-field>
              </td>
              <td class="state">
                <mat-form-field class="full-width">
                  <mat-label>State</mat-label>
                  <mat-select formControlName="state">
                    <mat-option value="new">new</mat-option>
                    <mat-option value="used">used</mat-option>
                  </mat-select>
                  <mat-icon class="mat-primary" matSuffix
                            matTooltip="[REQUIRED] Select it">info
                  </mat-icon>
                </mat-form-field>
              </td>
              <td class="value">
                <mat-form-field class="full-width">
                  <mat-label>Value</mat-label>
                  <input
                    matInput
                    placeholder="The value..."
                    autocomplete="off"
                    formControlName="value"
                  >
                  <mat-icon class="mat-primary" matSuffix
                            matTooltip="[REQUIRED] Some reminder">info
                  </mat-icon>
                </mat-form-field>
              </td>
            </ng-container>
          </tr>
        </table>
      </form>
    </mat-card-content>
    <mat-card-actions align="end">
      <button mat-raised-button [disabled]="!formValues.valid" (click)="saveSeller()" color="primary">Save seller <i
        class="material-icons">send</i></button>
    </mat-card-actions>
    <mat-card-footer style="margin: 1px;">
      <i class="mat-small">create date here</i>
    </mat-card-footer>
  </mat-card>
</div>

car.component.ts

import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup, FormArray, Validators} from '@angular/forms';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-car',
  templateUrl: './car.component.html',
  styleUrls: ['./car.component.css']
})
export class CarComponent implements OnInit {
  formValues = new FormGroup({
    cars: new FormArray([
      new FormGroup({
        model: new FormControl('', [Validators.required]),
        value: new FormControl('', [Validators.required]),
        state: new FormControl('', [Validators.required])
      })
    ]),
    name: new FormControl('', [Validators.required]),
    margin: new FormControl('', [Validators.required]),
    create_date: new FormControl('')
  });

  cars = this.formValues.get('cars') as FormArray;
  sellerIdFromRoute: any;
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    const routeParams = this.route.snapshot.paramMap;
    this.sellerIdFromRoute = String(routeParams.get('sellerId'));
    this.getSeller(this.sellerIdFromRoute);
  }

  // Form
  addRow() {
    const group = new FormGroup({
      model: new FormControl('', [Validators.required]),
      value: new FormControl('', [Validators.required]),
      state: new FormControl('', [Validators.required])
    });

    this.cars.push(group);
  }

  removeRow(i: number) {
    this.cars.removeAt(i);
  }

  saveSeller(){
    // POSTS TO API, THIS WORKS
    alert('added/updated seller');
    console.log(this.formValues.value);
  }

  getSeller(id) {
    // This is an example response from my API
    const example: Seller = {
      _id: id,
      name: 'Used Ford salesman',
      margin: 22,
      create_date: {
        $date: '2022-05-20T19:45:22.715Z'
      },
      cars: [
        {
          model: 'Estate',
          value: 12345,
          state: 'new'
        },
        {
          model: 'Saloon',
          value: 12345,
          state: 'used'
        },
      ]
    };
    console.log(example);
    return example;
  }

}


export interface Cars {
  model: string;
  value: number;
  state: string;
}

export interface Seller {
  _id: string;
  name: string;
  margin: number;
  create_date: any;
  cars: Array<Cars>;
}

正如 Angular 警告您的那样,混合使用 Template driven formReactive form 是一种不好的做法,这会导致很多不想要的行为(您可以获得很多关于 Angular 表格在 official Angular doc).

要使用响应式表单(您的情况)填充表单控件值,您必须在组件初始化时设置值。您可以同时设置所有 formGroup 值:

  ngOnInit() {
    ...
    this.formValues.setValue({
      name: "Test", 
      margin: "5",
      cars: [{ model: 'Mercedes', value: 'x', state: 'state'}],
      create_date: "01/01/2022"
    });
  }

如果您不想初始化所有 formControl,请在此处使用 patchValue 方法而不是 setValue

或者,也可以直接在 formControl 上设置值:

this.formValues.get('name')?.setValue('newName'); // or patchValue

就个人而言,我更喜欢在不同的领域分别管理 formControls,它为您提供了更多的灵活性和更多的控制权,以便充分利用响应式表单的丰富性 api。

不确定这是否是最好的方法,但最终使用了这个:

  ngOnInit() {
    const routeParams = this.route.snapshot.paramMap;
    this.sellerIdFromRoute = String(routeParams.get('sellerId'));
    const valuesFromAPI = this.getSeller(this.sellerIdFromRoute);
    this.addCars(valuesFromAPI.cars);
    this.formValues.patchValue({
      name: valuesFromAPI.name,
      margin: valuesFromAPI.margin,
      create_date: valuesFromAPI.cars
    });
  }

  addCars(cars: Cars[]) {
    cars.forEach(car => {
      const group = new FormGroup({
        model: new FormControl(car.model, [Validators.required]),
        value: new FormControl(car.value, [Validators.required]),
        state: new FormControl(car.state, [Validators.required])
      });
      this.cars.push(group);
    });
  }