混合反应形式与模板形式
Mixing Reactive Form with Template Form
我使用模板表单构建了一个包含大量输入的大表单。现在我需要动态添加一部分输入。由于使用 Reactive Form 动态添加输入似乎更容易,我想将输入的特定部分更改为 Reactive Form。
那么是否可以在同一个表单标签中混合反应式表单和模板表单?
摘自我在上面发布的 link / https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
部分:但是 ngModel 发生了什么?
请注意,ngModel 仍可用于响应式表单。只是表单值将在两个不同的地方可用:视图模型和 FormGroup,这可能会导致一些混淆。
您可以混合使用 reactive forms 和模板驱动的表单,但强烈不建议这样做。这是因为在响应式表单上使用 ngModel
违背了表单状态不变性的想法。
响应式表单的原则遵循 'one-way' 数据绑定规则,即您遵循一种不可变的方法来管理您的表单状态,从而使您的模板和组件逻辑之间的关注点更加分离。您可以在第一段的 link 上阅读更多关于反应式形式的优势。
假设您要混合使用模板驱动表单和响应式表单。当您 运行 ng serve
:
时,控制台将抛出以下错误
It looks like you're using ngModel on the same form field as formControlName. Support for using the ngModel input property and ngModelChange event with reactive form directives has been deprecated in Angular v6 and will be removed in Angular v7
For more information on this, see our API docs here: https://angular.io/api/forms/FormControlName#use-with-ngmodel
是的,您可以将两者结合使用,首先创建一个反应式表单,然后根据您的要求添加驱动的模板 works.Please 请参考 angular 如何结合使用两者的文档
是的,你可以,检查这个 link 它是完整的反应形式,但它接近你的场景,但你需要做的是进行一些修改以匹配你的情况。
就我而言,我所做的是:
1- 在我的表单中添加以下标签:
//下面的响应式表单标签应该在模板驱动表单中
<mat-tab [label]="'Invoices' | localize">
<mat-card-content [formGroup]="exampleForm">
<!-- Start form units array with first row must and dynamically add more -->
<mat-card formArrayName="units" >
<mat-card-title>Units</mat-card-title>
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let unit of exampleForm.controls.units.controls; let i=index" >
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="exampleForm.controls.units.controls.length > 1 && i > 0" ></mat-divider><br>
<!-- group name in this case row index -->
<div [formGroupName]="i">
<div fxLayout="row" fxLayout.xs="column" fxLayoutWrap fxLayoutGap="3.5%" fxLayoutAlign="center">
<!-- unit name input field -->
<mat-form-field fxFlex="30%">
<input matInput placeholder="Unit name" formControlName="unitName" required>
<!-- input field error -->
<mat-error *ngIf="unit.controls.unitName.invalid">
Unit name is required.
</mat-error>
</mat-form-field>
<!-- unit quantity input field -->
<mat-form-field fxFlex="10%" fxFlex.xs="20">
<input matInput placeholder="Quantity" type="number" formControlName="qty" required>
</mat-form-field>
<!-- unit price input field -->
<mat-form-field fxFlex="20%" fxFlex.xs="grow">
<input matInput placeholder="Unit price" type="number" formControlName="unitPrice" required>
</mat-form-field>
<!-- unit total price input field, calculated and not editable -->
<div fxLayout.xs="row">
<mat-form-field >
<input matInput placeholder="Total sum" formControlName="unitTotalPrice">
</mat-form-field>
<!-- row delete button, hidden if there is just one row -->
<button type="button" mat-mini-fab color="warn" fxFlex="nogrow"
*ngIf="exampleForm.controls.units.controls.length > 1" (click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button type="button" mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add new unit
</button>
<button type="button" mat-raised-button (click)="clearAllUnits()">
<mat-icon>remove_circle</mat-icon>
Clear all
</button>
</mat-card-actions>
</mat-card> <!-- End form units array -->
<br>
<!-- Total price calculation formated with angular currency pipe -->
<mat-card>
Total price is {{ totalSum | currency:'USD':'symbol-narrow':'1.2-2'}}
</mat-card>
</mat-card-content>
2- 及以下在我的 TS 文件中:
export class CreateSubProjectComponent extends AppComponentBase implements OnInit, AfterViewInit, OnDestroy {
exampleForm: FormGroup;
myFormValueChanges$;
totalSum: number = 0;
constructor(injector: Injector,
private formBuilder: FormBuilder,
private currencyPipe: CurrencyPipe){
super(injector);
}
ngOnInit() {
this.exampleForm = this.formBuilder.group({
units: this.formBuilder.array([
this.getUnit()
])
});
// initialize stream on units
this.myFormValueChanges$ = this.exampleForm.controls['units'].valueChanges;
// subscribe to the stream so listen to changes on units
this.myFormValueChanges$.subscribe(units => this.updateTotalUnitPrice(units));
}//end of ngOnInit
ngAfterViewInit() {}
ngOnDestroy() { this.myFormValueChanges$.unsubscribe(); }
private getUnit() {
const numberPatern = '^[0-9.,]+$';
return this.formBuilder.group({
unitName: ['', Validators.required],
qty: [1, [Validators.required, Validators.pattern(numberPatern)]],
unitPrice: ['', [Validators.required, Validators.pattern(numberPatern)]],
unitTotalPrice: [{value: '', disabled: true}]
});
}
/**
* Add new unit row into form
*/
addUnit() {
const control = <FormArray>this.exampleForm.controls['units'];
control.push(this.getUnit());
}
/**
* Remove unit row from form on click delete button
*/
removeUnit(i: number) {
const control = <FormArray>this.exampleForm.controls['units'];
control.removeAt(i);
}
/**
* This is one of the way how clear units fields.
*/
clearAllUnits() {
const control = <FormArray>this.exampleForm.controls['units'];
while(control.length) {
control.removeAt(control.length - 1);
}
control.clearValidators();
control.push(this.getUnit());
}
/**
* Update prices as soon as something changed on units group
*/
private updateTotalUnitPrice(units: any) {
// get our units group controll
const control = <FormArray>this.exampleForm.controls['units'];
// before recount total price need to be reset.
this.totalSum = 0;
for (let i in units) {
let totalUnitPrice = (units[i].qty*units[i].unitPrice);
// now format total price with angular currency pipe
let totalUnitPriceFormatted = this.currencyPipe.transform(totalUnitPrice, 'USD', 'symbol-narrow', '1.2-2');
// update total sum field on unit and do not emit event myFormValueChanges$ in this case on units
control.at(+i).get('unitTotalPrice').setValue(totalUnitPriceFormatted, {onlySelf: true, emitEvent: false});
// update total price for all units
this.totalSum += totalUnitPrice;
}
}
}
this article 反应形式也接近您的场景
我使用模板表单构建了一个包含大量输入的大表单。现在我需要动态添加一部分输入。由于使用 Reactive Form 动态添加输入似乎更容易,我想将输入的特定部分更改为 Reactive Form。
那么是否可以在同一个表单标签中混合反应式表单和模板表单?
摘自我在上面发布的 link / https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
部分:但是 ngModel 发生了什么?
请注意,ngModel 仍可用于响应式表单。只是表单值将在两个不同的地方可用:视图模型和 FormGroup,这可能会导致一些混淆。
您可以混合使用 reactive forms 和模板驱动的表单,但强烈不建议这样做。这是因为在响应式表单上使用 ngModel
违背了表单状态不变性的想法。
响应式表单的原则遵循 'one-way' 数据绑定规则,即您遵循一种不可变的方法来管理您的表单状态,从而使您的模板和组件逻辑之间的关注点更加分离。您可以在第一段的 link 上阅读更多关于反应式形式的优势。
假设您要混合使用模板驱动表单和响应式表单。当您 运行 ng serve
:
It looks like you're using ngModel on the same form field as formControlName. Support for using the ngModel input property and ngModelChange event with reactive form directives has been deprecated in Angular v6 and will be removed in Angular v7 For more information on this, see our API docs here: https://angular.io/api/forms/FormControlName#use-with-ngmodel
是的,您可以将两者结合使用,首先创建一个反应式表单,然后根据您的要求添加驱动的模板 works.Please 请参考 angular 如何结合使用两者的文档
是的,你可以,检查这个 link 它是完整的反应形式,但它接近你的场景,但你需要做的是进行一些修改以匹配你的情况。 就我而言,我所做的是:
1- 在我的表单中添加以下标签:
//下面的响应式表单标签应该在模板驱动表单中
<mat-tab [label]="'Invoices' | localize">
<mat-card-content [formGroup]="exampleForm">
<!-- Start form units array with first row must and dynamically add more -->
<mat-card formArrayName="units" >
<mat-card-title>Units</mat-card-title>
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let unit of exampleForm.controls.units.controls; let i=index" >
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="exampleForm.controls.units.controls.length > 1 && i > 0" ></mat-divider><br>
<!-- group name in this case row index -->
<div [formGroupName]="i">
<div fxLayout="row" fxLayout.xs="column" fxLayoutWrap fxLayoutGap="3.5%" fxLayoutAlign="center">
<!-- unit name input field -->
<mat-form-field fxFlex="30%">
<input matInput placeholder="Unit name" formControlName="unitName" required>
<!-- input field error -->
<mat-error *ngIf="unit.controls.unitName.invalid">
Unit name is required.
</mat-error>
</mat-form-field>
<!-- unit quantity input field -->
<mat-form-field fxFlex="10%" fxFlex.xs="20">
<input matInput placeholder="Quantity" type="number" formControlName="qty" required>
</mat-form-field>
<!-- unit price input field -->
<mat-form-field fxFlex="20%" fxFlex.xs="grow">
<input matInput placeholder="Unit price" type="number" formControlName="unitPrice" required>
</mat-form-field>
<!-- unit total price input field, calculated and not editable -->
<div fxLayout.xs="row">
<mat-form-field >
<input matInput placeholder="Total sum" formControlName="unitTotalPrice">
</mat-form-field>
<!-- row delete button, hidden if there is just one row -->
<button type="button" mat-mini-fab color="warn" fxFlex="nogrow"
*ngIf="exampleForm.controls.units.controls.length > 1" (click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button type="button" mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add new unit
</button>
<button type="button" mat-raised-button (click)="clearAllUnits()">
<mat-icon>remove_circle</mat-icon>
Clear all
</button>
</mat-card-actions>
</mat-card> <!-- End form units array -->
<br>
<!-- Total price calculation formated with angular currency pipe -->
<mat-card>
Total price is {{ totalSum | currency:'USD':'symbol-narrow':'1.2-2'}}
</mat-card>
</mat-card-content>
2- 及以下在我的 TS 文件中:
export class CreateSubProjectComponent extends AppComponentBase implements OnInit, AfterViewInit, OnDestroy {
exampleForm: FormGroup;
myFormValueChanges$;
totalSum: number = 0;
constructor(injector: Injector,
private formBuilder: FormBuilder,
private currencyPipe: CurrencyPipe){
super(injector);
}
ngOnInit() {
this.exampleForm = this.formBuilder.group({
units: this.formBuilder.array([
this.getUnit()
])
});
// initialize stream on units
this.myFormValueChanges$ = this.exampleForm.controls['units'].valueChanges;
// subscribe to the stream so listen to changes on units
this.myFormValueChanges$.subscribe(units => this.updateTotalUnitPrice(units));
}//end of ngOnInit
ngAfterViewInit() {}
ngOnDestroy() { this.myFormValueChanges$.unsubscribe(); }
private getUnit() {
const numberPatern = '^[0-9.,]+$';
return this.formBuilder.group({
unitName: ['', Validators.required],
qty: [1, [Validators.required, Validators.pattern(numberPatern)]],
unitPrice: ['', [Validators.required, Validators.pattern(numberPatern)]],
unitTotalPrice: [{value: '', disabled: true}]
});
}
/**
* Add new unit row into form
*/
addUnit() {
const control = <FormArray>this.exampleForm.controls['units'];
control.push(this.getUnit());
}
/**
* Remove unit row from form on click delete button
*/
removeUnit(i: number) {
const control = <FormArray>this.exampleForm.controls['units'];
control.removeAt(i);
}
/**
* This is one of the way how clear units fields.
*/
clearAllUnits() {
const control = <FormArray>this.exampleForm.controls['units'];
while(control.length) {
control.removeAt(control.length - 1);
}
control.clearValidators();
control.push(this.getUnit());
}
/**
* Update prices as soon as something changed on units group
*/
private updateTotalUnitPrice(units: any) {
// get our units group controll
const control = <FormArray>this.exampleForm.controls['units'];
// before recount total price need to be reset.
this.totalSum = 0;
for (let i in units) {
let totalUnitPrice = (units[i].qty*units[i].unitPrice);
// now format total price with angular currency pipe
let totalUnitPriceFormatted = this.currencyPipe.transform(totalUnitPrice, 'USD', 'symbol-narrow', '1.2-2');
// update total sum field on unit and do not emit event myFormValueChanges$ in this case on units
control.at(+i).get('unitTotalPrice').setValue(totalUnitPriceFormatted, {onlySelf: true, emitEvent: false});
// update total price for all units
this.totalSum += totalUnitPrice;
}
}
}
this article 反应形式也接近您的场景