问题
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 ?
回答1:
Currently ngxs runs all your actions outside the angular zone for performance reasons. If you subscribe to the action stream or a store.select then your subscription will be executed in the angular zone by default. The action handlers within the State object are run outside the angular zone. This is the default behaviour for ngxs. I think that it is a valid requirement to turn off this "run outside of zone" feature. Please could you log a github issue requesting this feature.
Back to your immediate problem, I made a Http Interceptor that you can add to force the http calls back into the angular zone (you only really need this to trigger the change detection). I forked your stackblitz and added it here: https://stackblitz.com/edit/ngx-progressbar-inzone?file=src%2Fapp%2FNgZoneHttpInterceptor.ts
Here is the code for the interceptor:
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()); }
});
});
};
}
Hope this helps!
来源:https://stackoverflow.com/questions/51235284/http-requests-made-from-ngxs-state-doesnt-get-detected-by-angular-zone-related