Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to inject a service inside another service and vice versa at the same time?

I have a real scenario in a real project where I need 2 services to access the properties and/or methods of each other. I'm not an Angular expert so is it possible?

I have tried and it fails. Here's my attempt:

app.component.ts

import { Component } from '@angular/core';
import { FirstService } from './first.service';
import { SecondService } from './second.service';

@Component({
  selector: 'my-app',
  template: '<h1>Hello world!</h1>',
  providers: [FirstService, SecondService]
})
export class AppComponent {

  constructor(public firstService: FirstService, public secondService: SecondService) {
    console.log(firstService.foo);
    console.log(secondService.bar);
  }

}

first.service.ts

import { Injectable } from '@angular/core';
import { SecondService } from './second.service';

@Injectable()
export class FirstService {

  foo: string = 'abc';

  constructor(public secondService: SecondService) {
    this.foo = this.foo + this.secondService.bar;
  }

}

second.service.ts

import { Injectable } from '@angular/core';
import { FirstService } from './first.service';

@Injectable()
export class SecondService {

  bar: string = 'xyz';

  constructor(public firstService: FirstService) {
    this.bar = this.bar + this.firstService.foo;
  }

}

Plunker: http://plnkr.co/edit/PQ7Uw1WHpvzPRf6yyLFd?p=preview

Just injecting the second service into the first service works fine but as soon as I inject the first service into the second service it fails and throws errors to the console.

So what is wrong?

A working solution should print the following to the console log:

abcxyz
xyzabc

Thanks in advance!

like image 381
nunoarruda Avatar asked May 09 '16 05:05

nunoarruda


People also ask

Can I inject a service into another service?

Yes, the first thing is to add the @Injectable decorator on each services you want to inject. In fact, the Injectable name is a bit insidious. It doesn't mean that the class will be "injectable" but it will decorate so the constructor parameters can be injected.

Can we inject one service in another service in Angular?

Angular Service that needs another service A service that needs another service should have a @Injectable decorator and then you can apply the same constructor injection pattern where you provide the service dependency with in the constructor.

How do you call a service inside another service?

Easy - Make it a singleton service Simply adding the providedIn: 'root' statement to the service's declaration will ensure there is only 1 instance of that service, which means anywhere you use it will be the same instance.

Can we Autowire service inside another service?

Of course you can and it is perfectly fine.

What happens when you inject service2 into a component?

But at the time that you inject Service2 into a component, you'll see something like this: So, whats happening here? 1 — Angular is instantiating Service2 because we injected it into AppComponent and declared it as a provider. 2 — To compl e te this task, Angular will check the dependencies of Service2 and, in this case, it is Service1.

What happens when service2 depends on Service1?

Let’s say now that this Service2 depends on Service1 and right way you would write something like: So far, you won't see any errors in your browser’s console. But at the time that you inject Service2 into a component, you'll see something like this: So, whats happening here?

How to instantiate Service1 manually in @injectable?

There is no providers field in @Injectable and instantiate Service1 manually it is strongly not recommended. So what we do? We must tell Angular to instantiate Service1 to be available (instatiated) before Service2. Hence, we declare it as a provider into our module. In this small example application, we have only one module, the app.module.ts.

Why is angular instantiating Service1 and service2?

1 — Angular is instantiating Service2 because we injected it into AppComponent and declared it as a provider. 2 — To compl e te this task, Angular will check the dependencies of Service2 and, in this case, it is Service1.


3 Answers

AngularJS does not allow injection of circular dependencies.

Miško Hevery, one of the authors of AngularJS, recommends finding the common elements:

+---------+      +---------+
|    A    |<-----|  B      |
|         |      |  |  +-+ |
|         |      |  +->|C| |
|         |------+---->| | |
|         |      |     +-+ |
+---------+      +---------+

And extracting it to a third service:

                         +---------+
+---------+              |    B    |
|    A    |<-------------|         |
|         |              |         |
|         |    +---+     |         |
|         |--->| C |<----|         |
|         |    +---+     +---------+
+---------+

For more information, see Circular Dependency in constructors and Dependency Injection by Miško Hevery.

like image 151
georgeawg Avatar answered Oct 26 '22 15:10

georgeawg


I'm not an Angular expert so is it possible

No. Circular dependencies are not resolved by angular's DI.

Also even systems that do support it, quite commonly are inconsistent e.g. commonjs https://nodejs.org/docs/latest/api/modules.html#modules_cycles will give you an empty object for a while.

Solution

Consider combining the two services into one. You can still move certain stuff (e.g. simple functions etc) out from the combined service if it becomes too much.

like image 20
basarat Avatar answered Oct 26 '22 15:10

basarat


I agree with the solution proposed by basarat. Another workaround would be to initialize the instances outside DI and provide them as value like

One service needs to be modified to be able to create an instance without providing the other service as dependency:

@Injectable()
export class FirstService {

  foo: string = 'abc';
  secondService: SecondService

  constructor() {
    //this.foo = this.foo + this.secondService.bar;
  }

  init(secondService:SecondService) {
    this.foo = this.foo + secondService.bar;
  }
}

Then create the instances imperatively and provide them as value

let firstService = new FirstService();
let secondService = new SecondService(firstService);

@Component({
  selector: 'my-app',
  template: '<h1>Hello world!</h1>',
  providers: [
    provide(FirstService, {useFactory: () => {
      firstService.init(secondService);
      return firstService;
  }}), provide(SecondService, {useValue: secondService})]
})
...

Plunker example

like image 45
Günter Zöchbauer Avatar answered Oct 26 '22 15:10

Günter Zöchbauer