【发布时间】:2020-10-27 05:22:03
【问题描述】:
所以我对 observables 有点陌生,我正在为一个场景而苦苦挣扎,我认为它可能是 SO 问题的一个很好的候选者。来了……
场景是这样的:我有一个下拉字段;当它改变时,我想
- 根据字段的先前值和新值检查条件
- 如果条件通过,则向用户请求确认,然后...
- 如果用户未确认,则恢复该字段的值。
这是带有 cmets 的代码:
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);
}
问题是,当我在不发出事件的情况下恢复值时,成对的 observable 在下一次值更改时发出不正确的“先前”值。我希望有一个或两个 RxJS 操作员我失踪了,并且可以在这里完美地工作。有没有人可以分享解决这个问题的技巧?
使用工作代码更新:
首先,特别感谢Andrej's answer。他对scan 运算符的使用绝对是正确的选择。我只需要一个小修复,那就是设置crt(或下面代码中的current)值以及累加器中的prev值。瞧!这是我的最终工作版本:
/**
* 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);
}
【问题讨论】:
-
我认为这可以通过 RxJS 解决,但我认为这里可能适合更简单的解决方案。当用户更改他的值时,您不能在组件上设置一个布尔值。然后,当值 get 切换回来时,在执行任何操作之前检查布尔值。
标签: angular typescript rxjs angular-reactive-forms rxjs-observables