3

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.

bartosz.baczek
  • 1,567
  • 1
  • 17
  • 32
  • Do you want to stop polling if you receive an error response? – frido Jan 24 '19 at 11:21
  • Nope, I want to stop polling only after "StopPollingSubnetEntries" action is emmited. – bartosz.baczek Jan 24 '19 at 11:36
  • Possible duplicate of [Repeat request (Angular2 - http.get) n seconds after finished](https://stackoverflow.com/questions/37938735/repeat-request-angular2-http-get-n-seconds-after-finished) – frido Jan 24 '19 at 14:48
  • Also checkout this blog post: https://blog.strongbrew.io/rxjs-polling/#polling-when-data-is-resolved – frido Jan 24 '19 at 14:49

3 Answers3

3

You could use expand to continuously map to the next http request and add a timer 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(_ => timer(3000).pipe(
    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-cdtwoc?file=app/app.component.ts

frido
  • 13,065
  • 5
  • 42
  • 56
  • Unfortunatelly it seems to not be working - getSubnetEntries() is send only once, and same response is then repeated over and over again. – bartosz.baczek Jan 24 '19 at 14:19
  • Thanks for the notice. I tested the code with a delayed Observable instead of a httpRequest and it seemed to work. I might fix my code here but also found another SO question and blog regarding your problem in the mean time. I'll add a comment to your question. – frido Jan 24 '19 at 14:48
  • @bartosz.baczek Are you sure my code doesn't work? I've added a stackblitz to my answers where I use real http requests now and it seems to work. Please try it out. – frido Jan 24 '19 at 15:23
2

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$),
  )),
);
martin
  • 93,354
  • 25
  • 191
  • 226
  • 1
    seems like your code is still using switchmap though. Did u mean to use concatMap there? – bartosz.baczek Jan 24 '19 at 13:22
  • 1
    I meant the second `switchMap`. But yeah, I didn't need the `concatMap` at the end. – martin Jan 24 '19 at 13:23
  • Seems like there is an issue with you code - this.subnetBrowserService.getSubnetEntries() is called only once, and only code in 2nd pipe is being repeated. – bartosz.baczek Jan 24 '19 at 13:37
  • I thought that's exactly what you wanted. – martin Jan 24 '19 at 13:40
  • Oh no! I want to send GET request every 3 seconds (get request is sent in getSubnetEntries method). – bartosz.baczek Jan 24 '19 at 13:44
  • What I want to achieve is simmilar to long polling (or pulling - not sure). The only difference is that I need this additional delay. – bartosz.baczek Jan 24 '19 at 13:46
  • @martin why is `take(1)` needed? Also note that this approach sends a http request and then waits before emitting the response (`request -> wait -> response -> request -> wait -> response -> request -> wait ...`) instead of emitting the response right away and then waiting (`request -> response -> wait -> request -> response -> wait -> ...`). – frido Jan 25 '19 at 10:00
  • 1
    `take(1)` is required to complete the chain so `repeat()` can resubscribe and start over. And yes, it really does add delay before reemitting the response. – martin Jan 25 '19 at 10:11
  • @martin Ok, I guess whether you need `take(1)` depends on the way your http request Observable works. Http requests with the Angular `HttpClient` emit one value and then complete automatically, so I don't think you need `take(1)` in this case. – frido Jan 25 '19 at 10:21
0

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 })))
      );
    })
  );
}
bartosz.baczek
  • 1,567
  • 1
  • 17
  • 32