6. Services

The Services are one suitable solution that provides you specific options and that can be used in different components separately. In this chapter, you will create own service. This will happen through DI (Dependency Injection). There are different approaches to create services. We will demonstrate one of the popular approaches and after that we will create a service for the started example of Heroes.

The whole picture

With services we can easily share some information with different classes without they knowing about each other.

How to create an own service

There are 3 important steps to create an Angular Service.

The app.service.ts example:

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

@Injectable()
export class MyService {  
    //Do Something
    MyMethod() {}
}

Now. How to include the service to one specific class

Here we should follow 4 important steps.

The app.component.ts example:

import { Component } from '@angular/core';
import { MyService } from './app.service';

@Component({
    selector: 'test-app',
    templateUrl: './app/example.html',
    providers: [MyService]
})

export class AppComponent {
    result: any = null;

    constructor(private _myService: MyService) {  
    }

    onClickMe() {    
        this.result = this._myService.MyMethod();    
    }
}

Continue with the main goal - Heroes project.

We will use the familiar approach with commands through the 'terminal' to create a service named hero.

ng generate service hero

Build the HeroService

This command (ng generate service hero) will generate a HeroService class in the src/app/hero.service.ts. It should look as follows:

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

@Injectable()
export class HeroService {

    constructor() { }

}

@Injectable() services

You could see that the Angular @Injectable decorator is added into this file. This decorator tells Angular that this service might itself have injected dependencies.

Now we will create the method for this service.

Implement getHeroes() method into the HeroService. In this way we could get data from anywhere - a web service, local storage, or a mock data source.

This method will deliver the mock heroes. Import the Hero class and HEROES list.

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

Now add the getHeroes() method as follow:

getHeroes(): Hero[] {
    return HEROES;
}

Add HeroService to the "providers" member

It is almost done. We need to include our service to the @NgModule.providers array of the AppModule in the app.module.ts file:

providers: [
    HeroService
],

We can use the Angular command to do this instead of us:

ng generate service hero --module=app

Include the HeroService to the HeroesComponent

Import the HeroService into the HeroesComponent.

import { HeroService } from '../hero.service';

Now we can remove the HEROES import because we do not need it anymore. We will get all needed data from the HeroService. Also, replace the definition of the heroes property with a simple declaration.

heroes: Hero[];

Define private service property

We should include a private heroService property of type HeroService to the constructor.

constructor(private heroService: HeroService) { }

The DI system sets the heroService parameter as a singleton instance of the HeroService at the moment when Angular creates a HeroesComponent.

Create internal getHeroes() method

The best practices suggest creating a function that will retrieve the heroes from the service.

getHeroes(): void {
    this.heroes = this.heroService.getHeroes();
}

Invoke the getHeroes() method in the ngOnInit

It is possible to invoke this method into the constructor but this is not the best practice. That is used for the simple initialization such as wiring constructor parameters to properties. We will invoke this function inside the ngOnInit

Our "heroes.component.ts" file should look like this below:

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';

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

export class HeroesComponent implements OnInit {
  heroes: Hero[];

  constructor(private heroService: HeroService) { }

  ngOnInit() {
    this.getHeroes();
  }

  getHeroes(): void {
    this.heroes = this.heroService.getHeroes();
  }

  selectedHero: Hero;

  rowdetails(event: any): void {
    let args = event.args;
    let rowData = args.row.bounddata;
    this.selectedHero = { id: rowData.Id, name: rowData.Name };
  }
}

After we save these changes our solution still will work as before.

Observable data

The implementation of our service to fetch 'heroes' is synchronous. The HeroesComponent consumes the result of the getHeroes() method synchronously - this.heroes = this.heroService.getHeroes();. If we imagine the real case where the data is retrieved from the server, this fetching operation is asynchronous. In this case, it will be better to prepare our service to work asynchronously.

Prepare HeroService to work with Observable

Observable is one of the key classes in the RxJS library. In this paragraph we will use RxJS Observables and also, for the demo purpose to simulate this fetching data from the server we will use RxJS of() method. Including these new changes into the HeroService placed in src/app/hero.service.ts and import as follows:

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

The next step is to replace the current implementation of the getHeroes() method as follows:

getHeroes(): Observable<Hero[]> {
    return of(HEROES);
}

The of(HEROES) will return an observable from this type Observable<Hero[]>. This will throw an error because the heroes variable that should get this data is not of the same type.

Changes in the getHeroes() method

We should make changes in the getHeroes method of the HeroesComponent. Currently it returns data with wrong type - Observable<Hero[]> but it expect Hero[] type. For this purpose we should transform our implementation for this situation. We will use Observable.subscribe() which is totally different from the previous approach. Replace the getHeroes() method of the HeroesComponent as below:

getHeroes(): void {
    this.heroService.getHeroes()
      .subscribe(heroes => this.heroes = heroes);
  }

The page will load these records simultaneously now or just a moment after that. The big plus of the subscribe is that the data will be emitted when it is all ready.

Notification of different actions

We will repeat some of the actions to become more confident. We will add a new component MessagesComponent, new service MessageService and you will know how to inject one service into another.

The new component for notification - MessagesComponent

Again we will use the Angular's commands to create the MessagesComponent.

ng generate component messages

Everything should be bound automatically. Now we have new folder src/app/messages with the new component MessagesComponent. Open the AppComponent and add the selector of this component into the template of the app.component.html:

<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>

Creation the new service - MessageService

The next step is to create a MessageService in src/app. Again we will use a command to achieve this and use --module=app option to provide this service in the AppModule.

ng generate service message --module=app

After we have MessageServicecreated, we will implement two new methods there. The add() method will add a message and the clear() method which will clear our messages. Make the following changes:

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

@Injectable()
export class MessageService {
  messages: string[] = [];

  add(message: string) {
    this.messages.push(message);
  }

  clear() {
    this.messages = [];
  }
}

Implement MessageService into the HeroService

We will import the MessageService to the HeroService.

import { MessageService } from './message.service';

After that we should determine a property relevant to this service in the constructor of the HeroService.

constructor(private messageService: MessageService) { }

We now know how to inject one service MessageService into another HeroService which will be injected into the HeroesComponent. This is a typical "service-in-service" scenario.

Using the method from one service into another one

Modify the getHeroes() method of the HeroService that will show a message when the heroes are fetched.

getHeroes(): Observable<Hero[]> {
    this.messageService.add('HeroService: fetched heroes');
    return of(HEROES);
}

Show the generated message

The message that we create is collected into the messages property of the instance of the MessageService. In the next step we will import the MessageService into the MessagesComponent and we will repeat the previous steps. Import that service into the src/app/messages/messages.component.ts.

import { MessageService } from './message.service';

Add the public messageService property because it will be bound into the template.

constructor(public messageService: MessageService) {}

Transform the template

We will add a new jQWidgets - jqxButtonComponent. Need to follow this steps bellow:

Open the MessagesComponent template and replace it as following:

<div *ngIf="messageService.messages.length">

    <h2>Messages</h2>
    <jqxButton (click)="goBack()" [theme]="'material'">go back</jqxButton>
    <div *ngFor='let message of messageService.messages'> {{message}} </div>

</div>

We use the *ngIf and *ngFor Angular's directives to visualize the template of the MessagesComponent and also, event binding to the button to invoke MessageService.clear() method. The message can be stylized in suitable way to look better.

servicesstate

In conclusion