I am using a custom Http provider to handle API authentication error. In my CustomHttp, I need to redirect the user to the login page when a 401 status error is emitted by the API. That works fine!
app.module.ts
export function loadCustomHttp(backend: XHRBackend, defaultOptions: AppRequestOptions,
router: Router, dataHelper: DataHelperService) {
return new CustomHttp(backend, defaultOptions, router, dataHelper);
}
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, Router, DataHelperService]
}
});
custom-http.ts
import { Injectable } from '@angular/core';
import { Http, RequestOptions, RequestOptionsArgs, ConnectionBackend, Request, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { DataHelperService } from '../helpers/data-helper.service';
import { AuthStorage } from '../services/auth/auth-storage';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/empty';
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private router: Router, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url, options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body, options));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body, options));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
let token = AuthStorage.getToken();
if (err.status === 401 && token && AuthStorage.isTokenExpired()) {
// token has expired -> redirecting user to login
AuthStorage.clearAll();
this.router.navigate(['auth/login']);
}
return Observable.throw(err);
});
}
}
Then, I tried to use the APP_INITIALIZER
opaque token to get the required settings to initialize my app.
app.module.ts
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config: ConfigService) => () => config.load(),
deps:[ConfigService, Http],
multi: true
}
});
config.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ConfigService {
public settings:AppSettings;
constructor(private http:Http) { }
load() : Promise<AppSettings> {
let url = '/settings/';
var observable= this.http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
This creates an error :
Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! ApplicationRef_: in NgModule AppModuleNgModuleProviderAnalyzer.parse @ provider_analyzer.js:291NgModuleCompiler.compile @ ng_module_compiler.js:54RuntimeCompiler._compileModule @ runtime_compiler.js:102RuntimeCompiler._compileModuleAndComponents @ runtime_compiler.js:65RuntimeCompiler.compileModuleAsync @ runtime_compiler.js:55PlatformRef_._bootstrapModuleWithZone @ application_ref.js:303PlatformRef_.bootstrapModule @ application_ref.js:285(anonymous function) @ main.ts:18__webpack_require__ @ bootstrap 0e2b412…:52(anonymous function) @ main.bundle.js:86665__webpack_require__ @ bootstrap 0e2b412…:52webpackJsonpCallback @ bootstrap 0e2b412…:23(anonymous function) @ main.bundle.js:1
If I comment out the custom Http provider, the error is not shown and the APP_INITIALIZER
works as expected.
If I remove the Router
from the Http provider deps declaration, I don't have the error anymore but the my ConfigService.load()
function is called twice.
Does anyone knows why this router dependency is causing this cyclic dependency error ?
How can I prevent my ConfigService.load()
function to be called twice ?
If needed, I have created a public repository reproducing the error : https://github.com/haia212/AngularErrorTestProject
I solved it simply by removing Router from the
deps
declarations :And everything else stay the same. It feels a bit like magic but it works.
Maybe this helps; the way I solved this is by changing the strategy for the
CustomHttp
class to use composition instead.My
CustomHttp
looks something like this:Now, I don't need Router nor any other service injected in my custom Http service.
In the configuration loader (
config.service.ts
) I made the following changes:Removed the need to inject the
Http
service dependency and instead added it to theload(http: Http)
method.In my
app.module.ts
I have the following:This is what I am currently using on my app. Not sure if this approach will work for you but hope it helps.
The problem is that
Router
can async load some routes. This is why it needsHttp
. YourHttp
depends onRouter
andRouter
depends onHttp
. Angular injector is not able to create any of these services.I had similar problems and one of the solutions can be injecting
Injector
instead of service and getting service afterwards.Code:
So, basically, you do not need
Router
to get instance ofHttp
service. The injection is done when you accessrouter
property - only when you want to redirect user.router
property is transparent to other parts of code.If it will not solve problem, you can do the same thing to the rest of injected services (except these to call
super
).