Creating an Angular 2 Injectable Service

This guide was written for Angular 2 version: 2.0.0-rc.4

Services are an abstraction layer that handles an application's business logic, which usually includes communicating with a backend and parsing/returning data or datasets. In Angular 1.x, we had a few different ways of creating a service (.service(), .factory() and .provider()). For this guide we'll be comparing just the .service() method against Angular 2 services, if you want to dive into the service versus factory explanation, check out my blog post on it.

Table of contents

Angular 1.x

In Angular 1.x, we can create a Service using the .service() API. Let's dive in!

Service definition

All we need is a good old ES2015 class, from which we'll be statically returning an Array of todo Objects. We'll move on to HTTP communication in later guides.

class TodoService {
  constructor() {}
  getTodos() {
    return [{
      "id": 1,
      "label": "delectus aut autem",
      "completed": false
    },{
      "id": 2,
      "label": "quis ut nam facilis et officia qui",
      "completed": false
    },{
      "id": 3,
      "label": "fugiat veniam minus",
      "completed": false
    },{
      "id": 4,
      "label": "et porro tempora",
      "completed": true
    },{
      "id": 5,
      "label": "laboriosam mollitia et enim quasi adipisci quia provident illum",
      "completed": false
    }];
  }
}

angular
  .module('app')
  .service('TodoService', TodoService);

We simply register the service with .service() and it's fully available inside the 'app' module. Any dependencies we want to inject into the service are to be bound inside the constructor and marked with $inject:

class TodoService {
  constructor($http) {
    this.$http = $http;
  }
  getTodos() {
    return [{..},{..},{..},{..},{..}];
  }
}

TodoService.$inject = ['$http'];

angular
  .module('app')
  .service('TodoService', TodoService);

Pretty simple here. Now, to use the Service inside a Controller for example, we need to utilize Angular's Dependency Injection (DI).

Service DI

const todo = {
  template: `
    <div>
      My Todo List:
      <ul>
        <li ng-repeat="todo in $ctrl.todos">
          {{ todo.label }}
        </li>
      </ul>
    </div>
  `,
  controller(TodoService) {
    $onInit() {
      this.todos = TodoService.getTodos();
    }
  }
};

The TodoService.getTodos(); above demonstrates a synchronous operation. For asynchronous operations, we'll return a promise and likely assign the this.todos inside the .then() promise response. However, we'll leave this for another guide on Services.

Notice how we're also using the $onInit lifecycle hook for the controller, which is the new and correct place for such logic.

Angular 2

Things are pretty much identical in Angular 2 – we also use ES2015 classes!

Service setup

Let's start with the ES2015 class and get it exported, adding the getTodos method to the constructor, which returns the Array of Objects:

export default class TodoService {
  constructor() {}
  getTodos(): array {
    return [{
      "id": 1,
      "label": "delectus aut autem",
      "completed": false
    },{
      "id": 2,
      "label": "quis ut nam facilis et officia qui",
      "completed": false
    },{
      "id": 3,
      "label": "fugiat veniam minus",
      "completed": false
    },{
      "id": 4,
      "label": "et porro tempora",
      "completed": true
    },{
      "id": 5,
      "label": "laboriosam mollitia et enim quasi adipisci quia provident illum",
      "completed": false
    }];
  }
}

Simple enough, what next? Dependency injection!

@Injectable() and DI

The next stage is using the @Injectable() decorator, which we import and simply decorate the class:

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

@Injectable()
export default class TodoService {
  constructor() {}
  getTodos(): array {
    return [{..},{..},{..},{..},{..}];
  }
}

Now we need to import the Service into our component, as well as the OnInit interface, which provides a hook named ngOnInit that we'll be using:

import {Component, OnInit} from '@angular/core';
import TodoService from './todo.service';

@Component({
  selector: 'todo',
  template: `
    <div>
      My Todo List:
      <ul>
        <li *ngFor="let todo of todos">
          {{ todo.label }}
        </li>
      </ul>
    </div>
  `
})
export default class CounterComponent implements OnInit {
  constructor() {}
}

So we import OnInit, and on the class export declare implements OnInit. Now, we'll move onto the constructor injection and assignment of the getTodos() service call:

import {Component, OnInit} from '@angular/core';
import TodoService from './todo.service';

@Component({
  selector: 'todo',
  template: `
    <div>
      ...
    </div>
  `
})
export default class CounterComponent implements OnInit {
  public todos: array;
  constructor(public todoService: TodoService) {}
  ngOnInit() {
    this.todos = this.todoService.getTodos();
  }
}

The constructor is the place to create bindings for injections, not to do any heavy lifting, which is why we implement the lifecycle hook ngOnInit. By using TypeScript, we can automatically bind TodoService to the constructor, which is essentially equivalent to this:

export default class CounterComponent implements OnInit {
  ...
  constructor(TodoService) {
    this.todoService = TodoService;
  }
  ...
}

There's just one step left, and that's registering the service inside the @Component. We do this through the providers Array:

import {Component, OnInit} from '@angular/core';
import TodoService from './todo.service';

@Component({
  selector: 'todo',
  template: `
    <div>
      ...
    </div>
  `,
  providers: [TodoService]
})
export default class CounterComponent implements OnInit {
  public todos: array;
  constructor(public todoService: TodoService) {}
  ngOnInit() {
    this.todos = this.todoService.getTodos();
  }
}

And that's it! There are a few more options for providers that allow us to globally inject services rather than on the component level. We'll discuss these in future guides.

Final code

Comments