Angular NgRx - Effect to continue polling a service only called the first time

流过昼夜 提交于 2020-01-04 14:16:23

问题


I have an application where I have just added NgRX where I wish to use effects to switch polling on and off.

Sample outline

I followed this post which seemed like a good approach. I have a simplified example of this here, with the bulk of the code is in app.effects.ts.

Similar to the example, I have the effects startPolling$, stopPolling$ and continuePolling$, except I am using the newer createEffect factory methods.

Also, I have moved the delay(2000)above the takeWhile(), as I found if the service call throws an error, the the catchError(err => of(appActions.getDataFail(err))) would cause the effect to go into an continuous very fast loop without the delay.

The start and stop button dispatches the polling start and stop...

public start() {
    console.log('dispatching start');
    this.store.dispatch(appActions.startPolling());
  }

  public stop() {
    console.log('dispatching stop');
    this.store.dispatch(appActions.stopPolling());
  }

My Problem

I have some console logs so we can see what is going on.

When we click the start button (just the first time), I can see the polling start, and continue as expected. Eg I can see the following over and over...

dispatching start
app effect started polling
app.service.getData
app effect continue polling
app.service.getData
app effect continue polling
app.service.getData
app effect continue polling

Perfect.

And when I hit the stop I see

dispatching stop
app effect stop polling

Also correct.

Now, the problem, is when I try to restart. If I now click the start button again, all I see is the initial start polling effect...

dispatching start
app effect started polling
app.service.getData

and the code in continuePolling$is no longer being called, so I have no polling.

Does anyone have any idea why this effect is not triggered the seconds time? I just cannot work out why this is.

Thanks in advance for any information.

[UPDATE1]

I think perhaps my problem is that once isPollingActive is set to false, and takeWhile(() => this.isPollingActive), "stops", the observable is no longer active, ie the continuePolling$ complete, so will never restart?

Assuming this, I tried the following where I have 2 different variables, one to "pause" the polling (eg if I detect the app in an offline mode), and another to cancel (ie when the user would navigate out of the component).

So, my whole effects now becomes...

    @Injectable()
    export class AppEffects {
      private isPollingCancelled: boolean;
      private isPollingPaused: boolean;

      constructor(
        private actions$: Actions,
        private store: Store<AppState>,
        private appDataService: AppDataService
      ) { }

      public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.startPolling),
        tap(_ => console.log('app effect started polling')),
        tap(() => {
          this.isPollingCancelled = false;
          this.isPollingPaused = false;
        }),        
          mergeMap(() =>
            this.appDataService.getData()
              .pipe(                        
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));

         public pausePolling$ = createEffect(() => this.actions$.pipe(
            ofType(appActions.pausePolling),
            tap(_ => this.isPollingPaused = true),
            tap(_ => console.log('app effect pause polling')),       
         ));

      public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => this.isPollingCancelled = true),
        tap(_ => console.log('app effect cancel polling')),
      ));

        public continuePolling$ = createEffect(() => this.actions$.pipe(
          ofType(appActions.getDataSuccess, appActions.getDataFail),    
          tap(data => console.log('app effect continue polling')),  
          takeWhile(() => !this.isPollingCancelled),    
          delay(3000),  

          mergeMap(() =>
            this.appDataService.getData()
              .pipe(   
                delay(3000),  
                tap(data => console.log('app effect continue polling - inner loop')),  
                takeWhile(() => !this.isPollingPaused), // check again incase this has been unset since delay 
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));    
    } 

I would not recommend running the above as when I then dispatch a pause polling action, the effect seem to get into an endless loop, and I have to kill the browser via task manager.

I have no ideas why this is happening, but I appear to be further from a solution than before.

Any help, once again, greatly appreciated

[UPDATE2]

I noticed I was not returning any actions from the pause and cancel effects.

So I have updated them we follows...

 public pausePolling$ = createEffect(() => this.actions$.pipe(
    ofType(appActions.pausePolling),
    tap(_ => this.isPollingPaused = true),
    tap(_ => console.log('app effect pause polling')),
    map(_ => appActions.pausePollingSuccess())
  ));

  public cancelPolling$ = createEffect(() => this.actions$.pipe(
    ofType(appActions.cancelPolling),
    tap(_ => {
      this.isPollingCancelled = true;
      this.isPollingPaused = true;
    }),
    tap(_ => console.log('app effect cancel polling')),
    map(_ => appActions.cancelPollingSuccess())
  ));

Now the pause seems to work OK, but when I dispatch the appActions.cancelPolling, I again see like an infinite loop of app effect cancel polling being logged to the console.

[UPDATE3]

I have found why I get the infinite loop and how to stop it. According to the doco here, I can add the dispatch:false...

    public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => {
          this.isPollingCancelled = true;
          this.isPollingPaused = true;
        }),
        tap(_ => console.log('app effect cancel polling')),
      ), { dispatch: false }); // <------ add this

and this seems to fix my infinite loop.

My only task now is to be able to work out how to be able to start, stop, and restart the polling handling both success calls to appDataService.getData() as well as for exceptions.

I can get it working for one or the other (depending on where I put the delay and takewhile), but not for both

[UPDATE4]

I have the latest code here.

Running it as is, I have the getData succeed, and surprisingly, either the pause OR stop action will stop it and allow it to restart.. I am surprised the stop action allows it to restart, as I was assuming the takeWhile(() => !this.isPollingCancelled), would cancel the effect.

Also, if trueis passed to getData this will cause it's observable to error. The polling continues (as wanted, ie still retry even on error), but once we now when we dispatch the pause action, it does NOT stop polling, and it we dispatch the stop, it DOES stop, but then it will not restart. I cannot win.

[UPDATE 5]

I thought perhaps since the continue polling effect gets cancelled, I could just recreate it each time, as below..

    import { Injectable, OnInit, OnDestroy } from '@angular/core';
    import { createEffect, Actions, ofType } from '@ngrx/effects';
    import { select, Store } from '@ngrx/store';
    import { mergeMap, map, catchError, takeWhile, delay, tap, switchMap } from 'rxjs/operators';
    import { AppState } from './app.state';
    import { Observable, of } from 'rxjs';
    import { AppDataService } from '../app-data.service';
    import * as appActions from './app.actions';

    @Injectable()
    export class AppEffects {
      private isPollingCancelled: boolean;
      private isPollingPaused: boolean;

      constructor(
        private actions$: Actions,
        private store: Store<AppState>,
        private appDataService: AppDataService
      ) { }

      public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.startPolling),
        tap(_ => console.log('app effect started polling')),
        tap(() => {
          this.isPollingCancelled = false;
          this.isPollingPaused = false;
          this.createPollingEffect(); // <--- recreate the effect every time
        }),        
          mergeMap(() =>
            this.appDataService.getData()
              .pipe(                        
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));

      public pausePolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.pausePolling),
        tap(_ => this.isPollingPaused = true),
        tap(_ => console.log('app effect pause polling')),
      ), { dispatch: false });

      public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => {
          this.isPollingCancelled = true;
          this.isPollingPaused = true;
        }),
        tap(_ => console.log('app effect cancel polling')),
      ), { dispatch: false });

      public continuePolling$: any;

      private createPollingEffect(): void {
        console.log('creating continuePolling$');
        this.continuePolling$ = createEffect(() => this.actions$.pipe(
          ofType(appActions.getDataSuccess, appActions.getDataFail),
          tap(data => console.log('app effect continue polling')),
          delay(3000),
          takeWhile(() => !this.isPollingCancelled),
          mergeMap(() =>
            this.appDataService.getData(false)
              .pipe(
                tap(data => console.log('app effect continue polling - inner loop')),

                switchMap(data => {
                  return [appActions.getDataSuccess(data)
                  ];
                }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ), { resubscribeOnError: true });
      } 
    }

So, in the startPolling I call this.createPollingEffect() to create the continue polling effect.

However, when I tried this, the polling never starts.


回答1:


Use that instead:

public startPolling$ = createEffect(() => this.actions$.pipe(
  ofType(appActions.startPolling),    
  tap(_ => console.log('app effect started polling')),  
  tap(() => this.isPollingActive = true),        
  switchMap(() =>
    this.appDataSurvice.getData()
      .pipe(                        
        exhaustMap(data => {              
          return [appActions.getDataSuccess(data)];
        }),
        catchError(err => of(appActions.getDataFail(err)))
      ))
));


来源:https://stackoverflow.com/questions/58426291/angular-ngrx-effect-to-continue-polling-a-service-only-called-the-first-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!