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

Wol*_*359 0 angular ngxs

我正在使用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)));
    }
Run Code Online (Sandbox Code Playgroud)
  • “发出请求(组件)”

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

@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();
  }
}
Run Code Online (Sandbox Code Playgroud)
  • “发出请求(分派到存储区,带区域)”

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

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

    });
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*eld 5

目前,出于性能原因,ngxs会在角度区域外运行所有操作。如果您订阅动作流或store.select,则默认情况下将在角度区域执行您的订阅。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()); }
      });
    });
  };
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!