angular - ejemplos - APP_INITIALIZER plantea "¡No se puede crear una instancia de dependencia cíclica! ApplicationRef_ ”cuando se usa con un proveedor Http personalizado que está redirigiendo
angular wikipedia (3)
Estoy usando un proveedor Http personalizado para manejar el error de autenticación de API. En mi CustomHttp, necesito redirigir al usuario a la página de inicio de sesión cuando la API emite un error de estado 401. Eso funciona bien!
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);
});
}
}
Luego, intenté usar el
APP_INITIALIZER
opaco
APP_INITIALIZER
para obtener la configuración requerida para inicializar mi aplicación.
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();
}
}
Esto crea un 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
Si comento el proveedor Http personalizado, el error no se muestra y el
APP_INITIALIZER
funciona como se esperaba.
Si elimino el
Router
de la declaración de proveedores del proveedor Http, ya no tengo el error, pero la función my
ConfigService.load()
se llama dos veces.
¿Alguien sabe por qué esta dependencia del enrutador está causando este error de dependencia cíclica?
¿Cómo puedo evitar que mi función
ConfigService.load()
se llame dos veces?
Si es necesario, he creado un repositorio público que reproduce el error: https://github.com/haia212/AngularErrorTestProject
El problema es que el
Router
puede cargar algunas rutas de forma asíncrona.
Es por eso que necesita
Http
.
Su
Http
depende del
Router
y el
Router
depende de
Http
.
El inyector angular no puede crear ninguno de estos servicios.
Tuve problemas similares y una de las soluciones puede ser inyectar
Injector
lugar de servicio y obtener servicio después.
Código:
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private injector: Injector, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
public get router(): Router { //this creates router property on your service.
return this.injector.get(Router);
}
...
Entonces, básicamente, no necesita el
Router
para obtener una instancia del servicio
Http
.
La inyección se realiza cuando accede a la propiedad del
router
, solo cuando desea redirigir al usuario.
router
propiedad del
router
es transparente a otras partes del código.
Si no resuelve el problema, puede hacer lo mismo con el resto de los servicios inyectados (excepto estos para llamar a
super
).
Lo resolví simplemente eliminando Router de las declaraciones
deps
:
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, DataHelperService]
}
Y todo lo demás permanece igual. Se siente un poco como magia pero funciona.
Quizás esto ayude;
La forma en que resolví esto es cambiando la estrategia para que la clase
CustomHttp
use composición en su lugar.
Mi
CustomHttp
parece a esto:
@Injectable()
export class CustomHttp {
constructor(private http: Http) {}
Ahora, no necesito enrutador ni ningún otro servicio inyectado en mi servicio Http personalizado.
En el cargador de configuración (
config.service.ts
) hice los siguientes cambios:
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() { }
load(http: Http) : Promise<AppSettings> {
let url = ''/settings/'';
var observable= http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
Se eliminó la necesidad de inyectar la dependencia del servicio
Http
y, en su lugar, se agregó al método de
load(http: Http)
.
En mi
app.module.ts
tengo lo siguiente:
providers: [
{
provide: Http,
useFactory: (backend, options) => new CustomHttp(new Http(backend, options)),
deps: [XHRBackend, RequestOptions]
},
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config, http) => () => config.load(http),
deps: [ConfigService, Http],
multi: true
},
Esto es lo que estoy usando actualmente en mi aplicación. No estoy seguro de si este enfoque funcionará para usted, pero espero que lo ayude.