Building Complex Business Logic With Angular 2 Services

In a traditional, multi-layered architecture, front-end side of the application occupies two of them: presentation and a part of business logic layer. Components, which are the primary construction blocks in Angular 2, make up the presentation tier, while services pertain to the business-logic one. In this article we dive deep into the world of Angular 2 services and show how we can easily use them to implement business logic decoupled from the presentation layer in a complex application.

What are Angular 2 services

Angular 2 Services are classes that implement some piece of business logic in our application that can be used by other elements such as components, models and other services. They can also serve as providers of utilities (e.g. logging) for other pieces of the project. Finally, as will be shown later in the article, they can be really helpful in extracting common code, even from the presentation tier.

Sample service could look something like that:

export class ShoutingService {

  public shout(message: string): string {
    return message.toUpperCase();

Since this is just a sample service, it is really simple and exposes just one method that takes a string and converts it to uppercase. Note the @Injectable() decorator – it is required for Angular’s Dependency Injection (DI) framework to inject dependencies. It is not mandatory in every case but it’s good practice to consistently mark your services this way. More on this topic can be found in Angular’s docs.

What’s really cool about Angular 2 services, is that – thanks to its Dependency Injection framework – they can be easily used by other pieces of our app. Moreover, Angular ensures that services are singletons, i.e. that each service consumer will get the same instance of the service class.

How do we use the service created above? Thanks to the aforementioned DI framework it’s really easy:

import {ShoutingService} from "./path/to/service/ShoutingService.ts"; // <-- 1) Import the service

  selector: "greeter",
  template: "<button (click)='greet()'>Greet</button>";
export class GreeterComponent {

  constructor(private shoutingService: ShoutingService /* <-- 2) add it as dependency in the constructor */) {

  public greet(): void {
    alert(this.shoutingService.shout("hello there, stranger!"));

There are two things we need to do:

  1. Import the appropriate class into our component file.
  2. Add it as dependency in our component’s constructor.

We also need to add appropriate provider in a module or component where we want to use our service. You can find out more on how to add providers in the abovementioned article on Dependency Injection.

Angular 1 services vs Angular 2 services

While in Angular 1 services required special syntax (ngModule.service(), .factory() or .provider()), services in Angular 2 are actually just plain classes. Only after they’ve been marked with the @Injectable() decorator and provided in a module or component, do they become available for injection into other parts of our application.

Benefits of using Angular 2 services

All right, we know how to create a service and inject it into our component. But why should we actually use services? Can’t we solve any problem with just components? Below, we list some of the most common cases where not only is the use of services justified, but is preferable to using other means.

Extracting common functionality

Even though services are traditionally tied to the business logic layer, they can be successfully used to help organize presentation layer as well. Say we’re developing an online bookstore app and in our available books list we want to dynamically add background color to each row based on book’s category. If the book is on sale, then the standard color is overriden.

In our book list component we could have the following code:

private colorsForBookCategory: Map<BookType, string> = new Map([
  [BookType.ACTION, "orange"],
  [BookType.DRAMA, "purple"],
  [BookType.SCIFI, "cyan"],
  /* many more here */
private onSaleColor: "red";
private getBackgroundColorFor(book: Book): string {
  if (book.onSale) {
    return this.onSaleColor;
  } else {
    return colorsForBookCategory.get(book.type);

These color rules (and hence our code) could, of course, be more complex in a real app. Now imagine, we’d want to add the same feature to our book description page. Copying this code would mean commiting one of the most serious (if not the most serious) crime in software development: duplicating code. Let’s say we want to add the same features to other components and also that the color rules change – it becomes a nightmare to maintain very fast.

Thankfully, we can create a service, say BookColorService and put all of this code there. Then any component that needs this functionality can easily use our service.

Separating levels of abstraction

Using services can help us separate levels of abstraction and stick to the Single Responsibility Principle. Let us continue with the bookstore app example. This time we’ve got a SpecialOffersListComponent that displays books that are currently on sale. It might look something like this:

/* Imports here... */

  selector: "special-offers-list"
  template: `

<div class="table-header">
      <span class="column-name">Author</span>
      <span class="column-name">Title</span>
      <span class="column-name">Genre</span>
      <span class="column-name">Price</span>


<div *ngFor="let book of booksOnSale" class="table-row">
      <span class="column-entry"> {{ }} </span>
      <span class="column-entry"> {{ book.title }} </span>
      <span class="column-entry"> {{ book.type }} </span>
      <span class="column-entry"> {{ book.price | currency:'USD':true:'1.2-2'}} </span>

export class SpecialOffersListComponent {
  private booksOnSale: Book[];

  constructor(private http: Http) {
      .subscribe((response: Response) => {
        this.booksOnSale = this.parse(response.json())
          .filter((book: Book) => book.onSale);

  private parse(booksJSON: Object): Book[] {
    /* logic for parsing JSON with an array of Book objects we get in the response to our HTTP GET request */


There’s quite a lot going on here. Our SpecialOffersListComponent, whose main responsibility is to display the books on sale, is engaged in some pretty low-level stuff. It performs the HTTP request itself (using built-in Angular 2 Http service), parses the received JSON into valid Typescript object and applies some additional logic to get only particular elements of the whole response. BTW: If you’re petrified by the subscribe() syntax, be sure to check out our post on observables in Angular 2, as they are pervasive across the framework. We can clearly see different levels of abstraction interleaving here. We could abstract these low-level operations into a service or – better – even two services which would result in a cleaner design. In our attempt to clean things up we could go with the following:

/* Inside SpecialOffersListComponent file */
constructor(private bookService: BookService) {
  this.booksOnSale = this.bookService.getBooksOnSale();

/* Inside BookService file */
export class BookService {

  constructor(private http: Http, private reponseParserService: ReponseParserService) {}

  public getBooksOnSale(): Book[] {
      .subscribe((response: Response) => {
        this.booksOnSale = this.reponseParserService.parse(response.json())
          .filter((book: Book) => book.onSale);

/* Inside ReponseParserService file */
export class ReponseParserService {

  public parse(responseJSON: Object): any {
     /* logic for parsing JSONs into appropriate application model objects */

Now both parse() and filter() were moved into the BookService. Since parsing JSONs into Typescript objects is quite a general activity and would be used extensively throughout our application, we could abstract it into a general ResponseParser service and use it inside BookService as well as in other services performing HTTP requests. Actually, we use such a general parsing service in our Angular 2 apps. If you want to find out more, be sure to check out our article on parsing JSONs into Typescript objects. All in all, we’ll end up with a cleaner design, obeying Single Responsibility Principle, without mixing various levels of abstraction.

Easier testing

Abstracting out functionalities into services usually makes it much easier to test them in comparison with components. Sticking to our bookstore example and SpecialOffersListComponent, we can separately unit-test individual service methods. This approach allows us to use mock data easily and avoid writing more bothersome component tests. Moreover, testing services this way allows us to keep the number of cumbersome end-to-end tests low. If you’re interested in e2e, check out our article on end-to-end testing Angular 2 applications with Protractor.

Decreased Coupling

Thanks to Angular’s Dependency Injection framework, it is really easy to inject services into our code. So separating our application into loosely coupled services instead of putting all the logic into components is far from troublesome.

Built-in Angular 2 services

Angular comes with a library of built-in services we can use right away in our applications. Here we mention just some of them but a full and exhaustive list can be found in Angular official documentation. Here are just some examples of built-in services, organized into groups:

  • HTTP connections – Http service (in @angular/http module) – contains logic for making HTTP requests (GET, POST, PUT, DELETE, etc.) easily. Example of its usage was shown in our SpecialOffersListComponent
  • Routing – Router service provides means for routing in our application (both HTML5 and hash routing) while Location service can be used to interact with the browser’s URL.
  • Forms – FormBuilder service helps building complex forms.
  • DOM Access – ElementRef service allows access to our component’s native element in the DOM.


While components remain the main element of Angular 2 applications, it’s hard to imagine building successful, large-scale solutions without the aid of services. They help us avoid code duplication and separate levels of abstraction. Moreover, they facilitate testing and overall lead to cleaner, more maintainable code.

comments: 0