Angular Services & Dependency Injection Tutorial

In this Angular tutorial we learn how services can provide common functionality to multiple components in our app.

We also cover how to import and inject all the necessary resources we need for such services to work.

Lastly we cover the @Injectable decorator and how to provide a service from anywhere in our application.

What are Services

We can think of a service in Angular as a function.

We take a section of code that we use more than once throughout our project and wrap it in a function. We can then use the function over and over without having to rewrite the logic each time.

Services are the same, they provide a common service or functionality to multiple components.

What is Dependency Injection

Dependency injection simply means we provide a component with all the stuff it needs to work.

For example, let’s say we define a function in a separate file called ‘custom-function’, and we want to use that function on the ‘about’ page in our project.

We can’t just start using the function because Angular doesn’t know what it is, where it is, or what it can do. We would need to import the function’s code from the ‘custom-function’ file into our ‘about’ page before we can use it, because the ‘about’ page depends on it.

But what if the function in the ‘custom-function’ file requires something to work that’s imported from somewhere else. Well, dependency injection takes care of all that.

It will automatically bring any dependencies the function requires, into our ‘about’ page.

Lesson project setup

For this lesson there is no initial setup other than having an app.

To keep things simple, we will be working directly from inside the main ‘app.component’ component and will create whatever else we need on a step-by-step basis.

How to create a Service

A service is just a TypeScript class with the @Injectable decorator applied to it so it’s relatively easy if we want to create it manually.

We will use the CLI to create it in this lesson though, because it takes care of most of the work, like the decorator, imports etc.

To create a new service, we use the service keyword with the standard generation command.

Syntax: CLI command
ng generate service custom_service_name

So, let’s create a new service called ‘message’.

Example: Command
ng generate service message

We will imagine this service will be used to print messages to the page based on what the user is doing, like adding an item to a cart.

When the command is executed, Angular will create the following files.

  1. message.service.spec.ts . This is the file used to create tests. We won’t use it at the moment so it’s safe to delete it if you want to.
  2. message.service.ts . This is our service class.

The message.service.ts file should look similar to the following.

Example: message.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MessageService {

  constructor() { }
}

Because we used the CLI to generate the file, Angular did some stuff for us.

  • The @Injectable decorator was imported and added to the class.
  • It specified the provider as ‘root’. We will cover this in a moment.
  • The word ‘Service’ was appended to the service name. As mentioned earlier in the course, this is not required but it is a good practice. Angular does it by default.

Next, we need to add our new service to the app.module.ts file. Typically this is done for us, but not with services so we have to add it manually.

There are two things we need to do.

  1. Import the MessageService class from our new message.service.ts file.
  2. Add the MessageService class to the providers array.

Because we didn’t specify a folder when creating the service, Angular created it in the ‘app’ directory beside our other files, like the app component, module etc. For this reason, we use ./ in front of the file name we want to import because ./ means “current directory”.

Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { MessageService } from './message.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    MessageService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

We’re now ready to use our service.

providedIn 'root'

We need to “provide” a service before it’s available to be used.

We can do this in one of three ways. The easiest and recommended way is to specify it in the @Injectable decorator configuration object.

Example:
@Injectable({
  providedIn: Type<any> | 'root'
})
  • If we specify the value as ‘root’, it means that it will be available from anywhere inside our application.
  • The Type<any> option allows us to specify a specific @NgModule , if we have more than one. For example, a UserModule.

As mentioned, registering the provider in the @Injectable decorator is the recommended way to do it.

It allows Angular to automatically optimize the app at compile time by removing the service if it isn’t used. This is done in a process called tree-shaking, which simply means that if something isn’t used, it’s “shaken out”.

Using a Service

At this point our service isn’t doing anything, there’s nothing in it that other components can use.

1. To keep things simple, let’s create a method called printMessage() in the service class that will just return a message.

Example: message.service.ts
import { Injectable } from '@angular/core';
import { Message } from '@angular/compiler/src/i18n/i18n_ast';

@Injectable({
  providedIn: 'root'
})
export class MessageService {

  constructor() { }

  printMessage() {
    return 'This is a global service announcement';
  }
}

Now we have something we can use in other components. To do this we use dependency injection, just like we did in the Custom Directives lesson.

If we were making an actual message system, this is where would conditionally add different messages.

2. Let’s use it in our main component.

Example: app.component.ts
import { Component } from '@angular/core';
import { MessageService } from './message.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  constructor(private msgService: MessageService) { }

}

In the example above, we inject the service by doing two things.

  • We import the service at the top of the document.
  • We assign the service’s class to the msgService object in the constructor.

3. Now we can access the printMessage() method from the service through the msgService object with dot notation.

We can access it in the constructor or in another method. Let’s demonstrate how to use it in another method.

Example: app.component.ts
import { Component } from '@angular/core';
import { MessageService } from './message.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  constructor(private msgService: MessageService) { }

  getMessage() {
    return this.msgService.printMessage();
  }
}

This new method will simply return whatever message the printMessage() method decides to show.

4. Finally, let’s actually get the message to show on the page. We’ll just use interpolation on a simple <p> tag in the main component.

Example: app.component.html
<p>{{ getMessage() }}</p>

If we run the app in the browser, it will display the message we defined in the service.

The great thing is we can use this one service to print any message to the user on any “page” or component by just adding getMessage() to that component.

Adding more messages and testing the system will also be fast and easy because it’s all in one place.