120

I'd like to inject a service into a class that is not a component.

For example:

Myservice

import {Injectable} from '@angular/core';
@Injectable()
export class myService {
  dosomething() {
    // implementation
  }
}

MyClass

import { myService } from './myService'
export class MyClass {
  constructor(private myservice:myService) {

  }
  test() {
     this.myservice.dosomething();
  }
}

I tried and it doesn't work. It seems like service need to be used in only component or service.

Is there a way to use a service in a normal class? or it's a bad practice to use a service in a normal class.

Thank you.

Elec
  • 1,699
  • 2
  • 13
  • 20
  • A possible clean way is to create a MyClassFactory() method (returning MyClass objects) within a regular @Injectable decorated class. – Flavien Volken Aug 07 '23 at 09:51

7 Answers7

73

Injections only works with classes that are instantiated by Angulars dependency injection (DI).

  1. You need to
  • add @Injectable() to MyClass and
  • provide MyClass like providers: [MyClass] in a component or NgModule.

When you then inject MyClass somewhere, a MyService instance gets passed to MyClass when it is instantiated by DI (before it is injected the first time).

  1. An alternative approach is to configure a custom injector like

With the new static injector

constructor(private injector:Injector) { 
  let childInjector = Injector.create({ providers: [MyClass], parent: this.injector});

  let myClass : MyClass = childInjector.get(MyClass);
}

With the deprecated ReflectiveInjector

constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

  let myClass : MyClass = childInjector.get(MyClass);
}

This way myClass will be a MyClass instance, instantiated by Angulars DI, and myService will be injected to MyClass when instantiated.
See also Getting dependency from Injector manually inside a directive

  1. Yet another way is to create the instance yourself:
constructor(ms:myService)
let myClass = new MyClass(ms);
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 2
    I think injecting service to a standalone class is an anti-pattern. – tomexx May 30 '17 at 09:30
  • 17
    Sure, but sometimes it can still make sense and it's always good to know what's possible – Günter Zöchbauer May 30 '17 at 15:34
  • Why create a new injector in the constructor? Why not use the one that was injected? If you are going to create a new one then there is no reason to have one injected in the first place – smac89 Jun 17 '17 at 18:48
  • The new one is a child injector with additional providers. If the providers added to the child injector depend on providers that are provided at parent components or at module level, then DI can resolve it all automatically. Therefore there definitely are situations where this makes sense. – Günter Zöchbauer Jun 17 '17 at 20:53
  • 1
    @TimothyPenz thanks for the edit. In this example the `private` isn't necessary, because `injector` isn't used outside the constructor, therefore no need to keep a reference in a field. – Günter Zöchbauer Oct 23 '17 at 03:03
  • UPDATE NEEDED: ReflectiveInjector is deprecated, whats the update on this for StaticInjector – Tomas Katz May 19 '19 at 11:23
  • Why is this an anti pattern? Dependency injection is a good thing? Why not use it? E.g. I have a class that makes http calls. Why not inject HttpClient? – Galdor Nov 09 '22 at 11:54
  • 1
    @Galdor if the class is an Angular service, then there shouldn't be a need to meddle with injectors in your code as shown in my answer, you just inject by configuring providers and listing constructor parameters. There are usually only rare edge cases where instances not managed by Angular need to inject something from Angular, like integration with libraries that are not built with Angular in mind or when application initialization order becomes tricky. If you need to pass injectors around in your normal Angular code, you're probably doing something poorly. – Günter Zöchbauer Nov 09 '22 at 12:38
  • So then it's best to instantiate the class explicitly with the HttpClient instance in the constructor as shown in your answer as (3)? – Galdor Nov 09 '22 at 15:01
  • 1
    @Glador If a class needs something from an injector, it should probably be registered as a provider and be injected instead of instantiating with `new`. So none of my 3 answers should be needed except in certain circumstances mentioned in my previous comment. – Günter Zöchbauer Nov 09 '22 at 15:54
  • I'm coming from NestJs, but if I have a `class Wizard` and it needs the injectable `MagicService`, and I want to instantiate 1000 Wizards, there is no other way than to pass the `MagicService` to the `Wizard` constructor from the wrapping `HeroCreatorService`. So this can't be an anti pattern if it is the only pattern available. – sezanzeb Mar 13 '23 at 08:42
34

Not a direct answer to the question, but if you're reading this SO for the reason I am this may help...

Let's say you're using ng2-translate and you really want your User.ts class to have it. You're immediate thought is to use DI to put it in, you are doing Angular after all. But that's kind of overthinking it, you can just pass it in your constructor, or make it a public variable you set from the component (where you presumably did DI it in).

e.g.:

import { TranslateService } from "ng2-translate";

export class User {
  public translateService: TranslateService; // will set from components.

  // a bunch of awesome User methods
}

then from some user-related component that injected TranslateService

addEmptyUser() {
  let emptyUser = new User("", "");
  emptyUser.translateService = this.translateService;
  this.users.push(emptyUser);
}

Hopefully this helps those out there like me who were about to write a lot of harder to maintain code because we're too clever sometimes =]

(NOTE: the reason you may want to set a variable instead of making it part of your constructor method is you could have cases where you don't need to use the service, so always being required to pass it in would mean introducing extra imports/code that are never really used)

Ryan Crews
  • 3,015
  • 1
  • 32
  • 28
  • and maybe make `translateService` a get / set where the `get` throws a meaningful error instead of a NullReference exception if you forgot to set it – Simon_Weaver Aug 13 '18 at 22:16
  • 3
    Maybe it makes more sense to make translateService a required param in the class constructor? This pattern is more in line with DI pattern imho. – Icycool Sep 28 '18 at 12:24
27

This is kind of (very) hacky, but I thought I'd share my solution as well. Note that this will only work with Singleton services (injected at app root, not component!), since they live as long as your application, and there's only ever one instance of them.

First, in your service:

@Injectable()
export class MyService {
    static instance: MyService;
    constructor() {
        MyService.instance = this;
    }

    doSomething() {
        console.log("something!");
    }
}

Then in any class:

export class MyClass {
    constructor() {
        MyService.instance.doSomething();
    }
}

This solution is good if you want to reduce code clutter and aren't using non-singleton services anyway.

Kilves
  • 1,028
  • 10
  • 13
  • But how do you use MyService in MyClass without any declaration in the constructor? – Akhil V Nov 10 '20 at 05:39
  • @AkhilV you just import the MyService as you would import any other class, then you can directly call the method as described. – Kilves Nov 11 '20 at 20:31
  • interesting hack... – FabioG Feb 24 '22 at 10:32
  • 1
    This way is good but there is only one issue. Angular won't instantiate the `MyService` unless it has been injected at least once. So if this service gonna be used only in classes it needs to be injected somewhere like `AppComponent` or `AppModule`. – Vahid Forghani Jun 21 '22 at 06:21
23

locator.service.ts

import {Injector} from "@angular/core";

export class ServiceLocator {
    static injector: Injector;
}

app.module.ts

@NgModule({ ... })

export class AppModule {
    constructor(private injector: Injector) {
        ServiceLocator.injector = injector;
    }
 }

poney.model.ts

export class Poney {

    id: number;
    name: string;
    color: 'black' | 'white' | 'brown';

    service: PoneyService = ServiceLocator.injector.get(PoneyService); // <--- HERE !!!

    // PoneyService is @injectable and registered in app.module.ts
}
Julien
  • 2,616
  • 1
  • 30
  • 43
  • 1
    Not sure what's the best practice for OP's scenario, but this answer seems so much simpler than the leading ones. Will moving the `injector.get()` call to the module work (assuming stateless service so several classes can "share" the same instance)? – G0BLiN Jun 28 '20 at 18:44
  • I think that code would end up by all the poney instances sharing the same instance of poney service. What do you think? – Julien Jun 30 '20 at 07:04
  • Julien - that's an acceptable result (actually the preferred one) for my scenario - think of something like an input validator, user permissions handler or localization service - where you need the same functionality across the app but there's no need for a unique instance for each individual consumer of the service. – G0BLiN Jul 05 '20 at 10:47
  • Having some trouble trying to get this to work in Jasmine tests. How to configure the test setup? ServiceLocator.injector keeps returning null, although I have injected the "PoneyService" into the TestBed? – GGizmos Nov 19 '20 at 18:15
  • 1
    @Julien can you add some explanation to why this answer works? – Luke Sep 21 '21 at 15:38
3

If your service methods are pure functions, a clean way to solve this is to have static members in your service.

your service

import {Injectable} from '@angular/core';
@Injectable()
export class myService{
  public static dosomething(){
    //implementation => doesn't use `this`
  }
}

your class

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
dasfdsa
  • 7,102
  • 8
  • 51
  • 93
1

Since Angular 14 you can use the inject function. But it should only be called in the constructor.

import { myService } from './myService'
export class MyClass {
  private myservice: myService
  constructor() {
    this.myservice = inject(myService);
  }
  test() {
     this.myservice.dosomething();
  }
}

elsewhere

import { MyClass } from './myClass'
@component()
export class MyComponent {
  constructor() {
    const myClass = new MyClass();
  }
}
Vahid
  • 6,639
  • 5
  • 37
  • 61
0

You can also make use of a factory like the MyClassFactory as follows:

import { Injectable } from '@angular/core';
@Injectable()
export class MyService {
  dosomething() {
    // implementation
  }
}
import { Injectable } from '@angular/core';
import { MyService } from './MyService'

@Injectable()
export class MyClassFactory {
  constructor(private myService:MyService) { }

  createMyClass() {
    return new MyClass(this.myService)
  }
}
import { MyService } from './MyService'
export class MyClass {
  constructor(private myService:MyService) { }
  test() {
     this.myservice.dosomething();
  }
}
Flavien Volken
  • 19,196
  • 12
  • 100
  • 133