RxJS-使用成对确认并还原输入字段 [英] RxJS - Using pairwise to confirm and revert input field
问题描述
因此,我对可观测对象还有些陌生,我正在为一个场景而苦苦挣扎,我认为这可能是解决SO问题的理想人选.我们去...
So I'm a little new to observables, I'm struggling with a scenario, and I thought it might be a good candidate for an SO question. Here we go...
情况是这样的:我有一个下拉字段;更改后,我想
The scenario is this: I have a drop-down field; when it is changed, I want to
- 根据字段的上一个值和新值检查条件
- 要求用户确认条件是否通过,然后...
- 然后,如果用户未确认,则还原该字段的值.
以下是带有注释的代码:
Here's the code with comments:
ngOnInit(): void {
// I am waiting for my view-model to load, then initializing my FormGroup using that view model data.
// NOTE: My view model is for "Contact" (this code is for contact list)
this.addSubcription(this.vm$.subscribe((vm) => this.initFormGroup(vm)));
const field:string = 'customerEmployerId'; // the field's name
// I create the observable that should listen to changes in the field, and return them in pairs
const employerValueChanges$ = this.formInit$.pipe(
switchMap(form=> form.get(field).valueChanges.pipe(
startWith(form.get(field).value)
)),
pairwise()
);
// I combine the changes observable with my other observables to access data from each
let employerCheckSub = combineLatest([
employerValueChanges$, // the value-changes obs
this.vm$, // the view-model data
this.customers$ // a list of customers from a CustomerService
]).subscribe(
([
[oldid,newid], // values from value-changes obs
contact, // the contact info / data
customers // the list of customers
])=> {
// check the previously and newly selected employer values
// request confirmation if contact was listed as the primary contact for the previously selected employer
if(oldid > 0 && newid !== oldid){
const employer = customers.find(c=> c.customerId === oldid && c.contactId === contact.contactId);
if(employer === null) return;
if(!confirm('Warning: changing this contact\'s employer will also remove them '+
'as the primary contact for that customer. Are you should you want to continue?')){
// user clicked cancel, so revert back to the previous value without emitting event
this.contactEditForm.get(field).setValue(oldid, {emitEvent:false});
}
}
});
this.addSubcription(employerCheckSub);
}
问题是当我在不发出事件的情况下还原该值时,成对的可观察对象将发出不正确的上一个"消息.下一个值更改时的值.我的希望是我缺少一个或两个RxJS运算符,并且可以在这里完美运行.有没有人有办法解决可以共享的问题?
The problem is that when I revert the value without emitting an event, the pairwise observable emits an incorrect "previous" value on the next value change. My hope is that there is an RxJS operator or two out there that I am missing and would work perfectly here. Does anyone have a trick to resolve this that they can share?
首先,特别感谢 Andrej的回答.他对 scan
运算符的使用绝对是正确的选择.我只需要解决一个小问题,那就是同时设置 crt
(或下面代码中的 current
)值和 prev
累加器中的值.瞧!这是我的最终工作版本:
First off, special thanks to Andrej's answer. His use of the scan
operator was definitely the way to go. There was only one little fix that I needed which was to also set the crt
(or current
in the code below) value as well as the prev
values in the accumulator. And voila! Here is my final working version:
/**
* Requests confirmation when attempting to change a contact's employer if that contact is also
* designated as the employer's primary contact.
*/
private addEmployerChangeConfirmation() {
// NOTE: In this scenario, "customers" are synonymous with "employers"; i.e., our customers are employers of these contacts.
const field: string = 'customerEmployerId'; // the field's name
const valueChanges$ = this.formInit$.pipe(
switchMap((form) => form.get(field).valueChanges)
);
let employerCheckSub = combineLatest([
// the value-changes obs
valueChanges$,
// the id needed from the view model
this.vm$.pipe(
filter((vm) => vm !== null),
map((vm) => vm.contactId)
),
// the customer/employer list
this.customers$,
])
.pipe(
// once the user approves, I don't bother re-confirming if they change back in same session
// NOTE: I use a "$$" naming convention to indicate internal subjects that lack a corresponding public-facing observable.
takeUntil(this.employerChangeApproved$$),
scan(
(acc, [current, contactId, customers], i) => ({
prevOfPrev: acc.prev,
///////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE: This was an interesting issue. Apparently the seed value is resolved immediately.
// So, there is no way I found to seed a value from another obs.
// Instead, I just check if this is the first run, and if so I use the resolved data for prev value.
// I know the data is resolved because an upstream obs provides it.
///////////////////////////////////////////////////////////////////////////////////////////////////
prev: i === 0 ? this.contactData.customerEmployerId : acc.current, // <-- setting seed manually on first emission
current,
contactId,
customers,
}),
{
prevOfPrev: null,
prev: null,
current: this.contactData?.customerEmployerId,
contactId: this.contactData?.contactId,
customers: [],
}
),
// only continue if condition passes
filter((data) =>
this.checkIfChangeWillRemoveAsPrimaryContact(
data.prev,
data.current,
data.contactId,
data.customers
)
),
// we only want to revert if user clicks cancel on confirmation box.
// NOTE: If they approve change, this also triggers the "employerChangeApproved$$" subject.
filter((data) => !this.confirmRemoveAsPrimaryContact())
)
// and now we actually subscribe to perform the action
.subscribe((data) => {
data.current = data.prev;
data.prev = data.prevOfPrev;
this.contactEditForm
.get(field)
.setValue(data.current, { emitEvent: false });
});
this.addSubcription(employerCheckSub);
}
推荐答案
这是我的方法:
form.valuesChanges.pipe(
scan(
(acc, item) => ({
// Needed in case we need to revert
prevOfPrev: acc[prev],
prev: acc[crt],
crt: item,
}),
{ prevOfPrev: null, prev: null, crt: null }
),
// 'check a condition based on the previous and new values of the field'
filter(v => condition(v.prev, v.crt)),
// 'request from the user a confirmation if the condition passes'
switchMap(
v => confirmationFromUser().pipe(
// 'then revert the value of the field if the user did not confirm'
tap(confirmed => !confirmed && (v[prev] = v[prevOfPrev])),
)
),
// Go further only if the user confirmed
filter(v => !!v),
)
这篇关于RxJS-使用成对确认并还原输入字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!