Смешивание реактивной формы с шаблонной формой
Я построил большую форму с большим количеством ввода, используя шаблон формы. Теперь у меня есть требование добавить часть ввода динамически. Поскольку динамическое добавление входных данных кажется более простым с помощью реактивной формы, я хотел бы изменить эту конкретную часть входных данных на реактивную форму.
Так можно ли смешивать реактивные формы и шаблоны в одном теге формы?
3 ответа
Вы можете смешивать как реактивные формы, так и формы на основе шаблонов, но это настоятельно не рекомендуется. Это потому, что с помощью ngModel
на реактивных формах идет вразрез с идеей неизменности формы государства.
Принципы реагирующих форм следуют "правилу одностороннего" связывания данных, согласно которому вы используете неизменный метод управления состоянием ваших форм, так что существует большая разница между логикой шаблона и компонента. Подробнее о преимуществах реактивных форм вы можете прочитать по ссылке в первом абзаце.
Предполагая, что вы продолжаете смешивать управляемые шаблоном формы и реактивные формы. Консоль выдаст следующую ошибку при запуске ng serve
:
Похоже, вы используете ngModel в том же поле формы, что и formControlName. Поддержка использования свойства ввода ngModel и события ngModelChange с директивами реагирующей формы устарела в Angular v6 и будет удалена в Angular v7. Для получения дополнительной информации об этом см. Наши документы API здесь: https://angular.io/api/forms/FormControlName
Да, вы можете использовать оба вместе, сначала создать реактивную форму, а затем добавить шаблон на основе ваших требований. Пожалуйста, ознакомьтесь с угловой документацией, как оба могут использоваться вместе.
Выдержка из ссылки, которую я разместил выше / https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
Раздел: Но что случилось с ngModel?
Обратите внимание, что ngModel все еще можно использовать с реактивными формами. Просто значение формы будет доступно в двух разных местах: модель представления и FormGroup, что потенциально может привести к некоторой путанице.
Да, вы можете, проверьте эту ссылку, это полная реактивная форма, но она близка к вашему сценарию, но вам нужно внести некоторые изменения, чтобы они соответствовали вашему случаю. В моем случае я сделал следующее:
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;
}
}
}
эта статья для реактивной формы также рядом с вашим сценарием