Using multiple instances of the same service
Asked Answered
R

4

45

I have a service, like this:

@Injectable()
export class EditorService { ... }

And I have a component, like this:

@Component({
  ...
  template: `<child-component></child-component>`,
  providers: [EditorService],
  ...
})
export class SomeComponent {
  constructor(
    private _appleEditorService: EditorService,
    private _pearEditorService: EditorService) {}
  }

As you may have noticed, this component has a child component:

@Component({
  ...
  selector: 'child-component',
  ...
})
export class ChildComponent {
  constructor(
    private _appleEditorService: EditorService,
    private _pearEditorService: EditorService) {}
  }

As you can see, I want two instances of my EditorService: one will be used for editing apples and one for editing pears. However, the code above is not going to work, since there is no way for Angular to know which instance of EditorService is which as their variable names are private. _pearEditorService in ChildComponent might as well be referring to the same instance as _appleEditorService in SomeComponent.

Question: How else then, can I use the same Angular2 service twice?

EDIT: The point of my question is if it is possible using the same class. I know there are workarounds by actually creating a seperate class for every instance of the service, and even doing that with little code by inheritance. I just want to know if it can be done without.

Raiment answered 20/7, 2016 at 13:14 Comment(0)
F
79

Angular DI maintains a single instance per provider. In your case if you have two constructor parameters with the same type, they resolve to the same instance.

What you can do is to provide a factory function (different from a simple useFactory provider event though it uses it as well)

(Example copied from https://mcmap.net/q/374376/-what-is-the-equivalent-of-a-factory-in-angular2)

{ provide: EditorService, useFactory: 
    (dep1, dep2) => {
        return (x) => { 
            new EditorService(x, dep1, dep2);
        }
    }, deps: [Dep1, Dep2]
})

....

constructor(@Inject(EditorService) editorServiceFactory: any) {
  let editorService1 = editorServiceFactory(1);
  let editorService2 = editorServiceFactory(2);
}

If you have a fixed number of instances this should work:

{ provide: 'instance1', useClass: EditorService },
{ provide: 'instance2', useClass: EditorService },
export class SomeComponent {
    constructor(
        @Inject('instance1') private _appleEditorService: EditorService,
        @Inject('instance2') private _pearEditorService: EditorService) {}
}
Fishhook answered 20/7, 2016 at 14:0 Comment(9)
^ This might actually be a working work-around. Hadn't seen it before and can't test it right now. Will give you the upvote though as this is a more direct answer to the OP than my answer is :-)Cobbie
It's always a good idea to also point out alternative views to a problem, upvote as well :-)Kujawa
I finally understand when I use to use injection vs a simple new() - I've created a plunker so I will always keep it mind, incase others wants: plnkr.co/edit/tsay1qciOkxOS74MOBNo?p=infoPhlebotomize
I understand how the solution in the linked answer works with function (x) nested inside (dep1, dep2) but I don't understand what is expected here given that (x) is a closure around (dep1, dep2). Are the functions nested the wrong way around in this answer? If this answer is correct then how do the nested deps get resolved correctly given that they they are not typed?Xerxes
(x) is an example parameter that you can pass when you invoke the injected factory (editorServiceFactory(1 /* passed to x */)). dep1, dep2 is created from desp: [Dep1, Dep2] and matched by position. Let me know if there is still something unclear.Kujawa
Thanks but I still think your code above does not work ;) Take a look at this plunker and you will see that version 1 works but I cannot get version 2 (as you have described in your answer above) to work - the only difference between the versions is the reversal of the nested functions (x) & (dep1, dep2) in src/app.tsXerxes
You are right. Thanks a lot for the hint. I mixed the signatures of the nested method. plnkr.co/edit/Jbrr0vfMxb1sIR2GvVtW?p=previewKujawa
is there an update in syntax for Angular 8? just checkingSergio
Don't thinks so, but I'm not following Angular that closely anymore. github.com/angular/angular/blob/master/CHANGELOG.md is a comprehensive list of all changes.Kujawa
C
5

maybe rethink your design

I think you might have to rethink your design altogether then. What's the point of having two variables that point to the exact same EditorService? If they share the same implementation.

Inheritance solution

A solution to this would perhaps be to use Inheritance. I have not seen your services so I am not sure what they do, but from this code I am assuming that the "apple" functionality and the "pear" functionality is actually different. Then this is indicating you might have some design issues, and your EditorService might be doing too much. Perhaps you could move the code that is similar for both Apples and Pears to a service called EditorService and then have two classes extend this. an AppleEditorServiceand a PearEditorService.

Use general fruitService

Just to talk a bit more about why I think you might need to rethink your design. Let's assume that appels and pears actually do have the same functionality. So the EditorServicedoes the same. You want one variable to be used for 'Apples' and one for 'Pears'. If I see that code (assume other people work on your team), and I notice that both variables point to the same service. (Services are singletons), I would just be tempted to remove that and make something like fruitService : EditorServiceor something of the likes.

Either way your question makes me think the design should be changed. You don't want two instances of the service, services are singleton and although you could find ways around it, I don't think that's really the solution to your problem.


But as I have mentioned before. Think about your design. Do you really need two child-services, or can you solve it differently? Can you have apples and pears just subclass fruit? Can you just use one variable 'fruitService' instead of both apples/pears? What happens when you want to store "Strawberries" as well in the future? Having a bunch of variables refer to the same service (because they do the same thing) but with different variable names is not a good idea. Imagine seeing this in code

 appleService = EditorService;
 pearService = EditorService;
 starwberryService = EditorService;
 lemonService = EditorService;
 ...
 guavaService = EditorService;

Would this not indicate something is weird with the code? I can imagine (you didn't provide the code at this point) that you store an Array of the fruitInstances. But then maybe a fruitService could work and then store fruitService.store(Apple a) and put that in an array of 'fruit'. Use a 'filter' to get the right fruit from the array shouldn't be too hard.

^ This is just written without having more of your code to go on. If you edit your question, some things might not hold up anymore.

Cobbie answered 20/7, 2016 at 13:26 Comment(3)
Well, I can think of many reasons why any any mutable class would be instantiated multiple times, even if it is constructed with zero parameters. That's the point of instances, if you mutate the one, change its state, you don't change the other. Inheritance obviously would be the quickest workaround, thanks for pointing that out, but my question is really if Angular2 can do it with the same class.Raiment
@Devaro you are working with Services here. Services are singletons. I know you can get multiple instances of them - in different classes, but not in the same class. (Scoped providers). Of a singleton there is always just one instance. I don't think inheritance is a workaround here - I think it's just a normal way of dealing with the services. But your services might not be okay, couldn't infer that from your code. Maybe they are not doing things in an optimal way for angular2.Cobbie
Hmm ok. No overriding the fact that they are singletons then. Well, thanks for your help.Raiment
S
0

Not ideal, but if you create a module to each component, and then import your service on each module, you'll have 2 instances of the same service (one for each module).

Stork answered 5/4, 2019 at 5:29 Comment(0)
D
0

What about using an object in the service to store X amount of instances...

export class Test {

  private vars: any;

  constructor() {
    vars = {};
  }

  createInstance(var: any) {
    this.vars[var] = var;
  }

  doSomething(var: any, options): void {
    this.vars[var] = someChangeToVar;
  }

  doSomethingElse(var: any): void {
    this.vars[var] = someOtherChangeToVar;
  }

  get var(var: any): any {
    return this.vars[var];
  }

}

And you call it with the first parameter being the name of the instance...

export class SomeComponent {

  constructor(private test: Test) {

    // create instance
    this.test.createInstance('myInstance');

    // do something to it
    this.test.doSomething('myInstance', 'some input here');

    // get it back
    const result = this.test.get('myInstance');

    console.log(result);
  }
...
}
Dang answered 2/3, 2021 at 2:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.