Rxjs combinationLatest and detecting Anguar2 changes on observable arrays

I struggle a lot with some basic changes in my application. I have been doing this for several days and am losing my will! I wonder if anyone here can point me in the right direction.

I have a group with 2 user lists. One for members and one for moderators .

This method connects to the database to get a list of objects, each of which has a value of $key . Now I want to use these keys and get the actual UserData for each user. I tried everything and combineLatest is the only thing I could get to work.

This way both function calls, and I use the async tube in my template.

 members$ = myService.getMemberListByType("blah", "members"); moderators$ = myService.getMemberListByType("blah", "moderators"); 

My function looks like this.

  private getMemberListByType(groupKey: string, memberType: string) { return this.af.database.list(`groups/${groupKey}/${memberType}`) .switchMap(userListKeyMap => { console.log("UserListKeyMap", memberType, userListKeyMap); let usersObservables: Observable<UserData>[] = []; userListKeyMap.forEach((object) => { usersObservables.push(this.af.database.object(`users/${object.$key}`) .map(UserData.fromJSON)); }); return Observable.combineLatest(usersObservables); }); } 

However, Angular does not detect changes to this list as follows. Say another method elsewhere removes member and adds it to moderator . In this case, the members list emits an empty user list [] .

My * ngIf and * ngFor do not detect changes in this new empty object. As a result, the user (in the template) is now included in 2 lists, but the data in it is absolutely accurate, as highlighted by the log.

I feel that when I combine Latest with an empty array, this is the source of my problem. Nothing can emit ... so how can Angular change? I do not know how to solve this. Is there an alternative to combLatest in this "empty" use case? Since I'm using an async handset, I need something that makes sense.

Can anyone highlight my problem?

Thanks!

Update

I am convinced that the problem is that angular2 does not detect an empty observable list. In particular, when the value of the observed array matters, but later that value becomes empty. angular2 will not see an empty array. I currently have no way to solve this problem. Or is my approach wrong, or should something specific happen to register an empty observable list?

problem screen

  • Blue = Component loading status is good, the user was a member.
  • Green = User has been moved from member to manager / moderator.
  • Red = this does not exist, so why is it still displayed?
+5
source share
4 answers

There is an easy way:

  private getMemberListByType(groupKey: string, memberType: string) { return this.af.database.list(`groups/${groupKey}/${memberType}`) .switchMap(userListKeyMap => { console.log("UserListKeyMap", memberType, userListKeyMap); let usersObservables: Observable<UserData>[] = []; userListKeyMap.forEach((object) => { usersObservables.push(this.af.database.object(`users/${object.$key}`) .map(UserData.fromJSON)); }); return usersObservables.length > 0 ? Observable.combineLatest(usersObservables) : Observable.of([]); }); } 
+4
source

Your approach seems right to me. Using combineLatest() with an empty array should not combineLatest() in an error (similarly, for example, forkJoin , using an empty array is a valid use case). Then even if it fails due to an empty array, you are using switchMap , so this should not be the problem I'm thinking of.

I have no experience with AngularFire2, so I don’t know how this.af.database.object just this.af.database.object all the elements once and then completes or , it emits a value every time it changes (this is probably what you want, if I think).

In any case, a very common problem using combineLatest() or forkJoin() is that their original observers should always emit at least one element . In other words, if any of this.af.database.object empty, then combineLatest() will not emit anything.

You can make sure that there is always at least one value with startWith() . For example, for example:

 userListKeyMap.forEach((object) => { usersObservables.push(this.af.database.object(`users/${object.$key}`) .startWith('{}') .map(UserData.fromJSON)); }); 

If that doesn't help, use only Observable.timer() instead of this.af.database.object or this.af.database.list to make sure Angular2 subscribes to it and keeps the subscription even when new items come out.

+4
source

You should try something like this:

 private getMemberListByType(groupKey: string, memberType: string) { return this.af.database.list(`groups/${groupKey}/${memberType}`) // Force the source obs to complete to let .toArray() do its job. .take(1) // At this point, the obs emits a SINGLE array of ALL users. .do(userList => console.log(userList)) // This "flattens" the array of users and emits each user individually. .mergeMap(val => val) // At this point, the obs emits ONE user at a time. .do(user => console.log(user)) // Load the current user .mergeMap(user => this.af.database.object(`users/${user.$key}`)) // At this point, the obs emits ONE loaded user at a time. .do(userData => console.log(userData)) // Transform the raw user data into a User object. .map(userData => User.fromJSON(userData)) // Store all user objects into a SINGLE, final array. .toArray(); } 

Note. Lines starting with do() are only here to explain what happens. There is no need to store them in the final code.

+2
source

Try using forkJoin in your watchlist

 let userListKeyMap$ = Rx.Observable.of([{key:1}, {key:2}, {key:3}]); //mock keys let requestUserFromKey = (key) => Rx.Observable.of({user: key}).delay(1000); //mock users let result$ = userListKeyMap$.switchMap((userKeys) => Rx.Observable.forkJoin(userKeys.map(uk => requestUserFromKey(uk.key)))); result$.subscribe((users) => console.log('got some users: ', users)); 
 <script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script> 
+1
source

Source: https://habr.com/ru/post/1264337/


All Articles