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