Angular NgRx-仅在首次调用时才继续轮询服务的效果 [英] Angular NgRx - Effect to continue polling a service only called the first time

查看:103
本文介绍了Angular NgRx-仅在首次调用时才继续轮询服务的效果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,我刚刚在其中添加了NgRX,我希望使用效果来打开和关闭轮询.

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

示例大纲

我关注了这篇文章这似乎是一个好方法.我有一个此处的简化示例,其中大部分代码位于app.effects.ts.

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.

与示例类似,除了使用新的createEffect工厂方法外,我还具有startPolling$stopPolling$continuePolling$的效果.

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

此外,我将delay(2000)移到了takeWhile()上方,因为我发现如果服务调用引发错误,则catchError(err => of(appActions.getDataFail(err)))会导致效果进入连续的非常快速的循环而没有延迟.

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());
  }

我的问题

我有一些控制台日志,所以我们可以看到发生了什么事.

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

完美.

当我停下来时,我会看到

And when I hit the stop I see

dispatching stop
app effect stop polling

也正确.

现在,问题是我尝试重新启动时.如果我现在再次单击开始按钮,那么我看到的只是初始的开始轮询效果...

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

并且 continuePolling$中的代码不再被调用,因此我没有轮询.

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.

感谢您提供任何信息.

[UPDATE1]

我认为也许我的问题是,一旦isPollingActive设置为false,并且takeWhile(() => this.isPollingActive),停止",可观察对象就不再处于活动状态,即continuePolling$完成,因此将永远不会重新启动吗?

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?

假设是这样,我尝试了以下操作,其中有2个不同的变量,一个用于暂停"轮询(例如,如果我在离线模式下检测到该应用程序),另一个用于取消(即,当用户退出导航时)组件).

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)))
              ))
        ));    
    } 

我不建议运行上面的命令,因为当我随后分发pause polling action时,效果似乎陷入了无休止的循环,我不得不通过任务管理器终止浏览器.

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.

再次提供任何帮助,

[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())
  ));

现在暂停似乎可以正常工作,但是当我调度appActions.cancelPolling时,我再次看到app effect cancel polling的无限循环被记录到控制台.

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]

我发现了为什么出现无限循环以及如何停止它.根据文档在这里,我可以添加dispatch:false. ..

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.

我现在唯一的任务是能够弄清楚如何启动,停止和重新启动轮询,从而处理对appDataService.getData()的成功调用以及异常处理.

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]

我在此处拥有最新代码.

按原样运行它,我使getData成功,而且令人惊讶的是,无论是stop还是stop操作都将停止它并允许它重新启动. ,就像我假设takeWhile(() => !this.isPollingCancelled),会取消效果一样.

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.

此外,如果将true传递给getData,这将导致其可观察到错误.轮询继续进行(根据需要,即,即使出现错误也要重试),但是一旦我们现在分派暂停操作,它就不会停止轮询,而是分派停止,它确实会停止,但是不会重启.我赢不了.

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 });
      } 
    }

因此,在startPolling中,我调用this.createPollingEffect()来创建连续轮询效果.

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

但是,当我尝试这样做时,轮询永远不会开始.

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

[UPDATE6]

我想出了一个似乎对我有用的解决方案.

I have come up with a solution that seems to work for me.

我有以下内容

public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.startPollingGetData),
        tap(_ => this.logger.info('effect start polling')),
        tap(() => this.isPollingActive = true),
        switchMap(_ => this.syncData())
      ), { dispatch: false });

    public continuePolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataPlannerActions.DataSuccess,
          dataActions.DataFail),
        tap(_ => this.logger.debug('data effect continue polling')),
        tap(_ => this.isInDelay = true),
        delay(8000),
        tap(_ => this.isInDelay = false),
        switchMap(_ => this.syncData())
      ), { dispatch: false });


    public stopPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.stopPollingData),
        tap(_ => this.isPollingActive = false),
        tap(_ => this.logger.info('data effect stop polling')),
        map(_ => dataActions.stopPollingDataSuccess())
      ), { dispatch: false });


    private syncData(): Observable<Action> {
        const result$: Observable<Action> = Observable.create(async subscriber => {
          try {
            // If polling "switched off", we just need to return anything (not actually used)
            // Id isInDelay, we may be restating while we still have a pending delay.
            // In this case we will exit, and just wait for the delay to restart
            // (otherwise we can end up with more than one call to this)
            if (this.isInDelay || !this.isPollingActive) {
              subscriber.next("");
              return;
            }

我在这里使用了几个标志",我敢肯定,这样做会更"rxy".

I use a couple of "flags" here, I am sure thee would be a more "rxy" way of doing this.

实际上,

In fact, see this post on how to possibly get rid of the isInDelay (I Just need to get around to putting this into my production code above)

推荐答案

我将此作为我的问题/讨论的一部分,但我认为可以作为一种解决方案,使它更加明显...

I had this as part of my question/discussion, but thought would put as a solution to make a little more visible...

我想出了一个似乎对我有用的解决方案.

I have come up with a solution that seems to work for me.

我有以下内容

public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.startPollingGetData),
        tap(_ => this.logger.info('effect start polling')),
        tap(() => this.isPollingActive = true),
        switchMap(_ => this.syncData())
      ), { dispatch: false });

    public continuePolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataPlannerActions.DataSuccess,
          dataActions.DataFail),
        tap(_ => this.logger.debug('data effect continue polling')),
        tap(_ => this.isInDelay = true),
        delay(8000),
        tap(_ => this.isInDelay = false),
        switchMap(_ => this.syncData())
      ), { dispatch: false });


    public stopPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.stopPollingData),
        tap(_ => this.isPollingActive = false),
        tap(_ => this.logger.info('data effect stop polling')),
        map(_ => dataActions.stopPollingDataSuccess())
      ), { dispatch: false });


    private syncData(): Observable<Action> {
        const result$: Observable<Action> = Observable.create(async subscriber => {
          try {
            // If polling "switched off", we just need to return anything (not actually used)
            // Id isInDelay, we may be restating while we still have a pending delay.
            // In this case we will exit, and just wait for the delay to restart
            // (otherwise we can end up with more than one call to this)
            if (this.isInDelay || !this.isPollingActive) {
              subscriber.next("");
              return;
            }

我在这里使用了几个标志",我敢肯定,这样做会更"rxy".

I use a couple of "flags" here, I am sure thee would be a more "rxy" way of doing this.

实际上,

In fact, see this post on how to possibly get rid of the isInDelay (I Just need to get around to putting this into my production code above)

这篇关于Angular NgRx-仅在首次调用时才继续轮询服务的效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆