Angular 未检测到从 Ngxs 状态发出的 Http 请求(区域相关问题)

Http requests made from Ngxs state doesn't get detected by Angular (zone related issue)

我正在使用 ngx-progressbar,它可以很好地处理从服务、组件或解析器中启动的 http 请求。

请注意,在 http 请求期间不需要手动触发进度条(通过服务等)。它是自动触发的。

不幸的是,当从 NGXS 状态中发出 http 请求时,它没有按预期工作:

stackblitz :

我为每个案例创建了一个按钮:

没有工作,没有进度条出现(或者它出现但挂起并且没有完成到 100%)

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

这确实按预期工作,出现进度条

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

我问了 ngx-progressbar 的开发者,他确实找到了问题并通过将 http 调用包装在 zone 中解决了问题。这确实有效,出现进度条:

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

    });
  }
}

由于我希望进度条随每个 http 请求一起出现,解决此问题的正确方法是什么?

有没有办法告诉 NGXS 运行 区域内的每个 http 请求?

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

回到您眼前的问题,我制作了一个 Http 拦截器,您可以添加它以强制将 http 调用返回到 angular 区域(您只需要它来触发更改检测)。我分叉了你的 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()); }
      });
    });
  };
}

希望对您有所帮助!