I am working on an effect, that will be polling server.
What I want to achieve is as follows:
1) Send GET request to server
2) After response is received, wait 3 seconds
3) Send same GET request
4) After response is received, wait 3 seconds
5) Send same GET request
... and so on.
The code that I have now, doesn't quite work, as it polls server every 3 seconds, no matter if response was received or not:
@Effect()
pollEntries$ = this.actions$.pipe(
ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StartPollingSubnetEntries),
switchMap(() => {
return timer(0, 3000);
}),
takeUntil(this.actions$.pipe(ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StopPollingSubnetEntries))),
switchMap(() => {
return this.subnetBrowserService.getSubnetEntries();
}),
map((entries) => {
return new SubnetBrowserApiActions.LoadEntriesSucces({ entries });
}),
catchError((error) => {
return of(new SubnetBrowserApiActions.LoadEntriesFailure({ error }));
}),
);
Another thing that I am struggling with is how to stop polling. If I emit StopPollingSubnetEntries
action before request is sent to server, then it works fine - however if I emit it after request is sent, then I receive one more subsequent response, before polling stops.
You could use expand
to continuously map to the next http request and add a delay
beforehand.
const stopPolling$ = this.actions$.pipe(
ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StopPollingSubnetEntries)
);
const httpRequest$ = this.subnetBrowserService.getSubnetEntries().pipe(
map(entries => new SubnetBrowserApiActions.LoadEntriesSucces({ entries })),
catchError(error => of(new SubnetBrowserApiActions.LoadEntriesFailure({ error })))
)
const pollEntries$ = this.httpRequest$.pipe(
expand(_ => of(1).pipe(
delay(3000),
mergeMap(_ => this.httpRequest$),
)),
takeUntil(this.stopPolling$)
);
To start polling you have to subscribe to pollEntries$
.
startPolling() {
this.pollEntries$.subscribe(entries => console.log(entries));
}
Or map to the pollEntries
whenever your action emits.
const pollEntriesOnAction$ = this.actions$.pipe(
ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StartPollingSubnetEntries),
switchMap(() => this.pollEntries$)
);
this.pollEntriesOnAction$.subscribe(entries => console.log(entries));
https://stackblitz.com/edit/angular-mu3kp5
I think you were close just instead of switchMap
and timer
you can use and delay()
, take(1)
and repeat()
:
const stop$ = this.actions$.pipe(ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StopPollingSubnetEntries));
@Effect()
pollEntries$ = this.actions$.pipe(
ofType(SubnetBrowserPageActions.SubnetBrowserPageActionTypes.StartPollingSubnetEntries),
switchMap(() => this.subnetBrowserService.getSubnetEntries().pipe(
catchError(...),
delay(3000),
take(1),
repeat(),
takeUntil(stop$),
)),
);
I wrote a blog post on this topic - https://bbonczek.github.io/jekyll/update/2018/03/01/polling-with-ngrx.html.
I've decided to create a few small effects, that when working together - will poll server. Code:
@Injectable()
export class SubnetEffects {
constructor(
private actions$: Actions<SubnetActions.SubnetActionsUnion>,
private http: HttpClient
) {}
private isPollingActive = false;
@Effect()
startPolling$ = this.actions$.pipe(
ofType(SubnetActions.SubnetActionTypes.StartPollingSubnetDevices),
map(() => this.isPollingActive = false), // switch flag to true
switchMap(() => {
return this.http.get<SubnetEntry>('http://localhost:5000/api/subnet').pipe(
switchMap(entries => new SubnetActions.GetSubnetDevicesSucceded({ entries })),
catchError(error => of(new SubnetActions.GetSubnetDevicesFailed({ error })))
),
}),
);
@Effect()
stopPolling$ = this.actions$.pipe(
ofType(SubnetActions.SubnetActionTypes.StopPollingSubnetDevices),
map(() => this.isPollingActive = false) // switch flag to false
);
@Effect()
continuePolling$ = this.actions$.pipe(
ofType(
SubnetActions.SubnetActionTypes.GetSubnetDevicesSucceded,
SubnetActions.SubnetActionTypes.GetSubnetDevicesFailed
),
takeWhile(() => this.isPollingActive), // do this only as long as flag is set to true
switchMap(() => {
return this.http.get<SubnetEntry>('http://localhost:5000/api/subnet').pipe(
delay(5000),
switchMap(entries => new SubnetActions.GetSubnetDevicesSucceded({ entries })),
catchError(error => of(new SubnetActions.GetSubnetDevicesFailed({ error })))
);
})
);
}