84

In my Angular 2 app, I have two services that depend on each other (service A calling methods from service B and vice versa).

Here are the relevant code:

app.component.ts:

import {Component} from 'angular2/core';
import {TempService} from '../services/tmp';
import {Temp2Service} from '../services/tmp2';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app/app.component.html',
    providers: [TempService, Temp2Service]
})
export class AppComponent { (...) }

Service 1:

import {Injectable} from 'angular2/core';
import {Temp2Service} from './tmp2';

@Injectable()
export class TempService {
  constructor (private _sessionService: Temp2Service) {}
}

Service 2:

import {Injectable} from 'angular2/core';
import {TempService} from './tmp';

@Injectable()
export class Temp2Service {
  constructor (private _sessionService: TempService) {}
}

Running the app leads to the following error:

EXCEPTION: Cannot resolve all parameters for 'Temp2Service'(undefined). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'Temp2Service' is decorated with Injectable

When commenting the constructor in one of the services, the app runs fine. So my guess is that the "cross-reference" of the two services is causing the problem.

Do you have an idea what is going wrong here? Or is my approach already wrong?

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Daniel
  • 2,409
  • 2
  • 26
  • 42
  • 5
    Well, angular needs a TempService to construct a Temp2Service, and it needs a Temp2Service to construct a TempService. It's a chicken and egg problem. Create a third service, and delegate to this third service to break the dependency. – JB Nizet Apr 02 '16 at 21:16

12 Answers12

51

This is a called circular dependency. It is not an issue with Angular2 itself. It is not allowed in any language I am aware of.

You will need to refactor your code to remove this circular dependency. Likely you will need to breakup one of these services into new service.

If you follow the single responsibility principle you will find you won't get into circular dependency trap.

Ahmad Baktash Hayeri
  • 5,802
  • 4
  • 30
  • 43
Martin
  • 15,820
  • 4
  • 47
  • 56
  • 113
    Actually, circular dependencies are allowed in all languages I know of. The problem is that the dependencies are needed by the constructor. If they were not, you could do `let a = new A(); let b = new B(); a.b = b; b.a = a;`. You would still have a circular dependency, but everything would run fine. I'm NOT saying that circular dependencies are a good thing. Just saying that they're possible. – JB Nizet Apr 02 '16 at 21:28
  • 2
    In some cases circular dependency can't be avoided. I found Julian FARHI's answer referencing forward reference useful. Solved my problem. – Chen Xing Apr 10 '17 at 05:39
  • 4
    `forwardRef` is for circular dependencies of classes within the same file. My answer is about DI dependencies. `forwardRef` won't have any influence of circular constructor dependencies of injectables. Removing one dependency from the constructor to break the cycle and instead inject `Injector` and use `setTimeout(() => { this.someDep = injector.get(SomeDependency); }` would be a way to work around. – Günter Zöchbauer May 23 '17 at 09:55
  • my answer provides a specific option for how to solve this – Alexander Mills Feb 04 '18 at 04:15
  • 5
    "In you follow the single responsibly principle you will find you won't get into circular dependency trap." This seems like a false statement. I have a view that can be shown as part of the page, or shown as a modal dialog. My view shows related models, which you can click on to view in a modal dialog. In Angular's eyes, this creates a circular dependency (the modal is not injected into the constructor). The responsibility of the actual view is no different in both scenarios (show the model), but it creates a circular dependency. I appreciate my scenario is different to the question, however. – ProgrammingLlama Apr 17 '18 at 02:14
  • 8
    You do not need to refactor your code. Just do not use constructor based injection. Use explicit injection. Why is this the top answer? – Charles Robertson Jul 26 '18 at 07:12
  • 1
    this response don't answer the question, the most simple solution is provided by @CharlesRobertson – Dimas Crocco Aug 13 '18 at 18:32
  • I dislike that ts has so huge problems with circular dependencies. circular dependecies are just really bad for Serializing. I used this very often. If you have a lot of services that manage things globally as a singleton, this is a very powerful tool and its a pity that they "fixed" this angularJS feature. – Manuel Graf Jun 04 '20 at 13:06
45

Constructor injection prevents circular dependencies.

It can be broken up by injecting the Injector and requesting a dependency imperatively like:

private payrollService:PayrollService;
constructor(/*private payrollService:PayrollService*/ injector:Injector) {
  setTimeout(() => this.payrollService = injector.get(PayrollService));
}

See also Circular dependency injection angular 2

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
35

The key here is not to inject the service via the constructor, but instead, use explicit setters & getters. I would use the following pattern in Angular 4:

app.component.ts

import { FooService } from './foo/foo.service';
import { BarService } from './bar/bar.service';

export class AppComponent {

  constructor(public fooService: FooService, public barService: BarService) {

    this.fooService.setBarService(barService);

  }

}

foo.service.ts

@Injectable()
export class FooService {

    barService: any;

    constructor(){
    }

    setBarService(barService: any): void {
        this.barService = barService;
    }

    getBarService(): any {
        return this.barService;
    }

}
Charles Robertson
  • 1,760
  • 16
  • 21
9

I updated this solution to work with Angular > 4. Using Injector class you can inject a service into another service

import { Injector } from '@angular/core';
import { TempService } from './tmp';


@Injectable()
export class Temp2Service {

  private tempService: any;

  constructor (private injector: Injector) { }

  public funcA() {
     this.tempService = this.injector.get(TempService);
     this.tempService.doSomething();
  }
}
Antony Orenge
  • 439
  • 5
  • 8
  • This does not help in Angular 4.0. – JoannaFalkowska Dec 07 '17 at 15:17
  • 1
    This is playing with fire - avoid it :) – Alexander Mills Feb 04 '18 at 04:08
  • 1
    @AlexanderMills: why do you say it is playing with fire ? and what better solution do you have ? Thank you. – Andrei Diaconescu Feb 16 '18 at 08:07
  • 1
    This is a reasonable solution to an unreasonable problem. – superluminary Oct 25 '18 at 17:26
  • This actually still works in later versions. Is it playing with fire? Yes, because it works around DI and will mess with testing. However, when you have a circular reference that is not glaringly obvious and is costing you production time and devs are sitting on their hands, this is a work-around to get your developers moving again while one person dives deep and tries to figure it out. – MBielski Aug 30 '21 at 17:10
7

It's a circular dependency and unfortunately it's a fundamental computer science problem, or information problem, something that Angular cannot solve. Try doing something like this instead:

export class ServiceA {
 constructor(private b: ServiceB){
    b.setA(this);
 }
}

export class ServiceB {
 
 private a: ServiceA

 constructor(){
    
 }

 setA(a){
   this.a = a;
 }

}

that's probably the best way to do it.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
2

We can solve forwordRef function to solve this issue.

//Allows to refer to references which are not yet defined.

@Inject(forwardRef(() => MyService)) private httpProxy: MyService

Hardik
  • 31
  • 1
1

If you are using Angular 2, and need circular dependency for calling functions of each other on some events, you may use Observables and subscribe them in Service in which you injected the other service.

Example:

@Injectable()
class Service1 {

  observeEvents() {
    return Obsevable.create((o) => {
      //store `o` it in any class variable
      //whenever you want to call function of Service2 from this class, do this `o.next('method_name');`
    });
  }
}

@Injectable()
class Service2 {
  constructor(private service1: Service1) {
    this.service1.subscribe((method) => {
      this[method]();
    });
  }
}
ssuperczynski
  • 3,190
  • 3
  • 44
  • 61
Ravinder Payal
  • 2,884
  • 31
  • 40
  • 1
    I won't downvote because all of these are hacks (thanks, Angular2, for not providing proper decorators) -- but this -- this is a hack. Props to the alternative solution, though. – Cody Jul 13 '17 at 05:48
  • @Cody sir, are hacks not allowed? – Ravinder Payal Jul 19 '17 at 19:45
  • 1
    Yes, hacks are allowed -- I think, but I'm not the arbiter on this. I was saying this is a bit of one -- not that Angular is providing much as an alternative. I appreciate the offer of another solution -- I wasn't counting anymore points against you than those against Angular. – Cody Aug 08 '17 at 05:26
  • @Cody seems interesting. Thanks – Ravinder Payal Aug 08 '17 at 05:41
1

Everything I tried to fix circular dependency warning with setTimeout or use of injector and moving the injection from constructor into another function didn't work for me with angular 7.

Here is my working solution:

I created another service just to hold the service referenz to first service:

@Injectable()
export class AnotherService {

  private _service: AService;

  get service(): AService {
    return this._service;
  }
  set service(service: AService) {
    this._service = service;
  }
}

Then I can use it like this:

@Injectable()
export class AService {

  constructor(private anotherService: AnotherService) {
    anotherService.service = this;
  }
  ...
}

and here:

@Injectable()
export class BService {
  private aService: AService;

  constructor(private injector: Injector) {
    const anotherService = injector.get(AnotherService);
    this.aService = anotherService.service;
  }
  ...
}
westor
  • 1,426
  • 1
  • 18
  • 35
0

I ran into circular dependency problems, found many answers and the best way I found to solve it is following.

As far as I understood, the problem occurs when you try to pass a: A to b: B into the constructor. So the way to avoid it is to create your object, and then only set a into b

As for me A and B are not so self talking, my example will be with my case which was having

Note that the imports do not influence the circular dependency problem.

rabbit.component.ts

export class RabbitComponent {

    public rabbitsArray: Rabbit[] = []

    constructor(){
        let lRabbit: Rabbit = new Rabbit(God.rabbitMotherFromEve())
        lRabbit.setRabbitComponent(this)
        rabbits.push(lRabbit)
    }
}

rabbit.ts

export class Rabbit {
    public feetsArray: Foot[] // I know its not the best practices but more concise for example
    public rabbitComponent: RabbitComponent

    constructor (anyThingYouWantButRabbitComponent: RabbitMother){
    }
}
Pipo
  • 4,653
  • 38
  • 47
0

I ran into an issue when querying my API. A can have many Bs, B has one A parent. When setting up these models one of these relations need to be left out in order to avoid a circular dependency. Then, when you query you can simply cast the type as any:

this.repositoryService.Query<A>(`$filter=Id eq ${B.AId}`).subscribe(As=>this.importantProp = (As[0] as any).C.property;)

Then in your definition of A:

@JsonProperty('B', [Object], true) Bs = new Array<Object>();
@JsonProperty('C', [Object], true) C = null;
James L.
  • 12,893
  • 4
  • 49
  • 60
-1

Use interfaces - this is a common pattern in many languages.

See Günters answer

Circular dependency with Angular 2 and SystemJS

Community
  • 1
  • 1
KnowHoper
  • 4,352
  • 3
  • 39
  • 54
  • I downvoted as linking to other answers rather than providing an answer directly is against the spirit of Stack Overflow. – RushPL Jul 18 '21 at 03:48
-2

you can try to call NEW on one of the services if not having a singleton is acceptable. like

this._sessionService = new TempService(this);

This was the approach I took since neither service used undefined member variables.