developer tip

APP_INITIALIZER는“순환 종속성을 인스턴스화 할 수 없습니다!

copycodes 2020. 12. 25. 10:20
반응형

APP_INITIALIZER는“순환 종속성을 인스턴스화 할 수 없습니다! 리디렉션중인 사용자 지정 Http 공급자와 함께 사용되는 경우 ApplicationRef_”


API 인증 오류를 처리하기 위해 사용자 지정 Http 공급자를 사용하고 있습니다. 내 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] 
    }
});

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

그런 다음 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예상대로 작동합니다. RouterHttp 공급자 deps 선언에서를 제거하면 더 이상 오류가 없지만 내 ConfigService.load()함수가 두 번 호출됩니다.

Does anyone knows why this router dependency is causing this cyclic dependency error ? How can I prevent my ConfigService.load() function to be called twice ?

If needed, I have created a public repository reproducing the error : https://github.com/haia212/AngularErrorTestProject


The problem is that Router can async load some routes. This is why it needs Http. Your Http depends on Router and Router depends on Http. Angular injector is not able to create any of these services.

I had similar problems and one of the solutions can be injecting Injector instead of service and getting service afterwards.

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

So, basically, you do not need Router to get instance of Http service. The injection is done when you access router property - only when you want to redirect user. router property is transparent to other parts of code.

If it will not solve problem, you can do the same thing to the rest of injected services (except these to call super).


Maybe this helps; the way I solved this is by changing the strategy for the CustomHttp class to use composition instead.

My CustomHttp looks something like this:

@Injectable()
export class CustomHttp {

    constructor(private http: Http) {}

Now, I don't need Router nor any other service injected in my custom Http service.

In the configuration loader (config.service.ts) I made the following changes:

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

}

Removed the need to inject the Http service dependency and instead added it to the load(http: Http) method.

In my app.module.ts I have the following:

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

This is what I am currently using on my app. Not sure if this approach will work for you but hope it helps.


I solved it simply by removing Router from the deps declarations :

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

And everything else stay the same. It feels a bit like magic but it works.

ReferenceURL : https://stackoverflow.com/questions/39767019/app-initializer-raises-cannot-instantiate-cyclic-dependency-applicationref-w

반응형