APP_INITIALIZER引发“无法实例化循环依赖性!“ ApplicationRef_”与重定向的自定义Http提供程序一起使用时


68

我正在使用自定义Http提供程序来处理API身份验证错误。在我的CustomHttp中,当API发出401状态错误时,我需要将用户重定向到登录页面。很好!

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 provider deps声明中删除,则不再有错误,但是我的ConfigService.load()函数被调用了两次。

有谁知道为什么此路由器依赖性导致此循环依赖性错误?如何防止ConfigService.load()函数被两次调用?

如果需要,我创建了一个公共存储库以重现错误:https : //github.com/haia212/AngularErrorTestProject

Answers:


90

问题是Router可以异步加载某些路由。这就是为什么需要它的原因Http。你Http依靠,RouterRouter依靠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)。


1
请注意,注入注入器被认为是一种不好的做法,因为这会使您的课程与特定的DI实现紧密结合,并且一开始就违反了DI的目的。
Slava Fomin II

2
@SlavaFominII当然可以。注入Injector不是一个好习惯,通常是设计不当的迹象(例如:两个服务相互依赖,意味着这些服务的目的没有正确定义)。但是,在这种情况下,我看不到任何在不限制功能的情况下保持完美设计的方法。在这里,锁定特定的DI实现似乎不是什么大问题(虚拟问题,我必须说),因为无论如何您都已锁定到Angular。另外,它Injector是一个抽象类,因此您可以根据需要提供实现。
rzelek

1
辉煌!!您如何从注入路由器服务中找出问题所在?我遇到了同样的问题,我找不到原因。感谢那!
Nguyen Tran

3

我只是通过从deps声明中删除Router来解决它:

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

其他一切保持不变。感觉有点像魔术,但是可以用。


1
如果在加载另一个提供程序时首先不需要路由器,则可以像执行操作一样从'deps'参数中删除路由器,从而解决了循环依赖性错误,但是这并不能解决您实际具有实际循环依赖性的情况(即A取决于B,而B取决于A)。
sarora

2

也许这会有所帮助;我解决此问题的方法是更改CustomHttp类使用组合的策略 。

CustomHttp看起来像这样:

@Injectable()
export class CustomHttp {

    constructor(private http: Http) {}

现在,我不需要路由器或自定义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
    },

这就是我目前在我的应用程序上使用的内容。不知道这种方法是否对您有用,但希望能有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.