Issue
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 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>;
}
Solution
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.
Answered By - Joffrey K
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.