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?
Related
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 ...*/))
))
);
}
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.
I'm creating an observable that needs to perform a few steps, each step is reliant on the previous - for example something like get an ID, use that ID to get a user, use that user to get an article. Each step makes a call to the database, which in turn returns its own observable. I'm really not sure how to deal with nested observables without subscribing to them, or ensure that when the outer observable is subscribed to eventually all the nested observables are subscribed to as well.
Example:
newArticle(article: Article): Observable<any> {
// db api returns an observable of requests
return this.db.get(`select id from user_ids where name = ${article.name}`).pipe(map((id) => {
return this.db.get(`select user from users where user_id = ${id}`).pipe(map((user) => {
return this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content});
}));
}));
}
Using this current method doesn't work, when the observable returned from newArticle is subscribed to only the outermost observable is subscribed and executed it seems. Is there something I'm missing in the way to deal with nested observables? I'm relatively new to rxjs and having a hard time completely grasping observables. Any help would be greatly appreciated.
In RxJS, the solution to avoid nested subscriptions is to use a "Higher Order Mapping Operator" (switchMap, mergeMap, concatMap, exhaustMap). These operators will subscribe to an "inner observable" for you and emit its emissions.
I won't go into the differences between these operators here, but switchMap will work for your case.
Your sample code is very close; you can basically use switchMap instead of map.
function newArticle(article: Article) {
return this.db.get(`select id from user_ids where name = ${article.name}`).pipe(
switchMap(id => this.db.get(`select user from users where user_id = ${id}`)),
switchMap(user => this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content}`))
);
}
You may find the code easier to follow if you define separate functions with specific purpose:
function getUserIdForArticle(article: Article): Observable<string> {
return this.db.get(`select id from user_ids where name = ${article.name}`);
}
function getUser(id: string): Observable<User> {
return this.db.get(`select user from users where user_id = ${id}`);
}
function storeArticle(user: User, article: Article): Observable<Article> {
return this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content}`);
}
function newArticle(article: Article) {
return getUserIdForArticle(article).pipe(
switchMap(id => getUser(id)),
switchMap(user => storeArticle(user, article))
);
}
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(...),
I need to create an observable, which I can "pull" data from, to work with a pageable api. I can only fetch 100 items per request, I want to be able to use observable as a generator function (on which I can call .next() to issue a request to get next 100 items.
I can't unfortunately find a way to do it with Rx. I suppose it's possible using controlled observable or a subject. Can you guys show me an example.
this is what I've gotten so far:
function list(entityType, viewName, fetchAll = false) {
var skip = 0,
total = 0;
const subject = new Rx.Subject(),
response$ = subject
.takeWhile(() => skip <= total)
.startWith(skip)
.flatMap((skip) => fetchPagePromise(skip)),
next = () => subject.onNext(skip);
if (fetchAll) {
Rx.Observable.timer(100, 100).subscribe(() => next());
}
return {
data$: response$.map(response => response),
next: fetchAll === true ? undefined : next
};
function fetchPagePromise() {
let limit = 100,
obj = {
viewName, limit, skip
},
qs = objectToQueryString(obj);
return $http.get(`${apiBase}/api/data/${entityType}${qs}`).then((res) => {
total = res.data.Total;
skip += limit;
return res.data.Rows;
});
}
}
this kinda works like a generator. it returns an Observable and next handler. Whenever next is called it pulls next 100 items from api and pushes into the Observable. Also if there’s a third parameter fetchAll passed, then it will keep fetching data until there’s no more. What scares me though that there are 2 mutating vars in function's closure - skip and total, and I don't know if managing them like this in asynchronous/unpredictable environment is ok.
One of the things you generally want to avoid is trying to make Rx into a plain old event emitter. Usually it is an indicator when you try and just trigger Observables manually by passing around a Subjects observer interface.
You should ask yourself, where is my data coming from? What calls next(), what calls that, etc. After enough of these you will generally find that this will lead you to something that can be wrapped by an Observable directly rather than explicitly calling next(). Also, I think the fetchAll flag should really be kept externally. You are only making the interface confusing by essentially turning it into a void method just by passing in a flag.
So I would recommend refactoring like so:
Rx.Observable.prototype.lazyRequest = function(entityType, viewName, limit = 100) {
var source = this;
return Rx.Observable.create(obs => {
var response = source
//Skip is really just the (limit * index)
.map((x, i) => i * limit)
.flatMap((skip) => {
let obj = {viewName, skip, limit},
qs = objectToQueryString(obj);
//Handle promises implicitly
return $http.get(`${apiBase}/api/data/${entityType}${qs}`);
},
//Return this with our skip information
(skip, res) => {skip, res})
//Publish it so the stream get shared.
.publish();
//This will emit once once you are out of data
var stop = response.first(x => x.skip >= x.res.data.Total);
return new CompositeDisposable(
//Complete this stream when stop emits
response.takeUntil(stop)
//Downstream only cares about the data rows
.map(x => x.res.data.Rows)
.subscribe(obs),
//Hook everything up
response.connect());
});
}
Then you can use it like so:
//An example of a "starting point", a button click
//Update the rows every time a new event comes through
Rx.Observable.fromEvent($button, 'click')
.startWith(0) //Inject some data into the pipeline
.lazyRequest(entityType, viewName)
.subscribe(/*Do something with the returned rows*/);
//Get all of the rows, will keep hitting the endpoint until it completes
Rx.Observable.interval(100)
.lazyRequest(entityType, viewName)
//Gather all the values into an array and emit that.
.toArray()
.subscribe();