Services depending on each other

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?

77803 次浏览

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.

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

See Günters answer

Circular dependency with Angular 2 and SystemJS

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.

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

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

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

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

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.

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


}

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

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

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;