Angular 5儿童发出事件导致儿童形式失去焦点

时间:2018-01-18 23:15:12

标签: angular angular-reactive-forms

我有一个带有表单组和多个表单控件的子组件。我正在使用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函数将不再正常工作,因为它将使用旧的子信息。我从根本上错过了一些东西吗?我是否必须立即提交整个子表单,然后重点无关紧要?

1 个答案:

答案 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>

希望这足以让有相同问题的人朝着正确的方向前进。