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 form
和 Reactive 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);
});
}
我正在尝试制作一个表单来提交数据,然后用数据填充该表单以便在稍后阶段对其进行编辑。该页面可以是新页面,但最好保留表单布局和验证方式。
我已经尝试对每个条目使用 [(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 form
和 Reactive 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);
});
}