69 votes

APP_INITIALIZER déclenche le message "Impossible d'instancier la dépendance cyclique! ApplicationRef_" lorsqu'il est utilisé avec un fournisseur Http personnalisé qui redirige

Je suis en utilisant un custom Http fournisseur pour gérer les API erreur d'authentification. Dans mon CustomHttp, j'ai besoin de rediriger l'utilisateur vers la page de connexion lorsqu'un état 401 erreur est émise par l'API. Qui fonctionne très bien!

app.le 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] 
    }
});

personnalisé-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);
    });
  }
}

Ensuite, j'ai essayé d'utiliser l' APP_INITIALIZER opaque jeton pour obtenir les paramètres nécessaires pour initialiser mon application.

app.le 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();
  }

}

Cela crée une erreur :

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 j'commentaire de la coutume Http fournisseur, l'erreur n'est pas affiché et l' APP_INITIALIZER fonctionne comme prévu. Si je supprime l' Router de la Http fournisseur de deps déclaration, je n'ai pas l'erreur, mais plus la ma ConfigService.load() fonction est appelée deux fois.

Personne ne sait pourquoi ce routeur dépendance est à l'origine de cette dépendance cyclique d'erreur ? Comment puis-je empêcher mon ConfigService.load() fonction à appeler deux fois ?

Si besoin, j'ai créé un dépôt public de reproduire l'erreur : https://github.com/haia212/AngularErrorTestProject

91voto

rzelek Points 2744

Le problème est qu' Router peut asynchrone en charge de certaines routes. C'est pourquoi il a besoin d' Http. Votre Http dépend Router et Router dépend Http. Angulaire de l'injecteur n'est pas en mesure de créer l'un de ces services.

J'ai eu des problèmes similaires, et l'une des solutions peut être injecter Injector le lieu de la prestation et à obtenir un service par la suite.

Code:

@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);
  }
  ...

Donc, fondamentalement, vous n'avez pas besoin d' Router pour obtenir de l'instance d' Http de service. L'injection est effectuée lorsque vous accédez router bien - uniquement lorsque vous souhaitez rediriger l'utilisateur. router propriété est transparente à d'autres parties du code.

Si cela ne résoudra pas le problème, vous pouvez faire la même chose pour le reste de la injecté des services (à l'exception de ces appeler super).

3voto

Anne Lacan Points 238

Je l'ai résolu simplement en supprimant Router des déclarations deps :

 {
      provide: Http,
      useFactory: loadCustomHttp,
      deps: [XHRBackend, RequestOptions, DataHelperService]
    }
 

Et tout le reste reste le même. C'est un peu magique, mais ça marche.

2voto

Luis C Points 21

Peut-être que cette aide; la façon dont j'ai résolu ce problème est de changer la stratégie de l' CustomHttp de la classe pour utiliser la composition à la place.

Mon CustomHttp ressemble à quelque chose comme ceci:

@Injectable()
export class CustomHttp {

    constructor(private http: Http) {}

Maintenant, je n'ai pas besoin de Routeur ni de tout autre service est injecté dans mon custom service Http.

Dans la configuration du chargeur (config.service.ts) j'ai fait les modifications suivantes:

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

}

Supprimé le besoin d'injecter de l' Http service à la dépendance et au lieu de l'ajouter à la load(http: Http) méthode.

Dans mon app.module.ts j'ai le texte suivant:

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
    },

C'est ce que j'utilise actuellement sur mon application. Vous ne savez pas si cette approche fonctionne pour vous, mais j'espère que ça aide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X