Angular, RxJS - How to cancel previous requests using switchMap? - javascript

I was trying to figure out that for a long time by myself but I stuck.
My case: I have a table with data, when user enters the page the request goes to REST API. Above the table there is a form that specify/filters results of the table. When user clicks a button, new request with query parameters goes to a service.
I'm trying to find a way to stop a previous request when user clicked a button, to search a table with specific parameters
Here is what I have done so far stackblitz. Maybe my approach is wrong and someone could show me how to write it better, or maybe there are just few thing to correct.
Thank you for any help

You can use the takeUntil operator inside your pipe, and pass a subject to it, and everytime you apply a new filter you can emit something to that subject to unsubscribe from the previous subscription and subscribe again! let me give you an example on how to use this operator:
regardless if your code was working or not!! I added just the part on how to use the takeUntil operator!
...
unsubscribe = new Subject<boolean>();
constructor(private _service: ServiceConnectService) {}
ngOnInit() {
this.clickStream
.pipe(
map(() => console.log(1)),
switchMap(() => {
return this.getMethodServicesInstances();
}),
takeUntil(this.unsubscribe) // this will be executed once you call next with a value!! and the subscription will be stopped!
)
.subscribe(results => {
console.log(2);
});
}
applyFilter(){
this.unsubscribe.next(true);
/*
you can then rerun the subscription and reset the unsubscribe subject state to null!
*/
}
...

Related

RxJs: Update observables inside a subscription correctly

I wondered how to update a observable inside a subscription without triggering to many events.
In this example I subscribe to area: Observable<Area>, if the area changes I'd like to update the theme: Observable<Theme>. distinctUntilChanged() should do that the subscription is only triggered if the value has changed, but everytime the area gets updated, the amount of theme updates increases by one.
observeArea(): void {
this.area
.pipe(distinctUntilChanged())
.subscribe(area => {
this.themeService.updateTheme({primaryColor: area.color}); // should happen only once per area change
})
}
Is there any "correct" way of doing this, without triggering endless theme updates?
I think the problem might be with the way you are updating the theme.
If you are trying to update the theme by calling observeArea() method.
Every time you call the method a new subscription will be created.
Event will be passed to every subscription. So each time you call the method one subscription will be increasing.
Solution
Use an async pipe
area$!: Observable<any>;
observeArea(): void {
this.area$ = this.area
.pipe(
distinctUntilChanged(),
tap(area => this.themeService.updateTheme({primaryColor: area.color});)
)
}
and in html use area$ | async to subscribe.
example:
<ng-container *ngIf="area$ | async">...</ng-container>
Else you should unsubscribe every time your subscription completes
observeArea(): void {
const sub = this.area
.pipe(distinctUntilChanged())
.subscribe(area => {
this.themeService.updateTheme({primaryColor: area.color});
sub.unsubscribe();
})
}
Better to use async pipe.
I believe this solves your issue.

prevent user to click button until service response angular 7 rxjs

I am looking for efficient solution to avoid user to click the submit button again and again until they get response from backend API.
I have thought following solution but looking for any other ways to handle this.
use loader which will disable whole html until we get some response.
use exhaustmap to avoid multiple http calls
use throttletime which will manually not allow user to click for specific time period.
Please guide me if there is any other solutions which we can use.
I would recommend to use exhaustMap() as primary solution in this case.
In template:
<button (click)="submitForm()">Submit</button>
then in your component:
submit$: Subject<boolean> = new Subject();
constructor() {
this.submit$.pipe(
exhaustMap(() => this.http.get("api/endpoint"))
).subscribe(res => {});
}
submitForm() {
this.submit$.next(true);
}
You click 'Submit' and all proceeding click events will be ignored by exhaustMap() operator until HttpClient instance returns response and get completed.
You can disable the button itself till you get a response by having a flag in your component's controller file.
// in component's controller
loading = false;
.
...
submit(): void {
loading = true;
this.someserive.getData().pipe(finalize(() => this.loading = false).subcribe(...
.
....
}
// in component's template
<button [disabled]="loading" (click)="submit()">Submit</button>

CodeMirror: onChanges - How to detect what the changes are?

I have an app where I allow the user to tyoe into a CodeMirror textbox.
I have a connection to the server using SignalR and am pushing up the full text of the textbox upon changes event.
The trouble is that changes are being pushed up to the server every time the user types a letter.
I managed to filter out the empty space and new line change with an if statement like this:
editorChanges(doc, changes: any[]) {
if (changes[0].text.some(str => str)) {
this.signalrHub.push(this.editor.value());
}
}
What I think I need is a solution using RxJs.
I am not all to familuar with RxJs.
Does anyone have experiencce with this?
Has anyone had to do this kind of thing before?
You can use a combination of fromEvent, map, debounceTime and distinctUntilChanged operators.
import { of, fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
let codeTextBox = document.getElementById('codetextbox');
let sourceStream$ = fromEvent(codeTextBox, 'input')
.pipe(
map((x: any) => x.target.value),
debounceTime(300),
distinctUntilChanged()
);
cont mysubscription = sourceStream$.subscribe(x=>{
console.log(x) //<-- this is the latest text in the textbox
});
//mysubscription.unsubscribe(); <-- add this line when your view is getting destroyed
Also, do take care not to subscribe multiple times. Ideally the subscribe should happen only once, when the textbox is available in DOM. And do NOT forget to unsubscribe else you'll have a memory leak.

ngRx state update and Effects execution order

I have my own opinion on this question, but it's better to double check and know for sure. Thanks for paying attention and trying to help. Here it is:
Imagine that we're dispatching an action which triggers some state changes and also has some Effects attached to it. So our code has to do 2 things - change state and do some side effects. But what is the order of these tasks? Are we doing them synchronously? I believe that first, we change state and then do the side effect, but is there a possibility, that between these two tasks might happen something else? Like this: we change state, then get some response on HTTP request we did previously and handle it, then do the side effects.
[edit:] I've decided to add some code here. And also I simplified it a lot.
State:
export interface ApplicationState {
loadingItemId: string;
items: {[itemId: string]: ItemModel}
}
Actions:
export class FetchItemAction implements Action {
readonly type = 'FETCH_ITEM';
constructor(public payload: string) {}
}
export class FetchItemSuccessAction implements Action {
readonly type = 'FETCH_ITEM_SUCCESS';
constructor(public payload: ItemModel) {}
}
Reducer:
export function reducer(state: ApplicationState, action: any) {
const newState = _.cloneDeep(state);
switch(action.type) {
case 'FETCH_ITEM':
newState.loadingItemId = action.payload;
return newState;
case 'FETCH_ITEM_SUCCESS':
newState.items[newState.loadingItemId] = action.payload;
newState.loadingItemId = null;
return newState;
default:
return state;
}
}
Effect:
#Effect()
FetchItemAction$: Observable<Action> = this.actions$
.ofType('FETCH_ITEM')
.switchMap((action: FetchItemAction) => this.httpService.fetchItem(action.payload))
.map((item: ItemModel) => new FetchItemSuccessAction(item));
And this is how we dispatch FetchItemAction:
export class ItemComponent {
item$: Observable<ItemModel>;
itemId$: Observable<string>;
constructor(private route: ActivatedRoute,
private store: Store<ApplicationState>) {
this.itemId$ = this.route.params.map(params => params.itemId);
itemId$.subscribe(itemId => this.store.dispatch(new FetchItemAction(itemId)));
this.item$ = this.store.select(state => state.items)
.combineLatest(itemId$)
.map(([items, itemId]: [{[itemId: string]: ItemModel}]) => items[itemId])
}
}
Desired scenario:
User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;
switchMap operator in our effect cancells previous request_1 and makes request_2;
get the item_2 in response;
store it under key itemId_2 and make loadingItemId = null.
Bad scenario:
User clicks on itemUrl_1;
we store itemId_1 as loadingItemId;
make the request_1;
user clicks on itemUrl_2;
we store itemId_2 as loadingItemId;
we receive the response_1 before we made the new request_2 but after loadingItemId changed;
we store the item_1 from the response_1 under the key itemId_2;
make loadingItemId = null;
only here our effect works and we make request_2;
get item_2 in the response_2;
try to store it under key null and get an error
So the question is simply if the bad scenario can actually happen or not?
So our code has to do 2 things - change state and do some side
effects. But what is the order of these tasks? Are we doing them
synchronously?
Let's say we dispatch action A. We have a few reducers that handle action A. Those will get called in the order they are specified in the object that is passed to StoreModule.provideStore(). Then the side effect that listens to action A will fire next. Yes, it is synchronous.
I believe that first, we change state and then do the side effect, but
is there a possibility, that between these two tasks might happen
something else? Like this: we change state, then get some response on
HTTP request we did previously and handle it, then do the side
effects.
I've been using ngrx since middle of last year and I've never observed this to be the case. What I found is that every time an action is dispatched it goes through the whole cycle of first being handled by the reducers and then by the side effects before the next action is handled.
I think this has to be the case since redux (which ngrx evolved from) bills itself as a predictable state container on their main page. By allowing unpredictable async actions to occur you wouldn't be able to predict anything and the redux dev tools wouldn't be very useful.
Edited #1
So I just did a test. I ran an action 'LONG' and then the side effect would run an operation that takes 10 seconds. In the mean time I was able to continue using the UI while making more dispatches to the state. Finally the effect for 'LONG' finished and dispatched 'LONG_COMPLETE'. I was wrong about the reducers and side effect being a transaction.
That said I think it's still easy to predict what's going on because all state changes are still transactional. And this is a good thing because we don't want the UI to block while waiting for a long running api call.
Edited #2
So if I understand this correctly the core of your question is about switchMap and side effects. Basically you are asking what if the response comes back at the moment I am running the reducer code which will then run the side effect with switchMap to cancel the first request.
I came up with a test that I believe does answer this question. The test I setup was to create 2 buttons. One called Quick and one called Long. Quick will dispatch 'QUICK' and Long will dispatch 'LONG'. The reducer that listens to Quick will immediately complete. The reducer that listens to Long will take 10 seconds to complete.
I setup a single side effect that listens to both Quick and Long. This pretends to emulate an api call by using 'of' which let's me create an observable from scratch. This will then wait 5 seconds (using .delay) before dispatching 'QUICK_LONG_COMPLETE'.
#Effect()
long$: Observable<Action> = this.actions$
.ofType('QUICK', 'LONG')
.map(toPayload)
.switchMap(() => {
return of('').delay(5000).mapTo(
{
type: 'QUICK_LONG_COMPLETE'
}
)
});
During my test I clicked on the quick button and then immediately clicked the long button.
Here is what happened:
Quick button clicked
'QUICK' is dispatched
Side effect starts an observable that will complete in 5 seconds.
Long button clicked
'LONG' is dispatched
Reducer handling LONG takes 10 seconds. At the 5 second mark the original observable from the side effect completes but does not dispatch the 'QUICK_LONG_COMPLETE'. Another 5 seconds pass.
Side effect that listens to 'LONG' does a switchmap cancelling my first side effect.
5 seconds pass and 'QUICK_LONG_COMPLETE' is dispatched.
Therefore switchMap does cancel and your bad case shouldn't ever happen.

Performance of an angular 2 application with Firebase

I have been creating a web application using angular2 with firebase (angularfire2),
I want to know if my development method is optimized or not.
When user select a group, I check if he is already member of the group.
ngOnInit() {
this.af.auth.subscribe(auth => {
if(auth) {
this.userConnected = auth;
}
});
this.router.params.subscribe(params=>{
this.idgroup=params['idgroup'];
});
this._groupService.getGroupById(this.idgroup).subscribe(
(group)=>{
this.group=group;
this.AlreadyPaticipe(this.group.id,this.userConnected.uid),
}
);
}
this method is work, but when I place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe() ,I get an error group is undefinded ,I now because angular is asynchrone. I don't khow how I can do it?. How I can optimize my code ?,How I can place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe()
Thanks in advance.
Everything as stream :
Well first, you shouldn't subscribe that much, the best practice is to combine your observables into one and subscribe to it just once, because everytime you subscribe, you need to cleanup when your component is destroyed (not for http, neither ActivatedRoute though) and you end up managing your subscription imperatively (which is not the aim of RXjs). You can find a good article on this topic here.
You must think everything as a stream, all your properties are observables :
this.user$ = this.af.auth.share(); //not sure of the share, I don't know firebase, don't know what it implies...
this.group$ = this.router.params.map(params => params["idgroup"])
.switchMap(groupID => this.groupService.getGroupById(groupID)).share();
// I imagine that AlreadyPaticipe return true or false, but maybe i'm wrong
this.isMemberOfGroup$ = Observable.combineLatest(
this.group$,
this.user$.filter(user => user !== null)
).flatMap(([group, user]) => this.AlreadyPaticipe(groupID, user.uid));
You don't even have to subscribe ! in your template you just need to use the async pipe. for example:
<span>user: {{user$|async}}</span>
<span>group : {{group$|async}}</span>
<span>member of group : {{isMemberOfGroup$|async}}</span>
Or if you don't want to use the pipe, you can combine all those observable and subscribe only once :
this.subscription = Observable.combineLatest(
this.group$,
this.user$,
this.isMemberOfGroup$
).do(([group, user, memberofGroup]) => {
this.group = group;
this.user = user;
this.isMemberofGroup = memberofGroup;
}).subscribe()
in this case, don't forget to this.subscription.unsubscribe() in ngOnDestroy()
there is a very handy tool on rxJS docs (at the bottom of the page) that helps you to choose the right operator for the right behavior.
I don't care about streams, I want it to work, quick n' dirty :
If You don't want to change your code too much, you could use a Resolve guard that will fetch the data before your component is loaded. Take a look at the docs:
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
You need a resolver.

Categories

Resources