我有一个带有表单组和多个表单控件的子组件。我正在使用valueChanges函数来控制控件更改时发生的事情:
this.proposalItemForm.get('qtyControl').valueChanges.forEach(
() => this.changeItem(this.proposalItemForm.get('qtyControl'))
);
以下是更改项目时发生的情况:
changeItem(control) {
// make sure the control is valid
if (control.status=="VALID") {
// first, make sure the data model matches the form model
this.proposalItem = this.prepareSaveItem();
// then submit the updated data model to the server
this.proposalItemService.update(this.proposalItem).subscribe(
data => {
this.proposalItem = data;
},
error => {
if (error.exception=="EntityNotFoundException") {
this.messagesService.error(error.message);
}
else {
this.messagesService.error("There was a problem updating the proposal item.");
console.error("Error: ", error);
}
},
() => {
this.itemTotal = this.proposalItem.quantity*this.proposalItem.priceEach;
this.updateTotal.emit(this.proposalItem);
}
);
}
}
服务更新完成后会出现问题。如果我删除行this.updateTotal.emit(this.proposalItem);
,则会遵循表单的Tab键顺序,并且行为符合预期。触发的事件会导致在父组件中重新评估某些数字。
(这是设置提案。提案可能有多个项目。当一个项目的数量或价格发生变化时,它应该更改该项目的总数,然后更改所有项目的总计。这是盛大的在发出事件时更新的总数。)
但是,当我离开该行以发出事件时,焦点将完全移出表单,因此在表单中移动的用户必须单击返回到表单才能继续。实际上,我可以看到,在短时间内,焦点转到选项卡索引中的下一个项目,但是当更新完成时它会丢失。
如何发布事件,但仍然将焦点保持在表单所属的位置?
修改/更新:
我现在明白为什么这种情况正在发生,但我还不知道如何解决它。发生此问题的原因是proposalItem
对象传递给父对象。这就是父母所发生的事情:
updateItem(item) {
console.log(item);
let itemIndex = this.proposalRevision.items.findIndex(it => it.proposalRevisionItemID == item.proposalRevisionItemID);
this.proposalRevision.items[itemIndex] = item;
this.updateItemTotal();
}
updateItemTotal() {
this.grandTotal = this.proposalRevision.items.reduce((sum, item) => sum + (item.quantity*item.priceEach), 0);
}
在updateItem
函数中,在项目数组中,使用新项目更新父项。该项目通过以下方式绑定到父项中的模板:
<proposal-item *ngFor="let item of proposalRevision.items" ...></proposal-item>
因此,当更新proposalRevision.items时,将刷新提案项(子项),从而失去焦点。
处理此问题的最佳方法是不使用修订后的子级更新父级吗?如果我这样做,那么如果更新父对象中的控件,它会将具有父对象的旧版本的子对象发送到服务器。服务器仍然得到孩子的新版本,所以它可以正常工作,但是让孩子的不同版本闲逛似乎有点奇怪。此外,updateItemTotal
函数将不再正常工作,因为它将使用旧的子信息。我从根本上错过了一些东西吗?我是否必须立即提交整个子表单,然后重点无关紧要?
答案 0 :(得分:0)
正确处理此问题的一种方法是拥有一个整体FormGroup,然后使用FormArray和更多FormGroup(s)。然后只需要为整个表单提供一个“提交”按钮,因此在每个控件填满后都不会发生任何事情。这种方法的优点在于您不再需要发出输出事件,因为所有内容都是同一表单的一部分。就我而言,我有一份提案表格。提案可以有一个或多个修订。每个修订版可以有一个或多个项目。以下是我在Proposal组件中设置Proposal表单的方法:
constructor(
private fb: FormBuilder,
private proposalService: ProposalService,
private proposalRevisionService: ProposalRevisionService,
private messagesService: MessagesService,
public dialog: MatDialog) {
this.createForm();
}
// this just creates an empty form, and it is populated in ngOnChanges
createForm() {
this.proposalForm = this.fb.group({
IDControl: this.proposal.proposalID,
propTimestampControl: this.proposal.proposalTimeStamp,
notesControl: this.proposal.proposalNotes,
estimatedOrderControl: this.proposal.estimatedOrderDate,
nextContactControl: this.proposal.nextContactDate,
statusControl: this.proposal.proposalStatus,
revisionsControl: this.fb.array([])
});
}
ngOnChanges() {
this.proposalForm.reset({
IDControl: this.proposal.proposalID,
propTimestampControl: this.proposal.proposalTimeStamp,
notesControl: this.proposal.proposalNotes,
estimatedOrderControl: this.proposal.estimatedOrderDate,
nextContactControl: this.proposal.nextContactDate,
statusControl: this.proposal.proposalStatus
});
this.setProposalRevisions(this.proposal.revisions);
}
setProposalRevisions(revisions: ProposalRevision[]) {
const revisionFormGroups = revisions.map(revision => this.fb.group({
proposalRevisionIDControl: revision.proposalRevisionID,
revisionIDControl: revision.revisionID,
revDateControl: {value: moment(revision.revTimeStamp).format("D-MMM-Y"), disabled: true},
subjectControl: [revision.subject, { updateOn: "blur", validators: [Validators.required] }],
notesControl: [revision.notes, { updateOn: "blur" }],
employeeControl: {value: revision.employee.initials, disabled: true},
printTimeStampControl: [revision.printTimeStamp, { updateOn: "blur" }],
deliveryContract: this.fb.group({
deliveryTime: [revision.deliveryContract.deliveryTime, { updateOn: "blur", validators: [Validators.required, Validators.pattern("^[0-9]*")] }],
deliveryUnit: [revision.deliveryContract.deliveryUnit, { validators: [Validators.required] }],
deliveryClause: [revision.deliveryContract.deliveryClause, { validators: [Validators.required] }]
}),
shippingContract: this.fb.group({
shippingTerms: revision.shippingContract.shippingTerms,
shippingTermsText: [revision.shippingContract.shippingTermsText, { updateOn: "blur" }]
}),
paymentContract: this.fb.group({
paymentTerms: [revision.paymentContract.paymentTerms, { updateOn: "blur" }],
paymentValue: [revision.paymentContract.paymentValue, { updateOn: "blur" }],
paymentUnit: revision.paymentContract.paymentUnit
}),
durationContract: this.fb.group({
durationValue: [revision.durationContract.durationValue, { updateOn: "blur" }],
durationUnit: revision.durationContract.durationUnit
}),
itemsControl: this.setProposalItems(revision.items)
}));
const revisionFormArray = this.fb.array(revisionFormGroups);
this.proposalForm.setControl('revisionsControl', revisionFormArray);
}
setProposalItems(items: ProposalItem[]): FormArray {
const itemFormGroups = items.map(item => this.fb.group({
proposalRevisionItemIDControl: item.proposalRevisionItemID,
itemIDControl: item.itemID,
descriptionControl: [item.itemText, { validators: [Validators.required] }],
qtyControl: [item.quantity, { validators: [Validators.required, Validators.pattern("^[0-9]*")] }],
// The price can have any number of digits to the left of a decimal point, but a decimal point does not have to be present.
// If a decimal point is present, then there must be 2-4 numbers after the decimal point.
priceEachControl: [item.priceEach, { validators: [Validators.required, Validators.pattern("^[0-9]*(?:[.][0-9]{2,4})?$")] }],
deliveryControl: this.fb.group({
deliveryTypeControl: item.delivery.deliveryType,
deliveryContractGroup: this.fb.group({
deliveryTime: (item.delivery.deliveryContract==null ? 0 : item.delivery.deliveryContract.deliveryTime),
deliveryUnit: (item.delivery.deliveryContract==null ? null : item.delivery.deliveryContract.deliveryUnit),
deliveryClause: (item.delivery.deliveryContract==null ? null : item.delivery.deliveryContract.deliveryClause)
})
}),
likelihoodControl: [item.likelihoodOfSale, { validators: [Validators.min(0), Validators.max(100)] }],
mnfgTimeControl: item.mnfgTime
}));
return this.fb.array(itemFormGroups);
}
然后在Proposal组件模板中:
<form [formGroup]="proposalForm" autocomplete="off" novalidate>
...
<div fxLayout="row" fxLayoutAlign="start center">
<p class="h7">Revisions</p>
<button mat-icon-button matTooltip="Add New Revision (The currently selected tab will be copied)" (click)="addRevision()"><i class="fa fa-2x fa-plus-circle"></i></button>
</div>
<mat-tab-group #revTabGroup class="tab-group" [(selectedIndex)]="selectedTab" formArrayName="revisionsControl">
<mat-tab *ngFor="let rev of revisionsControl.controls; let last=last; let first=first; let i=index" [formGroupName]="i" [label]="rev.get('revisionIDControl').value">
<ng-template mat-tab-label>
<button mat-icon-button>{{rev.get('revisionIDControl').value}}</button>
<button *ngIf="last && !first" mat-icon-button matTooltip="Delete Revision {{rev.get('revisionIDControl').value}}" (click)="deleteRevision(rev)"><i class="fa fa-trash"></i></button>
</ng-template>
<proposal-revision [proposalRevisionForm]="rev" [proposalMetadata]="proposalMetadata"></proposal-revision>
</mat-tab>
</mat-tab-group>
</form>
在ProposalRevisionComponent中,FormGroup现在是一个输入而不是数据模型:
grandTotal: number = 0;
@Input()
proposalMetadata: ProposalMetadata;
@Input()
proposalRevisionForm: FormGroup;
constructor(private fb: FormBuilder, private proposalRevisionService: ProposalRevisionService, private proposalItemService: ProposalItemService,
private messagesService: MessagesService, public dialog: MatDialog) { }
ngOnInit() {
this.dataChangeListeners();
//console.log(this.proposalRevisionForm);
}
dataChangeListeners() {
this.proposalRevisionForm.get('itemsControl').valueChanges.forEach(() =>
this.grandTotal = this.proposalRevisionForm.controls.itemsControl.controls.reduce((sum, control) => sum + (control.controls.qtyControl.value*control.controls.priceEachControl.value), 0)
);
}
然后,监听项目中的更改并更新修订的总计变得非常简单。至于更新每个单独的项目总数,在ProposalItemComponent模板中,我刚刚使用:
<span>Total: {{(proposalItemForm.controls.qtyControl.value*proposalItemForm.controls.priceEachControl.value) | currency:'USD':'symbol':'1.2-2'}}</span>
这是可能的,因为FormGroup再次作为ProposalItemComponent的输入传入。以下是ProposalRevisionComponent模板的一部分:
<div [sortablejs]="proposalRevisionForm.controls.itemsControl" [sortablejsOptions]="sortEventOptions" formArrayName="itemsControl">
<proposal-item *ngFor="let item of proposalRevisionForm.controls.itemsControl.controls; let i=index" [formGroupName]="i"
[proposalItemForm]="item"
[proposalMetadata]="proposalMetadata">
</proposal-item>
</div>
希望这足以让有相同问题的人朝着正确的方向前进。