2

I'm using ngx-progressbar and it works fine with http request started from within services, components or resolvers.

Note that no manual triggering of the progress bar (via service, etc) during http request is needed. It is triggered automatically.

Unfortunately it doesn't work as expected when making an http request from within an NGXS State :

stackblitz :

I created for every case a button :

  • "Make Request (Dispatch to store, w/o zone)"

This does not work, no progress bar appears (or it appears but hangs and does not complete to 100%)

@Action(LoadUrl)
    load({ setState }: StateContext<string>) {
        return this.http.get<any>('https://jsonplaceholder.typicode.com/posts/1').pipe(tap(res => console.log(res)));
    }
  • "Make Request (Component)"

This does work as expected, progress bar appears

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
// ...
  makeRequestComponent() {
    this.http.get<any>('https://jsonplaceholder.typicode.com/posts/1').pipe(tap(res => console.log(res))).subscribe();
  }
}
  • "Make Request (Dispatch to store, w/ zone)"

I asked the developer of ngx-progressbar and he actually found the issue and solved it by wrapping the http call inside zone. This does work, progress bar appears :

@State<string>({
  name: 'testState',
  defaults: ''
})
export class TestUrlState {

  constructor(private http: HttpClient, private zone: NgZone) { }

  @Action(LoadUrl)
  load({ setState }: StateContext<string>) {

    this.zone.run(() => {

      this.http.get<any>('https://reqres.in/api/users?delay=2').pipe(
        tap(res => console.log(res))
      ).subscribe();

    });
  }
}

Since I want the progress bar to appear with every http request, what would be the right approach to solve this ?

Is there a way to tell NGXS to run every http request inside zone ?

4

1 回答 1

7

目前,出于性能原因,ngxs 在角度区域之外运行您的所有操作。如果您订阅 action stream 或 store.select,那么您的订阅将默认在 angular zone 中执行。State 对象中的动作处理程序在角度区域之外运行。这是 ngxs 的默认行为。我认为关闭这个“在区域外运行”功能是一个有效的要求。请您记录一个请求此功能的 github 问题。

回到您当前的问题,我制作了一个 Http 拦截器,您可以添加它以强制将 http 调用返回到角度区域(您只需要它来触发更改检测)。我将你的 stackblitz 分叉并添加到这里: https ://stackblitz.com/edit/ngx-progressbar-inzone?file=src%2Fapp%2FNgZoneHttpInterceptor.ts

这是拦截器的代码:

import { Injectable, Optional, Inject, NgZone, NgModule, ModuleWithProviders } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpInterceptor, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable, Observer } from 'rxjs';

@NgModule({
})
export class NgZoneHttpInterceptorModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: NgZoneHttpInterceptorModule,
      providers: [
        { provide: HTTP_INTERCEPTORS, useClass: NgZoneHttpInterceptor, multi: true }
      ]
    };
  }
}

@Injectable()
export class NgZoneHttpInterceptor implements HttpInterceptor {

  constructor(private _ngZone: NgZone) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {    
    return this._ngZone.run(() => {
      return next.handle(req).pipe(enterZone(this._ngZone));
    });
  }
}


function enterZone<T>(zone: NgZone) {
  return (source: Observable<T>) => {
    return new Observable((sink: Observer<T>) => {
      return source.subscribe({
        next(x) { zone.run(() => sink.next(x)); },
        error(e) { zone.run(() => sink.error(e)); },
        complete() { zone.run(() => sink.complete()); }
      });
    });
  };
}

希望这可以帮助!

于 2018-07-09T11:25:53.717 回答