Introduction To Angular Services

As described in angular.io, “A service is typically a class with a narrow, well-defined purpose“. In other words, we can see services as a piece of code that does something very specific.

Following a model-view-controller pattern, we want components to render the UI and nothing else. The logic and every other task (fetching data, updating variables, logging stuff, etc.) should be placed elsewhere, i.e. in services, because they can be reused throughout our application minimizing duplications.

In the next example, we will create a simple logging service that can be used to log clicks from anywhere in our app.

App Structure

structure of components in Angular Services app

We will create a new Angular app using the ng new app-name from our console. We clean the boilerplate and create two components within src/app so that they are peers of AppComponent.

In the example code, we call them one and two.

To approach services in a very simple way, we will create a service that logs clicks from different components. By clicking the buttons, the service will get triggered and will perform a sum before logging the outcome to the console.

Eventually, the application will look like the following:

Log Service app image
App UI

Creating Angular Services

We will start by creating a file called logging-service.service.ts inside src/app. Since services are classes we can start by adding the following code.

export class LoggingService {}

Now we want to store the number of clicks in a property inside the service so that every time we need to retrieve or update that property, there is a single place where we need to go. In other words, when we will click on a component to log the number of clicks, everything happens in a single place, i.e. LoggingService.

private clicksNumber: number = 0;

Given that Angular is using Typescript, the code above declares a private property called clicksNumber of type number and it initializes it to zero.

After that, I wrote a helper function called addClick to add a certain number of clicks that will be defined where this method is called. If the number of clicks is not passed as a parameter, it defaults to add one click.

export class LoggingService {
  private clicksNumber: number = 0;

  addClick(number: number = 1) {
    this.clicksNumber += number;
    console.log(`
      ${number} click added. 
      ${this.clicksNumber} clicks in total
    `);
  }
}

Our LoggingService is ready. However, there is one last thing we need to do to make the service available everywhere else in the app.

Provide Services In The App

There are two main ways to provide the service to the whole app:

  1. Import and provide in AppModule
  2. Use the @Injectable() decorator

We will use the @Injectable() decorator because of some benefits. The documentation explains that “registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled application if it isn’t used, a process known as tree-shaking.

Eventually, here is logging-service.service.ts

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

@Injectable({ providedIn: 'root' })
export class LoggingService {
  private clicksNumber: number = 0;

  addClick(number: number = 1) {
    this.clicksNumber += number;
    console.log(`
      ${number} click added. 
      ${this.clicksNumber} clicks in total
    `);
  }
}

It is also important to remember that “when you provide the service at the root level, Angular creates a single, shared instance of [the service] and injects it into any class that asks for it“.

Use Angular Services

Finally, we are going to use Angular Services from the components in our app. To keep the example lean, I omitted the parts related to CSS (e.g. classes and properties) but you can find the code on Github.

Within the HTML template, we simply add an event listener to bind a click event.

// one.component.html

<div>
  <p>Add 1 click</p>
  <button (click)="onClick()">Log</button>
</div>

In OneComponent class we inject the service as a dependency. Following Angular Dependency Injection, 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.

Therefore, we need to add something to the constructor of one.component.ts so that Angular knows that this component depends on LoggingService.

In the constructor of one.component.ts, we declare a parameter called logService of type LoggingService.

import { Component, OnInit } from '@angular/core';
import { LoggingService } from '../logging-service.service';

@Component({
  selector: 'app-one',
  templateUrl: './one.component.html',
  styleUrls: ['./one.component.css'],
})
export class OneComponent implements OnInit {
  constructor(private logService: LoggingService) {}

  ngOnInit(): void {}

  onClick() {
    this.logService.addClick();
  }
}

Note that we need to import LoggingService. Then we can use it in the onClick method where we call logService to execute addClick. After this, you can see the result in your console.

To see the rest of the code and the app, find the code on Github.

Things To Remember

  • A service is a class with a well-defined purpose
  • Services are used to separate the logic from the UI (among other things)
  • Use @Injectable() decorator to make the service available in the whole app
  • Import the service and declare it in the constructor in every component where you need it

Next: Passing Data Using Angular Services