5 lines
36 KiB
JSON
5 lines
36 KiB
JSON
{
|
|
"id": "tutorial/toh-pt4",
|
|
"title": "Add services",
|
|
"contents": "\n\n\n<div class=\"github-links\">\n <a href=\"https://github.com/angular/angular/edit/master/aio/content/tutorial/toh-pt4.md?message=docs%3A%20describe%20your%20change...\" aria-label=\"Suggest Edits\" title=\"Suggest Edits\"><i class=\"material-icons\" aria-hidden=\"true\" role=\"img\">mode_edit</i></a>\n</div>\n\n\n<div class=\"content\">\n <h1 id=\"add-services\">Add services<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#add-services\"><i class=\"material-icons\">link</i></a></h1>\n<p>The Tour of Heroes <code>HeroesComponent</code> is currently getting and displaying fake data.</p>\n<p>After the refactoring in this tutorial, <code>HeroesComponent</code> will be lean and focused on supporting the view.\nIt will also be easier to unit-test with a mock service.</p>\n<div class=\"alert is-helpful\">\n<p> For the sample application that this page describes, see the <live-example></live-example>.</p>\n</div>\n<h2 id=\"why-services\">Why services<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#why-services\"><i class=\"material-icons\">link</i></a></h2>\n<p>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.</p>\n<p>In this tutorial, you'll create a <code>HeroService</code> that all application classes can use to get heroes.\nInstead of creating that service with the <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new\"><code>new</code> keyword</a>,\nyou'll rely on Angular <a href=\"guide/dependency-injection\"><em>dependency injection</em></a>\nto inject it into the <code>HeroesComponent</code> constructor.</p>\n<p>Services are a great way to share information among classes that <em>don't know each other</em>.\nYou'll create a <code>MessageService</code> and inject it in two places.</p>\n<ol>\n<li>Inject in HeroService, which uses the service to send a message.</li>\n<li>Inject in MessagesComponent, which displays that message, and also displays the ID\nwhen the user clicks a hero.</li>\n</ol>\n<h2 id=\"create-the-heroservice\">Create the <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#create-the-heroservice\"><i class=\"material-icons\">link</i></a></h2>\n<p>Using the Angular CLI, create a service called <code>hero</code>.</p>\n<code-example language=\"sh\" class=\"code-shell\">\n ng generate service hero\n</code-example>\n<p>The command generates a skeleton <code>HeroService</code> class in <code>src/app/hero.service.ts</code> as follows:</p>\n<code-example path=\"toh-pt4/src/app/hero.service.1.ts\" region=\"new\" header=\"src/app/hero.service.ts (new service)\">\nimport { <a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a> } from '@angular/core';\n\n@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>({\n providedIn: 'root',\n})\nexport class HeroService {\n\n constructor() { }\n\n}\n\n</code-example>\n<h3 id=\"injectable-services\"><code>@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>()</code> services<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#injectable-services\"><i class=\"material-icons\">link</i></a></h3>\n<p>Notice that the new service imports the Angular <code><a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a></code> symbol and annotates\nthe class with the <code>@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>()</code> decorator. This marks the class as one that participates in the <em>dependency injection system</em>. The <code>HeroService</code> 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 <a href=\"tutorial/toh-pt4#inject-message-service\">it will soon</a>.</p>\n<p>The <code>@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>()</code> decorator accepts a metadata object for the service, the same way the <code>@<a href=\"api/core/Component\" class=\"code-anchor\">Component</a>()</code> decorator did for your component classes.</p>\n<h3 id=\"get-hero-data\">Get hero data<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#get-hero-data\"><i class=\"material-icons\">link</i></a></h3>\n<p>The <code>HeroService</code> could get hero data from anywhere—a web service, local storage, or a mock data source.</p>\n<p>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.</p>\n<p>The implementation in <em>this</em> tutorial will continue to deliver <em>mock heroes</em>.</p>\n<p>Import the <code>Hero</code> and <code>HEROES</code>.</p>\n<code-example path=\"toh-pt4/src/app/hero.service.ts\" header=\"src/app/hero.service.ts\" region=\"import-heroes\">\nimport { Hero } from './hero';\nimport { HEROES } from './mock-heroes';\n\n</code-example>\n<p>Add a <code>getHeroes</code> method to return the <em>mock heroes</em>.</p>\n<code-example path=\"toh-pt4/src/app/hero.service.1.ts\" header=\"src/app/hero.service.ts\" region=\"getHeroes\">\ngetHeroes(): Hero[] {\n return HEROES;\n}\n\n</code-example>\n<a id=\"provide\"></a>\n<h2 id=\"provide-the-heroservice\">Provide the <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#provide-the-heroservice\"><i class=\"material-icons\">link</i></a></h2>\n<p>You must make the <code>HeroService</code> available to the dependency injection system\nbefore Angular can <em>inject</em> it into the <code>HeroesComponent</code> by registering a <em>provider</em>. A provider is something that can create or deliver a service; in this case, it instantiates the <code>HeroService</code> class to provide the service.</p>\n<p>To make sure that the <code>HeroService</code> can provide this service, register it\nwith the <em>injector</em>, which is the object that is responsible for choosing\nand injecting the provider where the application requires it.</p>\n<p>By default, the Angular CLI command <code>ng generate service</code> registers a provider with the <em>root injector</em> for your service by including provider metadata, that is <code>providedIn: 'root'</code> in the <code>@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>()</code> decorator.</p>\n<code-example>\n@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>({\n providedIn: 'root',\n})\n</code-example>\n<p>When you provide the service at the root level, Angular creates a single, shared instance of <code>HeroService</code> and injects into any class that asks for it.\nRegistering the provider in the <code>@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a></code> metadata also allows Angular to optimize an application by removing the service if it turns out not to be used after all.</p>\n<div class=\"alert is-helpful\">\n<p>To learn more about providers, see the <a href=\"guide/providers\">Providers section</a>.\nTo learn more about injectors, see the <a href=\"guide/dependency-injection\">Dependency Injection guide</a>.</p>\n</div>\n<p>The <code>HeroService</code> is now ready to plug into the <code>HeroesComponent</code>.</p>\n<div class=\"alert is-important\">\n<p>This is an interim code sample that will allow you to provide and use the <code>HeroService</code>. At this point, the code will differ from the <code>HeroService</code> in the <a href=\"tutorial/toh-pt4#final-code-review\">\"final code review\"</a>.</p>\n</div>\n<h2 id=\"update-heroescomponent\">Update <code>HeroesComponent</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#update-heroescomponent\"><i class=\"material-icons\">link</i></a></h2>\n<p>Open the <code>HeroesComponent</code> class file.</p>\n<p>Delete the <code>HEROES</code> import, because you won't need that anymore.\nImport the <code>HeroService</code> instead.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.ts\" header=\"src/app/heroes/heroes.component.ts (import HeroService)\" region=\"hero-service-import\">\nimport { HeroService } from '../hero.service';\n\n</code-example>\n<p>Replace the definition of the <code>heroes</code> property with a declaration.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.ts\" header=\"src/app/heroes/heroes.component.ts\" region=\"heroes\">\nheroes: Hero[] = [];\n\n</code-example>\n<a id=\"inject\"></a>\n<h3 id=\"inject-the-heroservice\">Inject the <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#inject-the-heroservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>Add a private <code>heroService</code> parameter of type <code>HeroService</code> to the constructor.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.1.ts\" header=\"src/app/heroes/heroes.component.ts\" region=\"ctor\">\nconstructor(private heroService: HeroService) {}\n\n</code-example>\n<p>The parameter simultaneously defines a private <code>heroService</code> property and identifies it as a <code>HeroService</code> injection site.</p>\n<p>When Angular creates a <code>HeroesComponent</code>, the <a href=\"guide/dependency-injection\">Dependency Injection</a> system\nsets the <code>heroService</code> parameter to the singleton instance of <code>HeroService</code>.</p>\n<h3 id=\"add-getheroes\">Add <code>getHeroes()</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#add-getheroes\"><i class=\"material-icons\">link</i></a></h3>\n<p>Create a method to retrieve the heroes from the service.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.1.ts\" header=\"src/app/heroes/heroes.component.ts\" region=\"getHeroes\">\ngetHeroes(): void {\n this.heroes = this.heroService.getHeroes();\n}\n\n</code-example>\n<a id=\"oninit\"></a>\n<h3 id=\"call-it-in-ngoninit\">Call it in <code>ngOnInit()</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#call-it-in-ngoninit\"><i class=\"material-icons\">link</i></a></h3>\n<p>While you could call <code>getHeroes()</code> in the constructor, that's not the best practice.</p>\n<p>Reserve the constructor for minimal initialization such as wiring constructor parameters to properties.\nThe constructor shouldn't <em>do anything</em>.\nIt certainly shouldn't call a function that makes HTTP requests to a remote server as a <em>real</em> data service would.</p>\n<p>Instead, call <code>getHeroes()</code> inside the <a href=\"guide/lifecycle-hooks\"><em>ngOnInit lifecycle hook</em></a> and\nlet Angular call <code>ngOnInit()</code> at an appropriate time <em>after</em> constructing a <code>HeroesComponent</code> instance.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.ts\" header=\"src/app/heroes/heroes.component.ts\" region=\"ng-on-init\">\nngOnInit() {\n this.getHeroes();\n}\n\n</code-example>\n<h3 id=\"see-it-run\">See it run<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#see-it-run\"><i class=\"material-icons\">link</i></a></h3>\n<p>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.</p>\n<h2 id=\"observable-data\">Observable data<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#observable-data\"><i class=\"material-icons\">link</i></a></h2>\n<p>The <code>HeroService.getHeroes()</code> method has a <em>synchronous signature</em>,\nwhich implies that the <code>HeroService</code> can fetch heroes synchronously.\nThe <code>HeroesComponent</code> consumes the <code>getHeroes()</code> result\nas if heroes could be fetched synchronously.</p>\n<code-example path=\"toh-pt4/src/app/heroes/heroes.component.1.ts\" header=\"src/app/heroes/heroes.component.ts\" region=\"get-heroes\">\nthis.heroes = this.heroService.getHeroes();\n\n</code-example>\n<p>This will not work in a real app.\nYou're getting away with it now because the service currently returns <em>mock heroes</em>.\nBut soon the application will fetch heroes from a remote server,\nwhich is an inherently <em>asynchronous</em> operation.</p>\n<p>The <code>HeroService</code> must wait for the server to respond,\n<code>getHeroes()</code> cannot return immediately with hero data,\nand the browser will not block while the service waits.</p>\n<p><code>HeroService.getHeroes()</code> must have an <em>asynchronous signature</em> of some kind.</p>\n<p>In this tutorial, <code>HeroService.getHeroes()</code> will return an <code>Observable</code>\nbecause it will eventually use the Angular <code>HttpClient.get</code> method to fetch the heroes\nand <a href=\"guide/http\"><code>HttpClient.get()</code> returns an <code>Observable</code></a>.</p>\n<h3 id=\"observable-heroservice\">Observable <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#observable-heroservice\"><i class=\"material-icons\">link</i></a></h3>\n<p><code>Observable</code> is one of the key classes in the <a href=\"https://rxjs.dev/\">RxJS library</a>.</p>\n<p>In a <a href=\"tutorial/toh-pt6\">later tutorial on HTTP</a>, you'll learn that Angular's <code><a href=\"api/common/http/HttpClient\" class=\"code-anchor\">HttpClient</a></code> methods return RxJS <code>Observable</code>s.\nIn this tutorial, you'll simulate getting data from the server with the RxJS <code>of()</code> function.</p>\n<p>Open the <code>HeroService</code> file and import the <code>Observable</code> and <code>of</code> symbols from RxJS.</p>\n<code-example path=\"toh-pt4/src/app/hero.service.ts\" header=\"src/app/hero.service.ts (Observable imports)\" region=\"import-observable\">\nimport { Observable, of } from 'rxjs';\n\n</code-example>\n<p>Replace the <code>getHeroes()</code> method with the following:</p>\n<code-example path=\"toh-pt4/src/app/hero.service.ts\" header=\"src/app/hero.service.ts\" region=\"getHeroes-1\">\ngetHeroes(): Observable<Hero[]> {\n const heroes = of(HEROES);\n return heroes;\n}\n\n</code-example>\n<p><code>of(HEROES)</code> returns an <code>Observable<Hero[]></code> that emits <em>a single value</em>, the array of mock heroes.</p>\n<div class=\"alert is-helpful\">\n<p>In the <a href=\"tutorial/toh-pt6\">HTTP tutorial</a>, you'll call <code>HttpClient.get<Hero[]>()</code> which also returns an <code>Observable<Hero[]></code> that emits <em>a single value</em>, an array of heroes from the body of the HTTP response.</p>\n</div>\n<h3 id=\"subscribe-in-heroescomponent\">Subscribe in <code>HeroesComponent</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#subscribe-in-heroescomponent\"><i class=\"material-icons\">link</i></a></h3>\n<p>The <code>HeroService.getHeroes</code> method used to return a <code>Hero[]</code>.\nNow it returns an <code>Observable<Hero[]></code>.</p>\n<p>You'll have to adjust to that difference in <code>HeroesComponent</code>.</p>\n<p>Find the <code>getHeroes</code> method and replace it with the following code\n(shown side-by-side with the previous version for comparison)</p>\n<code-tabs>\n\n <code-pane header=\"heroes.component.ts (Observable)\" path=\"toh-pt4/src/app/heroes/heroes.component.ts\" region=\"getHeroes\">\ngetHeroes(): void {\n this.heroService.getHeroes()\n .subscribe(heroes => this.heroes = heroes);\n}\n\n</code-pane>\n\n <code-pane header=\"heroes.component.ts (Original)\" path=\"toh-pt4/src/app/heroes/heroes.component.1.ts\" region=\"getHeroes\">\ngetHeroes(): void {\n this.heroes = this.heroService.getHeroes();\n}\n\n</code-pane>\n\n</code-tabs>\n<p><code>Observable.subscribe()</code> is the critical difference.</p>\n<p>The previous version assigns an array of heroes to the component's <code>heroes</code> property.\nThe assignment occurs <em>synchronously</em>, as if the server could return heroes instantly\nor the browser could freeze the UI while it waited for the server's response.</p>\n<p>That <em>won't work</em> when the <code>HeroService</code> is actually making requests of a remote server.</p>\n<p>The new version waits for the <code>Observable</code> to emit the array of heroes—which\ncould happen now or several minutes from now.\nThe <code>subscribe()</code> method passes the emitted array to the callback,\nwhich sets the component's <code>heroes</code> property.</p>\n<p>This asynchronous approach <em>will work</em> when\nthe <code>HeroService</code> requests heroes from the server.</p>\n<h2 id=\"show-messages\">Show messages<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#show-messages\"><i class=\"material-icons\">link</i></a></h2>\n<p>This section guides you through the following:</p>\n<ul>\n<li>adding a <code>MessagesComponent</code> that displays application messages at the bottom of the screen</li>\n<li>creating an injectable, app-wide <code>MessageService</code> for sending messages to be displayed</li>\n<li>injecting <code>MessageService</code> into the <code>HeroService</code></li>\n<li>displaying a message when <code>HeroService</code> fetches heroes successfully</li>\n</ul>\n<h3 id=\"create-messagescomponent\">Create <code>MessagesComponent</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#create-messagescomponent\"><i class=\"material-icons\">link</i></a></h3>\n<p>Use the CLI to create the <code>MessagesComponent</code>.</p>\n<code-example language=\"sh\" class=\"code-shell\">\n ng generate component messages\n</code-example>\n<p>The CLI creates the component files in the <code>src/app/messages</code> folder and declares the <code>MessagesComponent</code> in <code>AppModule</code>.</p>\n<p>Modify the <code>AppComponent</code> template to display the generated <code>MessagesComponent</code>.</p>\n<code-example header=\"src/app/app.component.html\" path=\"toh-pt4/src/app/app.component.html\">\n<h1>{{title}}</h1>\n<app-heroes></app-heroes>\n<app-messages></app-messages>\n\n\n</code-example>\n<p>You should see the default paragraph from <code>MessagesComponent</code> at the bottom of the page.</p>\n<h3 id=\"create-the-messageservice\">Create the <code>MessageService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#create-the-messageservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>Use the CLI to create the <code>MessageService</code> in <code>src/app</code>.</p>\n<code-example language=\"sh\" class=\"code-shell\">\n ng generate service message\n</code-example>\n<p>Open <code>MessageService</code> and replace its contents with the following.</p>\n<code-example header=\"src/app/message.service.ts\" path=\"toh-pt4/src/app/message.service.ts\">\nimport { <a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a> } from '@angular/core';\n\n@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>({\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</code-example>\n<p>The service exposes its cache of <code>messages</code> and two methods: one to <code>add()</code> a message to the cache and another to <code>clear()</code> the cache.</p>\n<a id=\"inject-message-service\"></a>\n<h3 id=\"inject-it-into-the-heroservice\">Inject it into the <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#inject-it-into-the-heroservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>In <code>HeroService</code>, import the <code>MessageService</code>.</p>\n<code-example header=\"src/app/hero.service.ts (import MessageService)\" path=\"toh-pt4/src/app/hero.service.ts\" region=\"import-message-service\">\nimport { MessageService } from './message.service';\n\n</code-example>\n<p>Modify the constructor with a parameter that declares a private <code>messageService</code> property.\nAngular will inject the singleton <code>MessageService</code> into that property\nwhen it creates the <code>HeroService</code>.</p>\n<code-example path=\"toh-pt4/src/app/hero.service.ts\" header=\"src/app/hero.service.ts\" region=\"ctor\">\nconstructor(private messageService: MessageService) { }\n\n</code-example>\n<div class=\"alert is-helpful\">\n<p>This is a typical \"<em>service-in-service</em>\" scenario:\nyou inject the <code>MessageService</code> into the <code>HeroService</code> which is injected into the <code>HeroesComponent</code>.</p>\n</div>\n<h3 id=\"send-a-message-from-heroservice\">Send a message from <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#send-a-message-from-heroservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>Modify the <code>getHeroes()</code> method to send a message when the heroes are fetched.</p>\n<code-example path=\"toh-pt4/src/app/hero.service.ts\" header=\"src/app/hero.service.ts\" region=\"getHeroes\">\ngetHeroes(): Observable<Hero[]> {\n const heroes = of(HEROES);\n this.messageService.add('HeroService: fetched heroes');\n return heroes;\n}\n\n</code-example>\n<h3 id=\"display-the-message-from-heroservice\">Display the message from <code>HeroService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#display-the-message-from-heroservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>The <code>MessagesComponent</code> should display all messages,\nincluding the message sent by the <code>HeroService</code> when it fetches heroes.</p>\n<p>Open <code>MessagesComponent</code> and import the <code>MessageService</code>.</p>\n<code-example header=\"src/app/messages/messages.component.ts (import MessageService)\" path=\"toh-pt4/src/app/messages/messages.component.ts\" region=\"import-message-service\">\nimport { MessageService } from '../message.service';\n\n</code-example>\n<p>Modify the constructor with a parameter that declares a <strong>public</strong> <code>messageService</code> property.\nAngular will inject the singleton <code>MessageService</code> into that property\nwhen it creates the <code>MessagesComponent</code>.</p>\n<code-example path=\"toh-pt4/src/app/messages/messages.component.ts\" header=\"src/app/messages/messages.component.ts\" region=\"ctor\">\nconstructor(public messageService: MessageService) {}\n\n</code-example>\n<p>The <code>messageService</code> property <strong>must be public</strong> because you're going to bind to it in the template.</p>\n<div class=\"alert is-important\">\n<p>Angular only binds to <em>public</em> component properties.</p>\n</div>\n<h3 id=\"bind-to-the-messageservice\">Bind to the <code>MessageService</code><a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#bind-to-the-messageservice\"><i class=\"material-icons\">link</i></a></h3>\n<p>Replace the CLI-generated <code>MessagesComponent</code> template with the following.</p>\n<code-example header=\"src/app/messages/messages.component.html\" path=\"toh-pt4/src/app/messages/messages.component.html\">\n<div *<a href=\"api/common/NgIf\" class=\"code-anchor\">ngIf</a>=\"messageService.messages.length\">\n\n <h2>Messages</h2>\n <button class=\"clear\"\n (click)=\"messageService.clear()\">Clear messages</button>\n <div *<a href=\"api/common/NgForOf\" class=\"code-anchor\">ngFor</a>='let message of messageService.messages'> {{message}} </div>\n\n</div>\n\n\n</code-example>\n<p>This template binds directly to the component's <code>messageService</code>.</p>\n<ul>\n<li>The <code>*<a href=\"api/common/NgIf\" class=\"code-anchor\">ngIf</a></code> only displays the messages area if there are messages to show.</li>\n</ul>\n<ul>\n<li>An <code>*<a href=\"api/common/NgForOf\" class=\"code-anchor\">ngFor</a></code> presents the list of messages in repeated <code><div></code> elements.</li>\n</ul>\n<ul>\n<li>An Angular <a href=\"guide/event-binding\">event binding</a> binds the button's click event\nto <code>MessageService.clear()</code>.</li>\n</ul>\n<p>The messages will look better when you add the private CSS styles to <code>messages.component.css</code>\nas listed in one of the <a href=\"tutorial/toh-pt4#final-code-review\">\"final code review\"</a> tabs below.</p>\n<h2 id=\"add-additional-messages-to-hero-service\">Add additional messages to hero service<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#add-additional-messages-to-hero-service\"><i class=\"material-icons\">link</i></a></h2>\n<p>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 <a href=\"tutorial/toh-pt5\">Routing</a>.</p>\n<code-example header=\"src/app/heroes/heroes.component.ts\" path=\"toh-pt4/src/app/heroes/heroes.component.ts\">\nimport { <a href=\"api/core/Component\" class=\"code-anchor\">Component</a>, <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> } from '@angular/core';\n\nimport { Hero } from '../hero';\nimport { HeroService } from '../hero.service';\nimport { MessageService } from '../message.service';\n\n@<a href=\"api/core/Component\" class=\"code-anchor\">Component</a>({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> {\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</code-example>\n<p>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 <strong>Clear messages</strong> button to clear the message history.</p>\n<a id=\"final-code-review\"></a>\n<h2 id=\"final-code-review\">Final code review<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#final-code-review\"><i class=\"material-icons\">link</i></a></h2>\n<p>Here are the code files discussed on this page.</p>\n<code-tabs>\n\n <code-pane header=\"src/app/hero.service.ts\" path=\"toh-pt4/src/app/hero.service.ts\">\nimport { <a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a> } 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@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>({\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</code-pane>\n\n <code-pane header=\"src/app/message.service.ts\" path=\"toh-pt4/src/app/message.service.ts\">\nimport { <a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a> } from '@angular/core';\n\n@<a href=\"api/core/Injectable\" class=\"code-anchor\">Injectable</a>({\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</code-pane>\n\n <code-pane header=\"src/app/heroes/heroes.component.ts\" path=\"toh-pt4/src/app/heroes/heroes.component.ts\">\nimport { <a href=\"api/core/Component\" class=\"code-anchor\">Component</a>, <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> } from '@angular/core';\n\nimport { Hero } from '../hero';\nimport { HeroService } from '../hero.service';\nimport { MessageService } from '../message.service';\n\n@<a href=\"api/core/Component\" class=\"code-anchor\">Component</a>({\n selector: 'app-heroes',\n templateUrl: './heroes.component.html',\n styleUrls: ['./heroes.component.css']\n})\nexport class HeroesComponent implements <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> {\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</code-pane>\n\n <code-pane header=\"src/app/messages/messages.component.ts\" path=\"toh-pt4/src/app/messages/messages.component.ts\">\nimport { <a href=\"api/core/Component\" class=\"code-anchor\">Component</a>, <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> } from '@angular/core';\nimport { MessageService } from '../message.service';\n\n@<a href=\"api/core/Component\" class=\"code-anchor\">Component</a>({\n selector: 'app-messages',\n templateUrl: './messages.component.html',\n styleUrls: ['./messages.component.css']\n})\nexport class MessagesComponent implements <a href=\"api/core/OnInit\" class=\"code-anchor\">OnInit</a> {\n\n constructor(public messageService: MessageService) {}\n\n ngOnInit() {\n }\n\n}\n\n\n</code-pane>\n\n <code-pane header=\"src/app/messages/messages.component.html\" path=\"toh-pt4/src/app/messages/messages.component.html\">\n<div *<a href=\"api/common/NgIf\" class=\"code-anchor\">ngIf</a>=\"messageService.messages.length\">\n\n <h2>Messages</h2>\n <button class=\"clear\"\n (click)=\"messageService.clear()\">Clear messages</button>\n <div *<a href=\"api/common/NgForOf\" class=\"code-anchor\">ngFor</a>='let message of messageService.messages'> {{message}} </div>\n\n</div>\n\n\n</code-pane>\n\n <code-pane header=\"src/app/messages/messages.component.css\" path=\"toh-pt4/src/app/messages/messages.component.css\">\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</code-pane>\n\n <code-pane header=\"src/app/app.module.ts\" path=\"toh-pt4/src/app/app.module.ts\">\nimport { <a href=\"api/platform-browser/BrowserModule\" class=\"code-anchor\">BrowserModule</a> } from '@angular/platform-browser';\nimport { <a href=\"api/core/NgModule\" class=\"code-anchor\">NgModule</a> } from '@angular/core';\nimport { <a href=\"api/forms/FormsModule\" class=\"code-anchor\">FormsModule</a> } 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@<a href=\"api/core/NgModule\" class=\"code-anchor\">NgModule</a>({\n declarations: [\n AppComponent,\n HeroesComponent,\n HeroDetailComponent,\n MessagesComponent\n ],\n imports: [\n <a href=\"api/platform-browser/BrowserModule\" class=\"code-anchor\">BrowserModule</a>,\n <a href=\"api/forms/FormsModule\" class=\"code-anchor\">FormsModule</a>\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</code-pane>\n\n <code-pane header=\"src/app/app.component.html\" path=\"toh-pt4/src/app/app.component.html\">\n<h1>{{title}}</h1>\n<app-heroes></app-heroes>\n<app-messages></app-messages>\n\n\n</code-pane>\n\n</code-tabs>\n<h2 id=\"summary\">Summary<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"tutorial/toh-pt4#summary\"><i class=\"material-icons\">link</i></a></h2>\n<ul>\n<li>You refactored data access to the <code>HeroService</code> class.</li>\n<li>You registered the <code>HeroService</code> as the <em>provider</em> of its service at the root level so that it can be injected anywhere in the app.</li>\n<li>You used <a href=\"guide/dependency-injection\">Angular Dependency Injection</a> to inject it into a component.</li>\n<li>You gave the <code>HeroService</code> <em>get data</em> method an asynchronous signature.</li>\n<li>You discovered <code>Observable</code> and the RxJS <em>Observable</em> library.</li>\n<li>You used RxJS <code>of()</code> to return an observable of mock heroes (<code>Observable<Hero[]></code>).</li>\n<li>The component's <code>ngOnInit</code> lifecycle hook calls the <code>HeroService</code> method, not the constructor.</li>\n<li>You created a <code>MessageService</code> for loosely-coupled communication between classes.</li>\n<li>The <code>HeroService</code> injected into a component is created with another injected service,\n<code>MessageService</code>.</li>\n</ul>\n\n \n</div>\n\n<!-- links to this doc:\n - errors/NG0201\n - guide/example-apps-list\n - guide/lifecycle-hooks\n - guide/router-tutorial-toh\n - guide/singleton-services\n - tutorial/toh-pt5\n-->\n<!-- links from this doc:\n - api/common/NgForOf\n - api/common/NgIf\n - api/common/http/HttpClient\n - api/core/Component\n - api/core/Injectable\n - api/core/NgModule\n - api/core/OnInit\n - api/forms/FormsModule\n - api/platform-browser/BrowserModule\n - guide/dependency-injection\n - guide/event-binding\n - guide/http\n - guide/lifecycle-hooks\n - guide/providers\n - tutorial/toh-pt4#add-additional-messages-to-hero-service\n - tutorial/toh-pt4#add-getheroes\n - tutorial/toh-pt4#add-services\n - tutorial/toh-pt4#bind-to-the-messageservice\n - tutorial/toh-pt4#call-it-in-ngoninit\n - tutorial/toh-pt4#create-messagescomponent\n - tutorial/toh-pt4#create-the-heroservice\n - tutorial/toh-pt4#create-the-messageservice\n - tutorial/toh-pt4#display-the-message-from-heroservice\n - tutorial/toh-pt4#final-code-review\n - tutorial/toh-pt4#get-hero-data\n - tutorial/toh-pt4#inject-it-into-the-heroservice\n - tutorial/toh-pt4#inject-message-service\n - tutorial/toh-pt4#inject-the-heroservice\n - tutorial/toh-pt4#injectable-services\n - tutorial/toh-pt4#observable-data\n - tutorial/toh-pt4#observable-heroservice\n - tutorial/toh-pt4#provide-the-heroservice\n - tutorial/toh-pt4#see-it-run\n - tutorial/toh-pt4#send-a-message-from-heroservice\n - tutorial/toh-pt4#show-messages\n - tutorial/toh-pt4#subscribe-in-heroescomponent\n - tutorial/toh-pt4#summary\n - tutorial/toh-pt4#update-heroescomponent\n - tutorial/toh-pt4#why-services\n - tutorial/toh-pt5\n - tutorial/toh-pt6\n - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new\n - https://github.com/angular/angular/edit/master/aio/content/tutorial/toh-pt4.md?message=docs%3A%20describe%20your%20change...\n - https://rxjs.dev/\n-->"
|
|
} |