Reputation: 4546
This is actually for angular app.
I have a filter form fields which emit values whenever user changes their values (typing, for example). This is the first Observable.
this.filterFrm.valueChanges.pipe(...);
And I have another Observable which just gives me the current form validity value:
const formIdValid = defer(() => of(this.filterFrm.valid));
How can I combine these two so when form values change I always get the current form.valid
value?
Here's what I tried:
this.filterFrm.valueChanges
.pipe(
concatMap((frmValue) => formIdValid.pipe(map((isValid) => ({ isValid, frmValue })))),
filter(({ isValid, frmValue }) => isValid),
takeUntil(this.destroy)
)
.subscribe((filterCriteria) => this.filterChanged.emit(filterCriteria));
But it only emits single value for some reason.
Upvotes: 1
Views: 701
Reputation: 4546
Right after I posted the question, I made it work!
this.filterFrm.valueChanges
.pipe(
debounceTime(400),
distinctUntilChanged(),
concatMap((frmValue) => formIdValid.pipe(map((isValid) => ({ isValid, frmValue })))),
tap(console.dir),
filter(({ isValid, frmValue }) => isValid),
map(({ isValid, frmValue }) => ({
searchPhrase: frmValue.filterFld,
startDate: frmValue.startDateFld ? new Date(frmValue.startDateFld) : undefined,
endDate: frmValue.endDateFld ? new Date(frmValue.endDateFld) : undefined
})),
takeUntil(this.destroy)
)
.subscribe((filterCriteria) => this.filterChanged.emit(filterCriteria));
Yes, in this particular case defer()
is not required. But it was a nice learning experiment anyway.
this.filterFrm.valueChanges
.pipe(
debounceTime(400),
distinctUntilChanged(),
filter(() => this.filterFrm.valid),
map((frmValue) => ({
searchPhrase: frmValue.filterFld,
startDate: frmValue.startDateFld ? new Date(frmValue.startDateFld) : undefined,
endDate: frmValue.endDateFld ? new Date(frmValue.endDateFld) : undefined
})),
takeUntil(this.destroy)
)
.subscribe((filterCriteria) => this.filterChanged.emit(filterCriteria));
Upvotes: 2
Reputation: 8062
I cant really think of any situations in which you'd want an observable like this:
const formIdValid = defer(() => of(this.filterFrm.valid));
It's basically just using a bit of extra computing power to read this.filterFrm.valid
wherever you subscribe.
For example, your solution can be re-written like this without any issue:
this.filterFrm.valueChanges.pipe(
debounceTime(400),
distinctUntilChanged(),
map(frmValue => ({
frmValue,
isValid: this.filterFrm.valid,
})),
tap(console.dir),
filter(({ isValid }) => isValid),
map(({ frmValue }) => ({
searchPhrase: frmValue.filterFld,
startDate: frmValue.startDateFld ? new Date(frmValue.startDateFld) : undefined,
endDate: frmValue.endDateFld ? new Date(frmValue.endDateFld) : undefined
})),
takeUntil(this.destroy)
).subscribe(
filterCriteria => this.filterChanged.emit(filterCriteria)
);
Or if you don't care about your console.dir
logging isValid, then this is also the same:
this.filterFrm.valueChanges.pipe(
debounceTime(400),
distinctUntilChanged(),
tap(console.dir),
filter(_ => this.filterFrm.valid),
map(frmValue => ({
searchPhrase: frmValue.filterFld,
startDate: frmValue.startDateFld ? new Date(frmValue.startDateFld) : undefined,
endDate: frmValue.endDateFld ? new Date(frmValue.endDateFld) : undefined
})),
takeUntil(this.destroy)
).subscribe(
filterCriteria => this.filterChanged.emit(filterCriteria)
);
consider the following:
let num = 10;
const $1 = of(num);
const $2 = defer(()=>of(num));
const $3 = of(1).pipe(map(_ => num));
const $4 = of(1).pipe(mapTo(num));
num++;
$1.subscribe(console.log);
$2.subscribe(console.log);
$3.subscribe(console.log);
$4.subscribe(console.log);
If you run this, what do you expect as output? What you need to understand to answer this properly is when the value of num
is resolved. For both, $1
and $4
, this is done when the observable is created, which means that you get the first value of num
. For $2
and $3
, num
is resolved when the lambda given to defer
/map
is invoked.
defer
doesn't invoke its factory function until subscription.map
invokes its transformation function each time a new value arrives, which is some time after subscription at the earliest.Therefore, both defer
and map
end up accessing the second value of num
The Output
10
11
11
10
this.filterFrm.valid
The problem comes from when this.filterFrm.valid is resolved. A function (in this case the lambda given to filter
) doesn't access that variable until it is invoked. Filter only invokes the function when it gets a new value and so for every new value, the filter will access this.filterFrm.valid
anew.
So in this case, you're not gaining anything with defer
Upvotes: 0