AngularT2 Dark_1200x303

Learn how to use services and dependency injection to improve your Angular development by making it modular, extensible and loosely coupled.

Angular is a framework for building dynamic client-side applications using HTML, CSS, and JavaScript. It has a nice CLI tool that helps with developer productivity, and for generating code that follows the recommended Angular design guide so you can build fast, responsive and modular applications. In this article, I'm writing about services and dependency injection in Angular.

If you want to continue reading, you should already have an understanding of components, directives, modules, and data binding in Angular. I'll use the Angular CLI to generate the needed files so an understanding of working with the Angular CLI is also needed. However, if you don't know those things, you're in good company because I've written about them 😃.

Here are the links to the articles I've written covering those topics:

  1. A Practical Guide To Angular: Environment and Project Set Up
  2. A Practical Guide To Angular: Components and Modules
  3. A Practical Guide To Angular: Data Binding & Directives

The sample application which we'll build together while you go through the article builds on the sample application from the articles I listed above. If you have been reading and working along with me over those articles, you should have the complete code. Otherwise, you can download the project on GitHub. When you download it, you should then copy the content from src-part-3 folder into the src folder if you want to code along while you read.

Services

Services is a broad term used in various development methodologies to refer to a function or group of functions designed to do something specific. You'll see it used in microservice architecture, service-oriented architecture, domain-driven design, and many others.

For example, let’s say you have a class that represents a bank account. This class has functions to tell you the balance, deduct and add money to the account. But, if you want to transfer funds from one account to another, you need a function that'll deduct from one account and credit another account. This functionality belongs to a service. It can be in a class with several other functions that don't fit in the account class but need to manipulate the account. Working with Angular and TypeScript, a service is typically a class with a well-defined purpose.

In order to build a loosely-coupled application and reuse code, it is best if you design your components to be lean and efficient. This means that the component's job should be to focus on the user experience and nothing more. A component should contain properties and methods for data binding, and delegate tasks such as fetching data and input validation to another class (a service). Doing it this way, we can also reuse that code or service in other components.

We're going to put the logic for data retrieval in a service. Add a new file in the src/app/expenses folder called expense.service.ts and put the code below in it.

import IExpense from "./expense";

export class ExpenseService {
  getExpenses(): IExpense[] {
    return [
      {
        description: "First shopping for the month",
        amount: 20,
        date: "2019-08-12"
      },
      {
        description: "Bicycle for Amy",
        amount: 10,
        date: "2019-08-08"
      },
      {
        description: "First shopping for the month",
        amount: 14,
        date: "2019-08-21"
      }
    ];
  }
}

This is a service which we'll use in places we need to retrieve expenses. We'll use this in the home component. Open src/app/home/home.component.ts, and after line 2, add the statement below:

import { ExpenseService } from "../expenses/expense.service";

Then declare a variable with the service class as the type, and update the constructor.

  private _expenseService: ExpenseService;
  constructor() {
    this._expenseService = new ExpenseService();
    this.expenses = this._expenseService.getExpenses();
  }

  expenses: IExpense[];

We initialized the service class and called getExpenses(), assigning the returned value to the expenses property. We removed the default value for expenses and set the value using the service class, as you can see in the constructor. This is how we move the logic of data retrieval to a service, and we can reuse that function across components.

Dependency Injection

Dependency Injection (DI) is a design pattern by which dependencies or services are passed to objects or clients that need them. The idea behind this pattern is to have a separate object create the required dependency, and pass it to the client. This makes a class or module to focus on the task it is designed for, and prevents side effects when replacing that dependency. For example, the home component's class depends on the ExpenseService service to retrieve data. We don't want it to be concerned with how to create this dependency, so we delegate that to a DI container that knows how to create services and pass them to clients that need them. Using this pattern helps achieve loose coupling and increases the modularity of a software application, thereby making it extensible.

DI is also at the core of Angular and can be used to provide components with the dependencies that they need. You will need to register the service with the Angular DI system so it knows how to inject it into components that need it. An injector is responsible for creating the dependencies and maintains a container of dependency instances that it reuses if needed. The injector knows how to find and create dependencies through an object called the provider. During the application's bootstrap process, Angular creates the needed injectors so you don't have to create them.

To make a service injectable, you need to register it with a provider. There are three ways you can do this:

  1. Register the service at the component level. To do this, you add the service to the array value of the providers option in the @Component() metadata. Using this approach, each time the component is created, a new instance of the service is created and injected into it.
    @Component({
      selector: "et-home",
      templateUrl: "./home.component.html",
      styleUrls: ["./home.component.css"],
      providers:  [ ExpenseService ]
    })
    
  2. Register the service at the module level. This means you register the service with a specific NgModule by specifying it in the providers option of the @NgModule() metadata. With this approach, a single instance of the service is injected into clients that need it. For example, if the home and briefing-cards components need the same service and that service is registered at the module level, the same instance of that service is injected into the instance of home and briefing-cards.
    @NgModule({
      providers: [ ExpenseService ],
     ...
    })
    
  3. Register the service at the root level. This means that a single instance of the service is shared across the app. To do this, you register the service using the @Injectable() decorator in the definition of that service.
    @Injectable({
     providedIn: 'root',
    })
    

You can use the CLI to generate services. Using the CLI will create a service class and register it using the root provider by default. To use the CLI, you run the command ng generate service. For example, we could have done ng generate service expenses/expense to generate the ExpenseService registered with the root provider.

You're going to register the ExpenseService you created earlier, with the root provider.

Open the service file and add the statement below

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

@Injectable({
 providedIn: 'root',
})
export class ExpenseService {
  .......
}

With this code, you referenced @Injectable and used the decorator on the class definition.

For this service to be injected into the component, you specify it as a parameter in the component's constructor. Angular determines which dependencies a component needs by looking at the constructor parameter types. We'll update the home component constructor so that the ExpenseService service will be created and injected into the component.

Open src/app/home/home.component.ts and update the constructor definition as follows:

  constructor(expenseService: ExpenseService) {
    this._expenseService = expenseService;
    this.expenses = this._expenseService.getExpenses();
  }

When the component needs to be created and Angular discovers that the component has a dependency on a service, it first checks if the injector has any existing instances of that service. If an instance of that service doesn't yet exist, the injector makes one using the registered provider, then adds it to the injector before returning it. If an instance of the service already exists in the injector, that instance is returned. The component is then initialized using the returned service instance from the injector.

Test Your Work!

We've come far enough that we now need to run the app and see that the code we added works. Open the command line and run ng serve -o. This should start the application and open it in the browser.

angular-services-and-DI.png

Conclusion

In this article, you learned about dependency injection as a pattern and how to use it in Angular. We walked through an example by creating a service and having the component's class know how to create that service. Then I introduced you to dependency injection, which is one of the ways Angular can make your application modular, extensible, and loosely coupled. With it, you make your component's focus on the view and how to render data. We moved code that knows how to retrieve data and manipulate the data away from the component's logic, into a service, then used dependency injection to allow Angular to pass that service into the component. With this approach, we achieved separation of concern where:

  • The component focuses on the view and only changes when the view requirement changes.
  • The service focused on logic (such as data retrieval) and only changes when our data requirement changes.
  • The injector in Angular knows how to inject services based on how we've configured the service to be injectable.

In the next article, you'll learn how to make HTTP requests in Angular. Stay tuned!😉

The code for this article can be downloaded from GitHub. It's contained in the src-part-4 folder. If you have any questions, feel free to leave a comment or reach out to me on Twitter.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter Mbanugo is a software developer, tech writer, and maker of Hamoni Sync. He currently works with Field Intelligence, where he helps build logistic and supply chain apps. He's also a contributor to Hoodie and a member of the Offline-First community. You can follow him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.