RxJs: How to return an Observable<T> from an Observable<T[]>? - rxjs

I'm calling a web service that returns a List. I want to return one item from that List, in a method. Essentially, when some function requests an instance of CollectorIssueBase, I want to retrieve ALL of them, cache them and return the one requested. But I can't figure out how to do it.
Here's my code:
public getByID(collectorID: string, id: string): Observable<CollectorIssueBase> {
return this.getAllMinimized(collectorID).pipe(
single(items => {
var item = items.find(i => i.ID == id);
return item;
})
);
}
The compiler keeps complaining that "Argument of type 'CollectorIssueValue[]' is not assignable to parameter of type 'CollectorIssueValue' which tells me right off the bat that I'm still returning an Array.
The value returned to the subscribing function is, indeed, an Array.
So what am I doing wrong? "single" seemed like the proper operator to use...am I using it wrong?

single callback takes CollectorIssueValue[] form each observable tick. If you want to change CollectorIssueValue[] to CollectorIssueValue i suggest use filter and map. Filter will filter empty arrays, and map will transform not empty array of CollectorIssueValue into CollectorIssueValue.
e.g.:
.pipe(
filter(arr => arr.length > 0),
map(arr => arr.find(...),

Related

Rxjs return an Observable from inner observable

I have an outer observable that i use with its result in the inner observable
and that I need to return the result from the inner observable
In the following example, I need to return the result allPersons from the second observable
the result from that function is Observable I want the it will return Observable
getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().subscribe(
(ids) => {
return this.dataService.getPersons().pipe(
map((allPersons) => {
console.log(ids);
//filter persons according to ids
return allPersons;
})
})
);
}
Also tried: and get
Argument of type 'Observable' is not assignable to parameter of type 'OperatorFunction<any, any>'.
getAllPerson(): Observable<any> {
return this. dataService.getIds().pipe(
switchMap((data) => {
this.dataService.getPersons().subscribe(
(allPersons) => {
console.log(ids);
//filter persons according to ids
return allPersons;
})
})
);
}
It's going to be somthing like that :
function getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().pipe(
switchMap((ids) => {
return this.dataService.getPersons().pipe(
map((allPersons) => {
return allPersons.filter(...); //filter persons according to ids
})
);
})
);
}
And subscribe the whole thing.
Okay, I see you're using TypeScript (nice), but there's a type error in the first two lines of your code.
getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().subscribe( /*more code here */ );
The type check is going to look at this is complain. It will say something like, "Denotationally, you've declared that this function returns an Observable object. By inference, I can see you're returning a Subscription object. As far as I can tell, Observable objects and subscription objects cannot be unified. This is a type error.
It's right.
The issue is that once you subscribe to an observable, you're no longer in RxJS land. You're left with an imperative way to end an observable, but you're done dealing with observable.
Think about subscribing as your way to exist the RxJS library. So if operators like of, from, fromEvent, new Subject, new BehaviorSubject, ect are ways to enter the RxJS library, then subscribe, lastValueFrom, firstValueFrom, behaviorSub.value, etc are ways to exit the RxJS library.
So how to avoid that dreaded subscribe. This is where RxJS hiher order operators come in. They let you chain, combine, merge, etc streams
for Example:
function getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().pipe(
switchMap(ids => this.dataService.getPersons().pipe(
map(allPersons => allPersons.filter(/* filter code ...*/))
))
);
}

Angular 11 finalize is not called when the subject type value emitted is null and not processed in switchmap

I have a subject which emits a string value and the code is as below: when the components get initialized, the subjectTypeSubject is null. But there is another method in a component get subscribed to this observable where i set isLoading to true. Because the finalize is not getting called, the loading is always set to true. How to make it work so it gets completed when the value is null as well.
private subjectTypeSubject = new BehaviorSubject<string>(null);
private getPage() {
this.subjectTypeSubject.pipe(
filter((selectedSubjectType) => {
console.log('subject type', selectedSubjectType); //first time it comes as null. so it wont go inside switchmap.
return selectedSubjectType && selectedSubjectType !== '';
}),
switchMap((selectedSubjectType) => {
return this.customListsService
.getCustomListItemsByTypeName()
}),
map((customItemsData) => {
return customItemsData
})
);
}
private _getPage(pageNumber: number, search: string) {
this.loading = true;
this._pageSubscription = this.getPage({
pageSize: this._config.pageSize,
pageNumber,
search
})
.pipe(finalize(() => (this.loading = false))) //this is not called
.subscribe((p) => {
this._currentPage = p.pageNumber;
this.options = p.options;
this._allLoaded = p.isLast;
this.loading = false;
});
}
Adding a takeWhile() instead of filter worked for me. If there is any other better solution. please let me know. thanks
BehaviorSubject doesn't complete unless you complete it
There are multiple ways to call complete an observable in a pipe. take, takeWhile and takeUntil are some of them. Calling .complete on the BehaviorSubject is also an option.
But you should ask yourself: is this really what you want to achieve here? After completion it's not possible to pass any data to the subscription, even if the initial BehaviorSubject emits a new value.
One thing that this strange about your code: it should not work at all. In getPage() you are creating a new observable (by piping the BehaviorSubject), but you are not returning it. Therefore it should return undefined. It‘s also a little bit odd that you are using pipe in a function call. You should either declare the pipe during initialization or directly subscribe to a newly created observable.

RxJS test equality of two streams regardless of order

RxJS provides the sequenceEqual operator to compare two streams in order. How would one go about testing equality of two streams regardless of order?
Pseudocode:
//how do we implement sequenceEqualUnordered?
from([1,2,3]).pipe(sequenceEqualUnordered(from([3,2,1]))).subscribe((eq) =>
console.log("Eq should be true because both observables contain the same values")
)
In my particular use case I need to wait until a certain set of values has been emitted or error but I don't care what order they're emitted in. I just care that each value of interest is emitted once.
Here's my solution:
import { Observable, OperatorFunction, Subscription } from 'rxjs';
export function sequenceEqualUnordered<T>(compareTo: Observable<T>, comparator?: (a: T, b: T) => number): OperatorFunction<T, boolean> {
return (source: Observable<T>) => new Observable<boolean>(observer => {
const sourceValues: T[] = [];
const destinationValues: T[] = [];
let sourceCompleted = false;
let destinationCompleted = false;
function onComplete() {
if (sourceCompleted && destinationCompleted) {
if (sourceValues.length !== destinationValues.length) {
emit(false);
return;
}
sourceValues.sort(comparator);
destinationValues.sort(comparator);
emit(JSON.stringify(sourceValues) === JSON.stringify(destinationValues));
}
}
function emit(value: boolean) {
observer.next(value);
observer.complete();
}
const subscriptions = new Subscription();
subscriptions.add(source.subscribe({
next: next => sourceValues.push(next),
error: error => observer.error(error),
complete: () => {
sourceCompleted = true;
onComplete();
}
}));
subscriptions.add(compareTo.subscribe({
next: next => destinationValues.push(next),
error: error => observer.error(error),
complete: () => {
destinationCompleted = true;
onComplete();
}
}));
return () => subscriptions.unsubscribe();
});
}
As many of RxJS operators have some input parameters and as all of them return functions, sequenceEqualUnordered also has some input parameter (mostly the same as Rx's sequenceEqual operator) and it returns a function. And this returned function has the Observable<T> as the source type, and has Observable<boolean> as the return type.
Creating a new Observable that will emit boolean values is exactly what you need. You'd basically want to collect all the values from both source and compareTo Observables (and store them to sourceValues and destinationValues arrays). To do this, you need to subscribe to both source and compareTo Observables. But, to be able to handle subscriptions, a subscriptions object has to be created. When creating a new subscriptions to source and compareTo, just add those subscriptions to subscriptions object.
When subscribing to any of them, collect emitted values to appropriate sourceValues or destinationValues arrays in next handlers. Should any errors happen, propagate them to the observer in error handlers. And in complete handlers, set the appropriate sourceCompleted or destinationCompleted flags to indicate which Observable has completed.
Then, in onComplete check if both of them have completed, and if they all are, compare the emitted values and emit appropriate boolean value. If sourceValues and destinationValues arrays don't have the same lengths, they can't equal the same, so emit false. After that, basically sort the arrays and then compare the two.
When emitting, emit both the value and complete notification.
Also, the return value of function passed to the new Observable<boolean> should be the unsubscribe function. Basically, when someone unsubscribes from new Observable<boolean>, it should also unsubscribe from both source and compareTo Observables and this is done by calling () => subscriptions.unsubscribe(). subscriptions.unsubscribe() will unsubscribe from all subscriptions that are added to it.
TBH, I haven't wrote any tests for this operator, so I'm not entirely sure that I have covered all edge cases.
My first idea. Use toArray on both then zip them together finally sort and compare results?

Should I unsubscribe from this Observable?

Say I have the following method used to retreive all the "year" from an array of object and then get the distinct value:
public getYears(): Observable<number[]>
{
return Observable.from(this.payments.map(p => p.year)).distinct().toArray();
}
And elsewhere in my code, I use it like this:
this.getYears().subscribe(years => this.yearsRefinement = years);
Is it enough to do this or is it better to do:
let sub = this.getYears().subscribe(years => this.yearsRefinement = years);
sub.unsubscribe();
I really struggle knowing when I have to unsubcribe or not from an observable. From what I understand, this observable is "finite", so once I get the value, it's completed, so it's not required to unsubscribe from it. Is it correct?

How do I delete an element returned from the each enumerator?

I have an array containing objects. Each object has an id field. I want to implement a function that deletes an object by specifying the id. I use .each from prototypejs on the array to step through the objects and check the id. If it matches, how do I actually delete it? I've tried setting the object returned from .each to null, but using FireBug I see that the object is still the same in the array.
EDIT: the objects in the array may in turn contain arrays with objects that may need to be deleted. My function is fine for finding the object to be removed, and I use splice to remove it (using a counter). It seems to me that .each (and the other enumerators like .reject) returns a copy of the object. If I set the object to null then upon inspection the object is still in the array. How would I return a reference of the object that when set to null would actually operate on the object in the array, and not a copy?
Here is the function, the deleteChild function works on the same principal:
function removeControl(controlName) {
var counter = 0;
cont.each(function (existingControl) {
if (existingControl.id == controlName) {
existingControl.destroy();
cont.splice(counter, 1);
}
else { // not found, check control's children
existingControl.deleteChild(controlName);
}
counter++;
}, this);
}
Only use .each when you want to do something to every object. Semantically speaking, you should be using Enumerable.reject in this situation. Think how much easier it will be to understand when you have to fix it in years' time.
function deleteById(objects, id) {
return objects.reject(function(obj) {
return obj.id == id;
}).each(function(obj) {
obj.deleteChild(id);
});
}

Resources