Getting a cyclic dependency error

空扰寡人 提交于 2019-12-11 14:40:39

问题


I'm trying to make use of the angular-jwt but I get the follower cyclic dependency error:

Cannot instantiate cyclic dependency! InjectionToken_JWT_OPTIONS
  at NgModuleProviderAnalyzer.parse (compiler.js:19550)
    at NgModuleCompiler.compile (compiler.js:20139)
    at JitCompiler._compileModule (compiler.js:34437)
    at eval (compiler.js:34368)
    at Object.then (compiler.js:474)
    at JitCompiler._compileModuleAndComponents (compiler.js:34366)
    at JitCompiler.compileModuleAsync (compiler.js:34260)
    at CompilerImpl.compileModuleAsync (platform-browser-dynamic.js:239)
    at PlatformRef.bootstrapModule (core.js:5567)
    at eval (main.ts:13)

In my first demo Angular 5 application, here is my setup:

import {NgModule} from '@angular/core';
import {HttpClientModule} from '@angular/common/http';
import {JwtModule, JWT_OPTIONS} from '@auth0/angular-jwt';

import {KeycloakService} from './keycloak.service';

// See https://github.com/auth0/angular2-jwt

export function jwtOptionsFactory(keycloakService) {
  return {
    whitelistedDomains: ['localhost:3001'],
    blacklistedRoutes: ['localhost:3001/auth/'],
    tokenGetter: () => {
      return keycloakService.getJwtTokenFromLocalStorage();
    }
  };
}

@NgModule({
  imports: [
    HttpClientModule,
    JwtModule.forRoot({
      jwtOptionsProvider: {
        provide: JWT_OPTIONS,
        useFactory: jwtOptionsFactory,
        deps: [KeycloakService]
      }
    })
  ],
  providers: [
    KeycloakService
  ]
})
export class AuthModule {}

Having an empty deps: property removes the issue. Of course, no service being injected is no solution either.

And my service is:

import {Injectable} from '@angular/core';
import {environment} from '../environments/environment';
import {Observable} from 'rxjs';
import {JwtHelperService} from '@auth0/angular-jwt';
import {HttpClient, HttpHeaders} from '@angular/common/http';

declare let Keycloak: any;

const JWT_TOKEN_NAME: string = 'token';

@Injectable()
export class KeycloakService {

  static auth: any = {};

  constructor(private httpClient: HttpClient, private jwtHelperService: JwtHelperService) {}

  static init(): Promise<any> {
    const keycloakAuth: any = Keycloak({
      url: environment.KEYCLOAK_URL,
      realm: environment.KEYCLOAK_REALM,
      clientId: environment.KEYCLOAK_CLIENTID,
      'ssl-required': 'external',
      'public-client': true
    });

    KeycloakService.auth.loggedIn = false;

    return new Promise((resolve, reject) => {
      keycloakAuth.init({onLoad: 'check-sso'})
        .success(() => {
          console.log('The keycloak client has been initiated successfully');
          KeycloakService.auth.loggedIn = true;
          KeycloakService.auth.authz = keycloakAuth;
          KeycloakService.auth.logoutUrl = environment.KEYCLOAK_URL
            + '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
            + document.baseURI;
          resolve();
        })
        .error(() => {
          reject();
        });
    });
  }

  static hasRole(role: string): boolean {
    return KeycloakService.auth.authz.tokenParsed.realm_access.roles.indexOf(role) > -1;
  }

  static getUsername(): string {
    return KeycloakService.auth.authz.tokenParsed.preferred_username;
  }

  static getFullName(): string {
    return KeycloakService.auth.authz.tokenParsed.name;
  }

  public login(ussername: string, password: string): Observable<any> {
    console.log('Sending the login credentials to obtain a token');
    const credentials = {username: ussername, password: password};
    const url: string = environment.KEYCLOAK_URL + '/realms/' + environment.KEYCLOAK_REALM
      + '/protocol/openid-connect/token/generate-token';
    return this.httpClient.post(url, credentials);
  }

  public logout(): void {
    KeycloakService.auth.loggedIn = false;
    KeycloakService.auth.authz = null;
    window.location.href = KeycloakService.auth.logoutUrl;
  }

  public getToken(): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      if (KeycloakService.auth.authz && KeycloakService.auth.authz.token) {
        KeycloakService.auth.authz
          .updateToken(5) // Refresh the token if it will expire in n seconds or less
          .success(() => {
            resolve(<string>KeycloakService.auth.authz.token);
          })
          .error(() => {
            reject('Failed to refresh the auth token');
          });
      } else {
        reject('The auth token could not be retrieved because the user was not logged in');
      }
    });
  }

  public isAuthenticated(): boolean {
    const token = this.getJwtTokenFromLocalStorage();
    return (token && !this.jwtHelperService.isTokenExpired(token));
  }

  public getTokenExpirationDate() {
    const token = this.getJwtTokenFromLocalStorage();
    return this.jwtHelperService.getTokenExpirationDate(token);
  }

  public getDecodedToken() {
    const token = this.getJwtTokenFromLocalStorage();
    return this.jwtHelperService.decodeToken(token);
  }

  public getJwtTokenFromLocalStorage(): string {
    return localStorage.getItem(JWT_TOKEN_NAME);
  }

  public setJwtTokenToLocalStorage(token: string): void {
    localStorage.setItem(JWT_TOKEN_NAME, token);
  }

  public getJwtTokenName(): string {
    return JWT_TOKEN_NAME;
  }

}

All my dependencies are:

"@angular/animations": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"@auth0/angular-jwt": "^1.0.0",
"angular-in-memory-web-api": "^0.5.3",
"core-js": "^2.4.1",
"jwt-decode": "^2.2.0",
"keycloak-js": "^3.4.3",
"npm": "^5.6.0",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"

UPDATE: I split the service into two services, one KeycloakService dealing with the Keycloak server and an AuthService sort of encapsulating it, so as to have smaller sized services. The issue remained though, and I had to go for David's solution and the workaround he mentioned. And my new AuthService has the following constructor:

@Injectable()
export class AuthService {

  //  constructor(private jwtHelperService: JwtHelperService) {} TODO
  jwtHelperService: JwtHelperService;
  constructor(private injector: Injector) {
    let jwtHelperService = this.injector.get(JwtHelperService);
  }

回答1:


As a workaround (it's not really ideal though), you could directly inject the injector in your KeycloakService

export class KeycloakService {

  static auth: any = {};

  constructor(private injector: Injector) {}

Then when you need access to HttpClient or JwtHelperService, then you can just call resolve in your service (not in the constructor !)

let httpClient = this.injector.get<HttpClient>(HttpClient);

Edit: From your comment on the question it looks like it's JwtHelperService causing the issue, so you could only onject Injector and HttpClient in the service's constructor and resolve JwtHelperService when needed



来源:https://stackoverflow.com/questions/49240232/getting-a-cyclic-dependency-error

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!