{ "id": "tutorial/toh-pt4", "title": "Add services", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

Add serviceslink

\n

The Tour of Heroes HeroesComponent is currently getting and displaying fake data.

\n

After the refactoring in this tutorial, HeroesComponent will be lean and focused on supporting the view.\nIt will also be easier to unit-test with a mock service.

\n
\n

For the sample application that this page describes, see the .

\n
\n

Why serviceslink

\n

Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data.\nThey should focus on presenting data and delegate data access to a service.

\n

In this tutorial, you'll create a HeroService that all application classes can use to get heroes.\nInstead of creating that service with the new keyword,\nyou'll rely on Angular dependency injection\nto inject it into the HeroesComponent constructor.

\n

Services are a great way to share information among classes that don't know each other.\nYou'll create a MessageService and inject it in two places.

\n
    \n
  1. Inject in HeroService, which uses the service to send a message.
  2. \n
  3. Inject in MessagesComponent, which displays that message, and also displays the ID\nwhen the user clicks a hero.
  4. \n
\n

Create the HeroServicelink

\n

Using the Angular CLI, create a service called hero.

\n\n ng generate service hero\n\n

The command generates a skeleton HeroService class in src/app/hero.service.ts as follows:

\n\nimport { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class HeroService {\n\n constructor() { }\n\n}\n\n\n

@Injectable() serviceslink

\n

Notice that the new service imports the Angular Injectable symbol and annotates\nthe class with the @Injectable() decorator. This marks the class as one that participates in the dependency injection system. The HeroService class is going to provide an injectable service, and it can also have its own injected dependencies.\nIt doesn't have any dependencies yet, but it will soon.

\n

The @Injectable() decorator accepts a metadata object for the service, the same way the @Component() decorator did for your component classes.

\n

Get hero datalink

\n

The HeroService could get hero data from anywhere—a web service, local storage, or a mock data source.

\n

Removing data access from components means you can change your mind about the implementation anytime, without touching any components.\nThey don't know how the service works.

\n

The implementation in this tutorial will continue to deliver mock heroes.

\n

Import the Hero and HEROES.

\n\nimport { Hero } from './hero';\nimport { HEROES } from './mock-heroes';\n\n\n

Add a getHeroes method to return the mock heroes.

\n\ngetHeroes(): Hero[] {\n return HEROES;\n}\n\n\n\n

Provide the HeroServicelink

\n

You must make the HeroService available to the dependency injection system\nbefore Angular can inject it into the HeroesComponent by registering a provider. A provider is something that can create or deliver a service; in this case, it instantiates the HeroService class to provide the service.

\n

To make sure that the HeroService can provide this service, register it\nwith the injector, which is the object that is responsible for choosing\nand injecting the provider where the application requires it.

\n

By default, the Angular CLI command ng generate service registers a provider with the root injector for your service by including provider metadata, that is providedIn: 'root' in the @Injectable() decorator.

\n\n@Injectable({\n providedIn: 'root',\n})\n\n

When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects into any class that asks for it.\nRegistering the provider in the @Injectable metadata also allows Angular to optimize an application by removing the service if it turns out not to be used after all.

\n
\n

To learn more about providers, see the Providers section.\nTo learn more about injectors, see the Dependency Injection guide.

\n
\n

The HeroService is now ready to plug into the HeroesComponent.

\n
\n

This is an interim code sample that will allow you to provide and use the HeroService. At this point, the code will differ from the HeroService in the \"final code review\".

\n
\n

Update HeroesComponentlink

\n

Open the HeroesComponent class file.

\n

Delete the HEROES import, because you won't need that anymore.\nImport the HeroService instead.

\n\nimport { HeroService } from '../hero.service';\n\n\n

Replace the definition of the heroes property with a declaration.

\n\nheroes: Hero[] = [];\n\n\n\n

Inject the HeroServicelink

\n

Add a private heroService parameter of type HeroService to the constructor.

\n\nconstructor(private heroService: HeroService) {}\n\n\n

The parameter simultaneously defines a private heroService property and identifies it as a HeroService injection site.

\n

When Angular creates a HeroesComponent, the Dependency Injection system\nsets the heroService parameter to the singleton instance of HeroService.

\n

Add getHeroes()link

\n

Create a method to retrieve the heroes from the service.

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

Call it in ngOnInit()link

\n

While you could call getHeroes() in the constructor, that's not the best practice.

\n

Reserve the constructor for minimal initialization such as wiring constructor parameters to properties.\nThe constructor shouldn't do anything.\nIt certainly shouldn't call a function that makes HTTP requests to a remote server as a real data service would.

\n

Instead, call getHeroes() inside the ngOnInit lifecycle hook and\nlet Angular call ngOnInit() at an appropriate time after constructing a HeroesComponent instance.

\n\nngOnInit() {\n this.getHeroes();\n}\n\n\n

See it runlink

\n

After the browser refreshes, the application should run as before,\nshowing a list of heroes and a hero detail view when you click on a hero name.

\n

Observable datalink

\n

The HeroService.getHeroes() method has a synchronous signature,\nwhich implies that the HeroService can fetch heroes synchronously.\nThe HeroesComponent consumes the getHeroes() result\nas if heroes could be fetched synchronously.

\n\nthis.heroes = this.heroService.getHeroes();\n\n\n

This will not work in a real app.\nYou're getting away with it now because the service currently returns mock heroes.\nBut soon the application will fetch heroes from a remote server,\nwhich is an inherently asynchronous operation.

\n

The HeroService must wait for the server to respond,\ngetHeroes() cannot return immediately with hero data,\nand the browser will not block while the service waits.

\n

HeroService.getHeroes() must have an asynchronous signature of some kind.

\n

In this tutorial, HeroService.getHeroes() will return an Observable\nbecause it will eventually use the Angular HttpClient.get method to fetch the heroes\nand HttpClient.get() returns an Observable.

\n

Observable HeroServicelink

\n

Observable is one of the key classes in the RxJS library.

\n

In a later tutorial on HTTP, you'll learn that Angular's HttpClient methods return RxJS Observables.\nIn this tutorial, you'll simulate getting data from the server with the RxJS of() function.

\n

Open the HeroService file and import the Observable and of symbols from RxJS.

\n\nimport { Observable, of } from 'rxjs';\n\n\n

Replace the getHeroes() method with the following:

\n\ngetHeroes(): Observable<Hero[]> {\n const heroes = of(HEROES);\n return heroes;\n}\n\n\n

of(HEROES) returns an Observable<Hero[]> that emits a single value, the array of mock heroes.

\n
\n

In the HTTP tutorial, you'll call HttpClient.get<Hero[]>() which also returns an Observable<Hero[]> that emits a single value, an array of heroes from the body of the HTTP response.

\n
\n

Subscribe in HeroesComponentlink

\n

The HeroService.getHeroes method used to return a Hero[].\nNow it returns an Observable<Hero[]>.

\n

You'll have to adjust to that difference in HeroesComponent.

\n

Find the getHeroes method and replace it with the following code\n(shown side-by-side with the previous version for comparison)

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

Observable.subscribe() is the critical difference.

\n

The previous version assigns an array of heroes to the component's heroes property.\nThe assignment occurs synchronously, as if the server could return heroes instantly\nor the browser could freeze the UI while it waited for the server's response.

\n

That won't work when the HeroService is actually making requests of a remote server.

\n

The new version waits for the Observable to emit the array of heroes—which\ncould happen now or several minutes from now.\nThe subscribe() method passes the emitted array to the callback,\nwhich sets the component's heroes property.

\n

This asynchronous approach will work when\nthe HeroService requests heroes from the server.

\n

Show messageslink

\n

This section guides you through the following:

\n\n

Create MessagesComponentlink

\n

Use the CLI to create the MessagesComponent.

\n\n ng generate component messages\n\n

The CLI creates the component files in the src/app/messages folder and declares the MessagesComponent in AppModule.

\n

Modify the AppComponent template to display the generated MessagesComponent.

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

You should see the default paragraph from MessagesComponent at the bottom of the page.

\n

Create the MessageServicelink

\n

Use the CLI to create the MessageService in src/app.

\n\n ng generate service message\n\n

Open MessageService and replace its contents with the following.

\n\nimport { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class MessageService {\n messages: string[] = [];\n\n add(message: string) {\n this.messages.push(message);\n }\n\n clear() {\n this.messages = [];\n }\n}\n\n\n\n

The service exposes its cache of messages and two methods: one to add() a message to the cache and another to clear() the cache.

\n\n

Inject it into the HeroServicelink

\n

In HeroService, import the MessageService.

\n\nimport { MessageService } from './message.service';\n\n\n

Modify the constructor with a parameter that declares a private messageService property.\nAngular will inject the singleton MessageService into that property\nwhen it creates the HeroService.

\n\nconstructor(private messageService: MessageService) { }\n\n\n
\n

This is a typical \"service-in-service\" scenario:\nyou inject the MessageService into the HeroService which is injected into the HeroesComponent.

\n
\n

Send a message from HeroServicelink

\n

Modify the getHeroes() method to send a message when the heroes are fetched.

\n\ngetHeroes(): Observable<Hero[]> {\n const heroes = of(HEROES);\n this.messageService.add('HeroService: fetched heroes');\n return heroes;\n}\n\n\n

Display the message from HeroServicelink

\n

The MessagesComponent should display all messages,\nincluding the message sent by the HeroService when it fetches heroes.

\n

Open MessagesComponent and import the MessageService.

\n\nimport { MessageService } from '../message.service';\n\n\n

Modify the constructor with a parameter that declares a public messageService property.\nAngular will inject the singleton MessageService into that property\nwhen it creates the MessagesComponent.

\n\nconstructor(public messageService: MessageService) {}\n\n\n

The messageService property must be public because you're going to bind to it in the template.

\n
\n

Angular only binds to public component properties.

\n
\n

Bind to the MessageServicelink

\n

Replace the CLI-generated MessagesComponent template with the following.

\n\n<div *ngIf=\"messageService.messages.length\">\n\n <h2>Messages</h2>\n <button class=\"clear\"\n (click)=\"messageService.clear()\">Clear messages</button>\n <div *ngFor='let message of messageService.messages'> {{message}} </div>\n\n</div>\n\n\n\n

This template binds directly to the component's messageService.

\n\n\n\n

The messages will look better when you add the private CSS styles to messages.component.css\nas listed in one of the \"final code review\" tabs below.

\n

Add additional messages to hero servicelink

\n

The following example shows how to send and display a message each time the user clicks on\na hero, showing a history of the user's selections. This will be helpful when you get to the\nnext section on Routing.

\n\nimport { Component, OnInit } from '@angular/core';\n\nimport { Hero } from '../hero';\nimport { HeroService } from '../hero.service';\nimport { MessageService } from '../message.service';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements OnInit {\n\n selectedHero?: Hero;\n\n heroes: Hero[] = [];\n\n constructor(private heroService: HeroService, private messageService: MessageService) { }\n\n ngOnInit() {\n this.getHeroes();\n }\n\n onSelect(hero: Hero): void {\n this.selectedHero = hero;\n this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);\n }\n\n getHeroes(): void {\n this.heroService.getHeroes()\n .subscribe(heroes => this.heroes = heroes);\n }\n}\n\n\n\n

Refresh the browser to see the list of heroes, and scroll to the bottom to see the\nmessages from the HeroService. Each time you click a hero, a new message appears to record\nthe selection. Use the Clear messages button to clear the message history.

\n\n

Final code reviewlink

\n

Here are the code files discussed on this page.

\n\n\n \nimport { Injectable } from '@angular/core';\n\nimport { Observable, of } from 'rxjs';\n\nimport { Hero } from './hero';\nimport { HEROES } from './mock-heroes';\nimport { MessageService } from './message.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class HeroService {\n\n constructor(private messageService: MessageService) { }\n\n getHeroes(): Observable<Hero[]> {\n const heroes = of(HEROES);\n this.messageService.add('HeroService: fetched heroes');\n return heroes;\n }\n}\n\n\n\n \nimport { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class MessageService {\n messages: string[] = [];\n\n add(message: string) {\n this.messages.push(message);\n }\n\n clear() {\n this.messages = [];\n }\n}\n\n\n\n\n \nimport { Component, OnInit } from '@angular/core';\n\nimport { Hero } from '../hero';\nimport { HeroService } from '../hero.service';\nimport { MessageService } from '../message.service';\n\n@Component({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements OnInit {\n\n selectedHero?: Hero;\n\n heroes: Hero[] = [];\n\n constructor(private heroService: HeroService, private messageService: MessageService) { }\n\n ngOnInit() {\n this.getHeroes();\n }\n\n onSelect(hero: Hero): void {\n this.selectedHero = hero;\n this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);\n }\n\n getHeroes(): void {\n this.heroService.getHeroes()\n .subscribe(heroes => this.heroes = heroes);\n }\n}\n\n\n\n\n \nimport { Component, OnInit } from '@angular/core';\nimport { MessageService } from '../message.service';\n\n@Component({\n selector: 'app-messages',\n templateUrl: './messages.component.html',\n styleUrls: ['./messages.component.css']\n})\nexport class MessagesComponent implements OnInit {\n\n constructor(public messageService: MessageService) {}\n\n ngOnInit() {\n }\n\n}\n\n\n\n\n \n<div *ngIf=\"messageService.messages.length\">\n\n <h2>Messages</h2>\n <button class=\"clear\"\n (click)=\"messageService.clear()\">Clear messages</button>\n <div *ngFor='let message of messageService.messages'> {{message}} </div>\n\n</div>\n\n\n\n\n \n/* MessagesComponent's private CSS styles */\nh2 {\n color: #A80000;\n font-family: Arial, Helvetica, sans-serif;\n font-weight: lighter;\n}\n\n.clear {\n color: #333;\n background-color: #eee;\n margin-bottom: 12px;\n padding: 1rem;\n border-radius: 4px;\n font-size: 1rem;\n}\n.clear:hover {\n color: white;\n background-color: #42545C;\n}\n\n\n\n\n \nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { AppComponent } from './app.component';\nimport { HeroesComponent } from './heroes/heroes.component';\nimport { HeroDetailComponent } from './hero-detail/hero-detail.component';\nimport { MessagesComponent } from './messages/messages.component';\n\n@NgModule({\n declarations: [\n AppComponent,\n HeroesComponent,\n HeroDetailComponent,\n MessagesComponent\n ],\n imports: [\n BrowserModule,\n FormsModule\n ],\n providers: [\n // no need to place any providers due to the `providedIn` flag...\n ],\n bootstrap: [ AppComponent ]\n})\nexport class AppModule { }\n\n\n\n\n \n<h1>{{title}}</h1>\n<app-heroes></app-heroes>\n<app-messages></app-messages>\n\n\n\n\n\n

Summarylink

\n\n\n \n
\n\n\n" }