Importing Injectables and Angular Services

1.2k Views Asked by At

I am learning about Injectables and Services in Angular and I am not clear about certain importing and default characteristics about injectables. Specifically, when should a service in Angular:

  1. Have the @Injectables tag? I had seen a blogpost that doesn't use it. What happens when you leave it out, and how would leaving this tag being left out change the way the service behaves?

  2. Be declared in the providers array in app.module?

  3. Be declared in the providers array of an individual component, rather than the app.module?

  4. What is the default value of the providedIn keyword when using the @Injectables tag? If this keyword is not defined in your service, what should be assumed about the keyword's value and the service's scope?

  5. Say I have a service (let's call it DummyService) with these characteristics: has the @Injectables tag, does not define providedIn, is not declared in any providers array (neither app.module nor any individual component).

    • 5.a) Since providedIn was not specified and the service is not declared in any providers array, can this service be shared with sibling components?
    • 5.b) When calling DummyService from a component who imported DummyService, what is the difference from these use cases:
DummyService.addToList("Stacy");

vs

constructor(private _dummyservice: DummyService){} 
ngOnInit(){
 this.dummyservice.addToList("Stacy");
}
3

There are 3 best solutions below

0
On
@Injectable({
  providedIn: 'root',
})

The code above tells angular, take this class and load it to the injector/container which can be thought as a container on top of the components. Angular will automatically create a single instance of each service for us. We do not create ourselves like

dummy= new DummyService()

angular will make it for us and make it available to all the different components inside the injector container. this will make the code reusable.

So whenever someone calls DummyService in any component, angular will ship the instance of DummyService. You could manually create it in the constructor of your component class.

  • The goal of dependency injection is to make testing easier and make the code reusable.

if you go to browser and check the compiler, get to the line where your class is defined you will see something like this:

AppComponent.ctorParameters=() ⇒  {type:_dummy_service__WEBPACK_IMPORTED_MODULE_2__[“DummyService” ]} ]

All the code inside of the typescript files are being processed by the typescript compiler before it even gets executed inside the browser. then webpack takes over, all those types, all those annotations, all the public and private stuff gets ripped out. As your browser does not know how to understand typescript. Class components constructor arguments also ripped out as a new property like above.

3
On

1.) @Injectable Decorator that marks a class as available to be provided and injected as a dependency. (From: https://angular.io/api/core/Injectable)

Means this class can be used with an Injector. But Angular can't actually inject it anywhere until you configure an Angular dependency injector with a provider of that service. This can be done inside of @Injectable, @NgModule() or @Component()

2.) Declaring a service in the providers array of app.module is the same as @Injectable({providedIn: 'root'}). You dont need to do it both. Using providedIn is the new / current recommended way of doing it.

3.) Service in Angular can be singletons, so if you provide a service in app.module, you can access the service everywhere and it's always the same instance. If you provide a service on component level, every component-instance get its own instance of the service.

@Component({providers: [DummyService], ...})
export class FooComponent {
  constructor(
    private readonly _dummyService: DummyService,
  ) {}
}

is essentially the same as

@Component(...)
export class FooComponent {
  private readonly _dummyService = new DummyService();
  constructor() {}
}

except that the latter does not use Angular's DI.

4.) {providedIn: 'root'}, if providedIn is not set, the service has to be set via providers-array.

5.) Angular DI can't resovle your service. See here: https://stackblitz.com/edit/angular-ivy-c48sfa?file=src/app/app.module.ts

3
On

going to answer alittle more in depth...

  1. the @Injectable decorator registers your service with the injector. Marking a service injectable means other services can be injected INTO the service. If you omit it, you can safely provide and inject the service, but if you try to inject into it, angular will throw an error at you. Generally though, best practice is to mark all services as @Injectable(), whether you inject into them or not.

  2. currently using providedIn: 'root' is the preferred way of registering a singleton service, or a service that you only want to have one instance of shared app wide, and it is more or less equivalent to adding it to the providers array in app.module. However, the case where you want to use the app.module providers array is if you need to use a special provider like a factory provider. providedIn: 'root' also is useful for lazy loaded modules as earlier iterations of angular had some issues with lazy loaded modules with their own provider arrays getting different copies of services meant to be singletons. the providedIn option solves this issue very well.

  3. providing in a component means that the component will get its own instance of a given service, AND ALSO THAT ALL CHILDREN OF THAT COMPONENT will receive that instance of a given service. if you provide ONLY at app.module (or root) level, then all components will receive the same service app wide. You CAN provide at root AND component levels if appropriate but you should be aware of what this does (a common case for this is some sort of global vs local alerts service).

  4. the "default" for providedIn is nothing. If you omit it, you must declare the service in a providers array appropriately to inject and use it. It has no scope until you declare it as root provided or put it in a providers array.

  5. The service can't be injected if it is not provided, full stop. questions 5a and 5b are both irrelevant as the service cannot be injected. Your example seems to be accessing a service method in a static fashion which is generally a bad idea. it's important to actually inject the services you intend to use to make dependency injection useful to your app.