Reputation: 4883
Here is what I am trying to do: I have a list of users coming from an async data source. The source returns an Observable<User[]>
. The list is then displayed in the template via Angular's async
pipe like this.
<ng-template #loading>Loading...</ng-template>
<ul *ngIf="users$ | async; let users; else loading">
<li *ngFor="let user of users">{{user.name}}</li>
</ul>
The list of users is searchable. Using reactive forms, I have bound the search field's change event like this (with this.resolve
performing the async task, having the signature (text: string) => Observable<User[]>
):
search = new FormControl('');
users$ = this.search.valueChanges
.pipe(startWith(this.search.value))
.pipe(debounceTime(250))
.pipe(distinctUntilChanged())
.pipe(switchMap(this.resolve));
This works perfectly fine. The search input is debounced and a new async request to resolve the users is fired when the search input changes.
Now I have a problem. Users may change on the client side and need to be updated in the list without performing the async task again. My attempt to use another pipe operation in the update function results in this.resolve
being executed again.
update(user: User): void {
this.users$ = this.users$
.pipe(map(users => {
// replace user in users
return users;
}));
}
However, I do not want to resolve the users again. I just want to locally update the async user list. How can I do this?
Upvotes: 0
Views: 1158
Reputation: 8314
Adding on to martin's merge()
suggestion, I would also "share" the users result to prevent the resolve
from firing again.
RXJS will run through all the pipes every time a subscription is added. Think of them as transformers where the "datastore" is the initial Subject
(this.search.valueChanges
). So every time you subscribe, RXJS is going to run through your pipes to get the transformed value.
So you want to look at RXJS share()
or shareReplay(1)
. What these will do is internally create a new Subject()
(in the case of share()
) or a new ReplaySubject(1)
(in the case of shareReplay(1)
) to hold your transformed value in a new "datastore", if you will.
search = new FormControl('');
otherUsers$ = new Subject();
users$ = merge(
otherUsers$,
this.search.valueChanges.pipe(
startWith(this.search.value)),
debounceTime(250)),
distinctUntilChanged()),
switchMap(this.resolve))
)
).pipe(share());
Subject
vs ReplaySubject
depends on whether you want to hold a value even when there is no subscription. (This is my interpretation based on my own usage.) I found shareReplay(1)
useful in cases where I have subscriptions like obs.pipe(first()).subscribe(...)
. However one of your subscriptions is the AsyncPipe
in the DOM, so you're probably fine with share()
, but try it out yourself.
Upvotes: 1
Reputation: 96909
You can use Subject
and merge it into the users$
chain after switchMap
.
otherUsers$ = new Subject();
users$ = this.search.valueChanges
.pipe(
startWith(this.search.value),
debounceTime(250)),
distinctUntilChanged()),
switchMap(this.resolve),
merge(otherUsers$),
);
Then you can call next()
and avoid going through the whole chain:
otherUsers$.next([{}, {}, {}, ...]);
Upvotes: 3