I am not a rxjs expert.
I have a method which actually does a save.
this.saveProducts(this.boxes, 'BOXES')
But there are two other different type of items that needs to use the same method for saving , just the parameter is different.
this.saveProducts(this.containers, 'CONTAINER')
In my component I have few other independent saving is happening and all these should happen one by one.
So my method look like this.
return this.service.edit(materials)
.do((data) => {
this.materials= data;
})
.switchMap(() => this.customerService.addCustomer(this.id, this.customers))
.switchMap(() => this.saveProducts(this.boxes, 'BOXES'))
.switchMap(() => this.saveProducts(this.containers, 'CONTAINER'))
.switchMap(() => this.saveProducts(this.books, 'BOOKS'))
.do(() => {
.......
}
But whats happening is it never calls the second saveProducts method unless I have a return from first one
private saveProducts(products: IProduct[], type:
Type): Observable<any> {
console.log(type);
}
Thanks for anyone who looked at it..
The answer to this issue is to return an empty observable if nothing is there to save.
if (products$.length === 0) {
return Observable.of([]);
}
Thanks guys.. Happy coding..
Related
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 asked a question
Is Observable from chained promises equivalent of observables created with from and chained with concatMap?
on totally false premises. It seems that neither of my solutions had nothing to do with my intention.
I created a method that returns Observable and calls 2 methods returning Promise. I tried 2 ways:
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
return from(this.db.selectionItemInfos.clear().then(() => {
return this.db.selectionItemInfos.bulkAdd(itemInfos);
}));
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = from(this.db.selectionItemInfos.clear());
const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));
return clear$.pipe(concatMap(() => bulkAdd$))
}
the use will be:
myService.setItemInfos(itemInfos).subsribe(count => {
console.log(`Cleared the table 1st and then added ${count} new items`);
});
I thought from both versions that:
table clear is execution is finished when bulkAdd starts
when bulkAdd is finished i get the count from that in subscribe
How this should really be done? Or can it be done?
This is (from what I can tell here), how I would do it.
In general, defer (or any higher-order operator) is a better way to create an observable from a promise. Defer lets you take the eager evaluation semantics of promises and turn them into the lazy evaluation semantics of observables.
Then all the usual observable operators and such will function as expected.
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = defer(() => this.db.selectionItemInfos.clear());
const bulkAdd$ = defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos));
return concat(clear$, bulkAdd$);
}
Update 1:
So I think I might know what you're after. This isn't really idiomatic RxJS since it's such an interleaving mix of declarative, imperative style of code. Even so, this should work? I haven't tested it fully, but some tinkering and I think this should do what you're after.
There's most assuredly a better way to accomplish the same thing, but without seeing the bigger picture of what you're after, it's hard to say.
interface Tagged<T> {
payload: T,
tag: number
}
class abitraryClass{
private setItemInfoSub: Subject<Tagged<IItemInfo[]>>;
private processItemInfo: Observable<Tagged<number>>;
private itemInfoTag = 0;
constructor(){
this.setItemInfoSub = new Subject<Tagged<IItemInfo[]>>();
this.processItemInfo = this.setItemInfoSub.pipe(
concatMap(({tag, payload: itemInfos}) => this.db.selectionItemInfos.clear().pipe(
ignoreElements(),
concatWith(defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos))),
map(response => ({
payload: response,
tag
}))
)),
shareReplay(1)
);
// Make the processing pipeline live at all times.
this.processItemInfo.subscribe();
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const myTag = this.itemInfoTag++;
this.setItemInfoSub.next({
payload: itemInfos,
tag: myTag
});
return this.processItemInfo.pipe(
filter(({tag}) => tag == myTag),
map(({payload}) => payload)
);
}
}
I am new to Angular with Akita.
I have an application where the users are loaded and set in the store. all user have an initial imageUrl property set to eg: 'http://testxxxxxx'
The component query the store for all users, and pipe to calls a method which loops through each person, make an api call to get 'blob' image response from api, and update the store with the person's imageUrl = 'blob:http:2fk2fjadkf' and the component set the <img src='imageUrl'.
But for some reason the method inside the outer observable is looping for many times. not sure why.
Here is my code:
Component:
peopleToShow$ = this.peopleFacade.peopleToShow$;
Component.html uses peopleToShow$ and the imageUrl property of each person. Right now it is not taking the updated blob url which is set in the this.loadImagesAsBlobs
Facade:
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people))
);
private loadImagesAsBlobs(people: Person[]) {
people.forEach((person) => {
if (!person.isUrlChecked) {
this.imageDataService
.getAndStoreImageOnClient(person.imageUrl)
.pipe(
take(1),
switchMap((safeUrl) => {
this.updatePersonWithBlobImageUrl(person.id, safeUrl);
return EMPTY;
}),
catchError(() => {
this.updatePersonWithBlobImageUrl(person.id, null);
return EMPTY;
})
)
.subscribe();
}
});
}
private updatePersonWithBlobImageUrl(id: number, blobUrl: SafeUrl) {
this.peopleStore.updatePersonWithBlobImageUrl(id, blobUrl as string);
}
Thanks
It's not within this code, but when I've had this problem, it was because I had multiple things listening to a single observable, which means it was all happening several times.
To fix, change
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people))
);
to
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people)),
share()
);
or use shareReplay(1) instead, if you're worried about peopleToShow$ emitting before everything downstream is set up.
I am currently trying to figure out how to load an angular/fire-collection including all of its subcollections with RxJS.
This is my current approach:
return this.collectionRef.valueChanges().pipe(
flatMap((entities: Entity[]) => entity),
mergeMap((entity: Entity) => this.setSubCollection1(entity)),
mergeMap((entity: Entity) => this.setSubCollection2(entity)),
scan((entities: Entity[], entity: Entity) => entities.filter(a => a.id !== entity.id).concat(entity), [])
);
and to load the documents in their subcollections
private setSubCollection1 = (entity: Entity): Observable<Entity> => {
return this.subCollectionRef.valueChanges(actor).pipe(
map((subEntities1: SubEntity1[]) => {
entity.subEntities1 = subEntities1;
return entity;
})
);
}
It works fine when having a full stream.
But now I wanted to get all of data in one single Promise: I already tried .first().toPromise() but this only gets the first entry, and does not finish if the collection has no entries. Using reduce in the query also does not work, because valueChanges() never finishes.
Am I using the wrong operators? Or any other ideas on how to solve that?
I hope to hear from you.
I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.
Let's say I have a service with that signature:
GeoService {
getState(): Observable<string> {
return this.http.get<string>(stateURL);
}
getCities(state: string): Observable<string[]> {
return this.http.get<string[]>(citiesURL + state);
}
}
I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:
import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';
...
ngOnInit() {
this.svc.getState().
pipe(map((state) => {
this.state = state;
return this.svc.getCities(state);
}),
mergeMap((cities) => this.cities = cities))
).
subscribe(console.log('done'));
The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)
Thanks!
Edit: None of the "possible duplicate" posts contain an actual example that works
The 21st attempt would have been correct ;-)
this.svc.getState().
pipe(mergeMap((state) => {
this.state = state;
return this.svc.getCities(state);
}),
tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));
The chained Observable goes inside mergeMap. You can think of it as:
First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable
Also, use tap instead of map if you intend to change an outside state.
You were almost there:
this.svc.getState().
pipe(
mergeMap((state) => {
return this.svc.getCities(state).pipe(map(cities => {
return { state: state, cities: cities }
}));
}),
).subscribe(stateAndCities => console.log(stateAndCities));
I advise you to read this article:
https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions
It also explains why you shouldnt interact with global variables in rxjs operators.
You can do something like this
this.svc.getState().pipe(
tap(state=>this.state=state),
switchMap(this.svc.getCities))
.subscribe(cities=>{
//got the cities
})
the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.
note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)