Services depending on each other

Angular

Angular Problem Overview


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?

Angular Solutions


Solution 1 - Angular

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.

Solution 2 - Angular

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 https://stackoverflow.com/questions/40525850/circular-dependency-injection-angular-2

Solution 3 - Angular

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

}

Solution 4 - Angular

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

Solution 5 - Angular

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.

Solution 6 - Angular

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

Solution 7 - Angular

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

Solution 8 - Angular

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

Solution 9 - Angular

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

Solution 10 - Angular

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;

Solution 11 - Angular

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

See Günters answer

https://stackoverflow.com/questions/36278806/circular-dependency-with-angular-2-and-systemjs

Solution 12 - Angular

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDanielView Question on Stackoverflow
Solution 1 - AngularMartinView Answer on Stackoverflow
Solution 2 - AngularGünter ZöchbauerView Answer on Stackoverflow
Solution 3 - AngularCharles RobertsonView Answer on Stackoverflow
Solution 4 - AngularAntony OrengeView Answer on Stackoverflow
Solution 5 - AngularAlexander MillsView Answer on Stackoverflow
Solution 6 - AngularHardikView Answer on Stackoverflow
Solution 7 - AngularRavinder PayalView Answer on Stackoverflow
Solution 8 - AngularwestorView Answer on Stackoverflow
Solution 9 - AngularPipoView Answer on Stackoverflow
Solution 10 - AngularJames L.View Answer on Stackoverflow
Solution 11 - AngularKnowHoperView Answer on Stackoverflow
Solution 12 - AngularJerrad PatchView Answer on Stackoverflow