35

I have 3 services:

auth.service.ts, account.service.ts, http.service.ts

While user signup I should create new account therefore I imported account.service.ts to auth.service.ts. I should do it because I use signup form data for creating a new account.

@Injectable()
export class AuthService {
  constructor(public accountService: AccountService) {}

  signUp(name: string, phone: string, email: string, password: string): void {

    ...

  userPool.signUp(phone, password, attributeList, null, (err: any, result: any) => {
  if (err) {

    ...

    return;
  }

  this.accountService.createAccount(name, phone, email).subscribe(res => {

    ...

    this.router.navigate(['/auth/confirmation-code']);
  });
});

}

So as I use AWS Cognito I should add an authorization token from auth.service.ts to http.service.ts therefore I imported auth.service.ts to http.service.ts.

@Injectable()
export class HttpService {
  private actionUrl: string;
  private headers: Headers;
  private options: RequestOptions;

  constructor(
    public _http: Http,
    public authService: AuthService 
  ) {
    this.actionUrl = 'https://example.com/dev';
    this.headers = new Headers();

    this.authService.getAuthenticatedUser().getSession((err: any, session: any) => {
      if(err) return;
      this.headers.append('Authorization', session.getIdToken().getJwtToken());
    });

    this.headers.append('Content-Type', 'application/json');
    this.headers.append('Accept', 'application/json');
    this.headers.append('Access-Control-Allow-Headers', 'Content-Type, X-XSRF-TOKEN');
    this.headers.append('Access-Control-Allow-Origin', '*');

    this.options = new RequestOptions({ headers: this.headers });
  }

    get(request: string): Observable<any> {
        return this._http.get(`${this.actionUrl}${request}`)
            .map(res => this.extractData(res))
            .catch(this.handleError);
   }

In my account.service.ts I should use http.service.ts for creating new account.

@Injectable()
export class AccountService {
  constructor(public httpService: HttpService) {}

WARNING in Circular dependency detected: src/app/core/services/account.service.ts -> src/app/core/services/http.service.ts -> src/app/core/services/auth.service.ts -> src/app/core/services/account.service.ts

WARNING in Circular dependency detected: src/app/core/services/auth.service.ts -> src/app/core/services/account.service.ts -> src/app/core/services/http.service.ts -> src/app/core/services/auth.service.ts

WARNING in Circular dependency detected: src/app/core/services/http.service.ts -> src/app/core/services/auth.service.ts -> src/app/core/services/account.service.ts -> src/app/core/services/http.service.ts

I understand that this is circular dependency Error. How to solve it? Best practice? All services fulfill their role and are important.

Dmitry Grinko
  • 13,806
  • 14
  • 62
  • 86
  • can you show constructors of each service plz! – El houcine bougarfaoui Oct 19 '17 at 14:26
  • As a quick answer: Create a new service that will have commun parts and import it in others. :) Also, the token could be send as argument to http service instead of importing the whole auth service to http service – Vega Oct 19 '17 at 14:26
  • You need to restructure your application if you're running into these types of problems. Circular dependencies aren't an angular specific issue, they're an application design issue, these patch solutions will not serve you in the long term. The HTTP service needs the auth service to set auth headers, and the account service needs the http service to make http calls. However, why would the auth service need the account service? it seems to me that the account service should be the one consuming the auth service, not the other way around. – bryan60 Oct 19 '17 at 14:42
  • > However, why would the auth service need the account service? Because I am using data from signup form to creat new account. – Dmitry Grinko Oct 19 '17 at 15:02
  • Please look at my updated question – Dmitry Grinko Oct 21 '17 at 11:57
  • Thanks, i'm writing the answer – yurzui Oct 21 '17 at 11:58

3 Answers3

52

You can use Injector for this. Inject it via constructor as usual, and then when you will need some service that leads to the circular dependency, get that service from it.

class HttpService {
  constructor(private injector: Injector) { }

  doSomething() {
    const auth = this.injector.get(AuthService);
    // use auth as usual
  }
}
Martin Adámek
  • 16,771
  • 5
  • 45
  • 64
  • 1
    I think this code won't work because Http has other dependencies (ConnectionBackend etc...). it's better to use `this.injector.get(AccountService);` or '`this.injector.get(AuthService );`' because we can know their dependencies. – El houcine bougarfaoui Oct 19 '17 at 14:39
  • Well, this code will work, i am using it in my AuthInterceptor. But this is just a prove of concept, you can inject any service like this when needed. – Martin Adámek Oct 19 '17 at 14:40
  • Oh I see I was confused. you're right and your approach is the way to go. upvote – El houcine bougarfaoui Oct 19 '17 at 14:49
  • It's true that it is better not to fall in this problem, but sometimes there is no way to go over (by refactoring the dependency graph). For example when you want to make http call from your HttpInterceptor. – Martin Adámek Oct 19 '17 at 14:50
  • 2
    @MartinAdámek, from my experience it is not true. There's always a way to not fall into this problem, though sometimes it is very hard to see it. What I do usually is introduce one more layer of abstraction and/or move responsibilities between components, sometimes by introducing couple more components. However, this method is not a silver bullet and must be used very carefully, otherwise amount of abstraction layers will lead to a great mess, but in such a case it is well justified. – Alexander Leonov Oct 19 '17 at 15:26
  • I guess you are using `Injector` in your service constructor, right? You can not do that, that's basically the same as injecting the problematic dependency directly. You have to use it later when calling a method from the service. – Martin Adámek Oct 21 '17 at 12:03
  • 2
    Anyway, if the problem is with authentication, its better to use HttpInterceptor for this, that can add necessary headers to every request on the fly. – Martin Adámek Oct 21 '17 at 12:05
  • I see your updated question. This is definitelly use case for HttpInterceptor, you should ger rid of the HttpService at all. – Martin Adámek Oct 21 '17 at 13:07
  • 5
    `HttpService` still has a reference to `AuthService` (it is used as Type in the `injector.get` method. How does this solve the circular depencency then? The files, services, classes are still referencing each other. – Youp Bernoulli Aug 16 '20 at 11:21
7

@deprecated from v4.0.0 use Type or InjectionToken

const auth = this.injector.get(AuthService);

Works on Angular 10:

const AuthService = this.injector.get<AuthService>(AuthService);
Ashish Gehlot
  • 483
  • 8
  • 16
5

Just wanted to share a scenario that happened with me (maybe it could help someone). So I was getting the same circular dependency error and a Null Injector error too. The only thing that made my head spin was that I just had a single service and there was no literal circular dependency.

So I just tried to look the Null Injector error, the issue was that in my app.module I hadn't imported HttpClientModule. Once I imported that in imports array both error got fixed.

Wahab Shah
  • 2,066
  • 1
  • 14
  • 19