Подтвердить что ты не робот

APP_INITIALIZER повышает значение "Невозможно создать циклическую зависимость! ApplicationRef_" при использовании с настраиваемым поставщиком Http, который перенаправляет

Я использую пользовательский поставщик Http для обработки ошибки аутентификации API. В моем CustomHttp мне нужно перенаправить пользователя на страницу входа, когда API-адрес ошибки 401 выдается API. Это прекрасно работает!

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

заказ 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);
    });
  }
}

Затем я попытался использовать непрозрачный токен APP_INITIALIZER, чтобы получить необходимые настройки для инициализации моего приложения.

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

}

Это создает ошибку:

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

Если я прокомментирую пользовательский поставщик Http, ошибка не отображается, а APP_INITIALIZER работает так, как ожидалось. Если я удалю Router из объявления депонирования поставщика Http, у меня больше нет ошибки, но моя функция ConfigService.load() вызывается дважды.

Кто-нибудь знает, почему эта зависимость от маршрутизатора вызывает эту ошибку циклической зависимости? Как я могу запретить вызов функции ConfigService.load() дважды?

При необходимости я создал публичный репозиторий, воспроизводящий ошибку: https://github.com/haia212/AngularErrorTestProject

4b9b3361

Ответ 1

Проблема заключается в том, что Router может асинхронно загружать некоторые маршруты. Вот почему ему нужно Http. Ваш Http зависит от Router и Router зависит от Http. Angular инжектор не может создать ни одну из этих служб.

У меня были аналогичные проблемы, и одно из решений может вводить Injector вместо обслуживания и получать обслуживание после этого.

код:

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

Итак, в принципе, вам не нужно Router, чтобы получить экземпляр службы Http. Инъекция выполняется при доступе к свойству Router - только если вы хотите перенаправить пользователя. Router свойство прозрачно для других частей кода.

Если это не решит проблему, вы можете сделать то же самое с остальными внедренными службами (кроме них, чтобы вызвать super).

Ответ 2

Возможно, это помогает; как я решил это, изменив стратегию для класса CustomHttp, чтобы использовать композицию.

Мой CustomHttp выглядит примерно так:

@Injectable()
export class CustomHttp {

    constructor(private http: Http) {}

Теперь мне не нужен Router или любая другая услуга, введенная в мою пользовательскую службу Http.

В загрузчике конфигурации (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() { }

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

}

Убрали необходимость вставить зависимость службы Http и вместо этого добавили ее в метод load(http: Http).

В моем app.module.ts у меня есть следующее:

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

Это то, что я сейчас использую в своем приложении. Не уверен, что этот подход будет работать для вас, но надеюсь, что это поможет.

Ответ 3

Я решил это просто, удалив Router из объявлений deps:

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

И все остальное остается неизменным. Он немного похож на волшебство, но он работает.