Home > Enterprise >  Angular 13 - Unable to update formGroup with data from API
Angular 13 - Unable to update formGroup with data from API

Time:05-21

I am trying to make a form to submit data, then populate that form with data in order to edit it on a later stage. The page can be new page, but it would be nice to keep the form layout and validation either way.

I have tried using [(ngModel)] for each entry that works for everything except the list of cars that can multiple entries. Also this method gives me a message saying it's deprecated making me think that is not the way: "It looks like you're using ngModel on the same form field as formControlName....."

Attached is a simplified page that show the issue I am having. How do I prefill the inputs with the data from the API ?

car.component.html

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

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

        <table  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 >
                <mat-form-field >
                  <mat-label>Model</mat-label>
                  <input
                    matInput
                    placeholder="Car model"
                    autocomplete="off"
                    formControlName="model"
                  >
                  <mat-icon  matSuffix
                            matTooltip="[REQUIRED] Please input">info
                  </mat-icon>
                </mat-form-field>
              </td>
              <td >
                <mat-form-field >
                  <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  matSuffix
                            matTooltip="[REQUIRED] Select it">info
                  </mat-icon>
                </mat-form-field>
              </td>
              <td >
                <mat-form-field >
                  <mat-label>Value</mat-label>
                  <input
                    matInput
                    placeholder="The value..."
                    autocomplete="off"
                    formControlName="value"
                  >
                  <mat-icon  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
        >send</i></button>
    </mat-card-actions>
    <mat-card-footer style="margin: 1px;">
      <i >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>;
}

CodePudding user response:

As Angular warns you, it's a bad practice to mix Template driven form and Reactive form this can lead to a lot of unwanted behaviours (you can get a lot of informations about Angular forms in the official Angular doc).

To fill form control values with Reactive form (your case), you have to set values at your component initialization. You can set all formGroup values at the same time :

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

Use patchValue method instead of setValue here if you don't want to initialise all formControls.

Or, it's also possible to set value directly on formControl :

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

Personally, I prefer managing formControls separately in different fields, it gives you more flexibility and more control in order to take full advantage of the richness of the reactive forms api.

CodePudding user response:

Not sure if this is the best way, but ended up using this:

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