Services and Dependency injection in Angular

Services and Dependency injection in Angular

1 Like

Service is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose.

Angular distinguishes components from services to increase modularity and reusability. By separating a component’s view-related functionality from other kinds of processing, you can make your component classes lean and efficient.

Ideally, a component’s job is to enable the user experience and nothing more. A component should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a model).

A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console. By defining such processing tasks in an injectable service class, you make those tasks available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances.

Why we create a service or why can’t we do everything on component

There are many reason why we should use services, instead of writing everything to components beacese

  • A component’s job is to enable the user experience and nothing more. A component should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a model).
  • Angular distinguishes components from services to increase modularity and reusability. By separating a component’s view-related functionality from other kinds of processing, you can make your component classes lean and efficient.
  • A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console. By defining such processing tasks in an injectable service class, you make those tasks available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances.

Dependency injection (DI)

DI is wired into an angular framework and used everywhere to provide new components with the services or other things they need. Components consume services i.e, you can inject service into a component, giving a component access to that service class
image

To define a class as a service in Angular, use the @Injectable() decorator to provide the metadata that allows Angular to inject it into a component as a dependency . Similarly, use the @Injectable() decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) has a dependency.

  • The injector is the main mechanism. Angular creates an application-wide injector for you during the bootstrap process, and additional injectors as needed. You don’t have to create injectors.
  • An injector creates dependencies, and maintains a container of dependency instances that it reuses if possible.
  • A provider is an object that tells an injector how to obtain or create a dependency.

For any dependency that you need in your app, you must register a provider with the app’s injector, so that the injector can use the provider to create new instances. For a service, the provider is typically the service class itself.

A dependency doesn’t have to be a service—it could be a function, for example, or a value.
When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types. For example, the constructor of HeroListComponent needs HeroService .

constructor( private service: HeroService) { }

When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn’t yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.

When all requested services have been resolved and returned, Angular can call the component’s constructor with those services as arguments.

Dependency Hierarchy

Each instance of component has an injector, and when a dependency is requested it looks at its own injector to see if it has the dependency. If it doesn’t, it will look at its parent component’s injector, until it reaches the root component of your app. The injectors are responsible for instantiating the services and keeping track of them for child components to use. This behavior is slightly different from dependency injection in AngularJS, where the injector tree was flat and were tied to individual modules.

Services need to be registered in the providers array at the parent-most component that will be using it. There will one instance of your service per injector, so registering a service more than once can cause your app to have multiple instances of the same service in your child components.

It’s also worth noting that not all services are singletons. They can be if you register them in the AppModule that’s used for bootstrapping, but there may be cases where you want to register a service at a child-level component, where each instance of that component gets their own instance of the service. This is the exception however, and not the rule. See the Provider Scope section of the Angular docs for more clarity.

Provider Scope

A provider is an instruction to the Dependency Injection system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.

Providing a service

If you already have an app that was created with the Angular CLI, you can create a service using the ng generate CLI command in the root project directory.

ng generate service User

This command creates the following UserService skeleton:

import { Injectable } from '@angular/core' ;

@Injectable ({

providedIn: 'root' ,

})

export class UserService {

}

You can now inject UserService anywhere in your application.

The service itself is a class that the CLI generated and that’s decorated with @Injectable(). By default, this decorator has a providedIn property, which creates a provider for the service. In this case, providedIn: 'root' specifies that Angular should provide the service in the root injector.

Provider scope

When you add a service provider to the root application injector, it’s available throughout the app. Additionally, these providers are also available to all the classes in the app as long they have the lookup token.

You should always provide your service in the root injector unless there is a case where you want the service to be available only if the consumer imports a particular @NgModule.

providedIn and NgModules

It’s also possible to specify that a service should be provided in a particular @NgModule . For example, if you don’t want UserService to be available to applications unless they import a UserModule you’ve created, you can specify that the service should be provided in the module:

import { Injectable } from '@angular/core' ;

import { UserModule } from './user.module' ;

@Injectable ({

providedIn: UserModule,

})

export class UserService {

}

The example above shows the preferred way to provide a service in a module. This method is preferred because it enables tree-shaking of the service if nothing injects it. If it’s not possible to specify in the service which module should provide it, you can also declare a provider for the service within the module:

import { NgModule } from '@angular/core' ;

import { UserService } from './user.service' ;

@NgModule ({

providers: [UserService],

})

export class UserModule {

}

The @Injectable Decorator and Provider Scope

The @Injectable() decorator is needed when a Service needs dependencies injected to its constructor.

IMPORTANT

Singleton services

A singleton service is a service for which only one instance exists in an app.

Providing a singleton service

There are two ways to make a service a singleton in Angular:

  • Set the providedIn property of the @Injectable() to "root".
  • Include the service in the AppModule or in a module that is only imported by the AppModule

Using providedIn

Beginning with Angular 6.0, the preferred way to create a singleton service is to set providedIn to root on the service’s @Injectable() decorator. This tells Angular to provide the service in the application root.

import { Injectable } from '@angular/core' ;

@Injectable ({

providedIn: 'root' ,

})

export class UserService {

}

The forRoot() pattern

Generally, you’ll only need providedIn for providing services and forRoot() /forChild() for routing. However, understanding how forRoot() works to make sure a service is a singleton will inform your development at a deeper level.

If a module defines both providers and declarations (components, directives, pipes), then loading the module in multiple feature modules would duplicate the registration of the service. This could result in multiple service instances and the service would no longer behave as a singleton.

There are multiple ways to prevent this:

  • Use the providedIn syntax instead of registering the service in the module.
  • Separate your services into their own module.
  • Define forRoot() and forChild() methods in the module.

Use forRoot() to separate providers from a module so you can import that module into the root module with providers and child modules without providers .

  1. Create a static method forRoot() on the module.
  2. Place the providers into the forRoot() method.

static forRoot(config: UserServiceConfig): ModuleWithProviders {

return {

ngModule: GreetingModule,

providers: [

{provide: UserServiceConfig, useValue: config }

]

};

}

More

Angular Service Example

Examples to create Angular service and import service as global dependency (via module) vs local dependency (via component) with example.

Create a service vie CLI using ng g s services-examples/calc

Inject as global service

To inject as global service, inject the service into root module.

...

import { ComponentServiceInjectTestComponent } from './components/component-service-inject-test/component-service-inject-test.component' ;

@NgModule ({

declarations: [

...

ComponentServiceInjectTestComponent

],

...

})

export class AppModule { }

Inject as local service.

...

@Component ({

selector: 'app-component-service-inject-test' ,

templateUrl: './component-service-inject-test.component.html' ,

styleUrls: [ './component-service-inject-test.component.scss' ],

providers: [CalcService]

})

export class ComponentServiceInjectTestComponent implements OnInit {

value:any;

constructor( private calcService: CalcService) {

this .value = 'from component-service-inject-test ' + this .calcService.getValue();

}

...

}

1 Like