tutorial ejemplos curso cli angular typescript angular-dependency-injection

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.