From 8f084d721469b8331ccdf66038142c1bf82b56ab Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Thu, 31 Jan 2019 14:25:30 -0500 Subject: [PATCH] docs: clarify toh (#28571) PR Close #28571 --- .../src/app/heroes/heroes.component.css | 18 +- .../src/app/heroes/heroes.component.ts | 3 + .../src/app/heroes/heroes.component.css | 18 +- .../src/app/heroes/heroes.component.css | 20 +- .../toh-pt5/src/app/app-routing.module.1.ts | 13 + .../toh-pt5/src/app/app-routing.module.ts | 4 +- .../examples/toh-pt6/src/app/app.module.ts | 9 + .../examples/toh-pt6/src/app/hero.service.ts | 17 +- aio/content/tutorial/toh-pt1.md | 57 ++- aio/content/tutorial/toh-pt2.md | 36 +- aio/content/tutorial/toh-pt3.md | 2 +- aio/content/tutorial/toh-pt4.md | 146 +++---- aio/content/tutorial/toh-pt5.md | 273 +++++------- aio/content/tutorial/toh-pt6.md | 407 ++++++++---------- 14 files changed, 456 insertions(+), 567 deletions(-) create mode 100644 aio/content/examples/toh-pt5/src/app/app-routing.module.1.ts diff --git a/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.css b/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.css index 9121bec918..9759a4211b 100644 --- a/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.css +++ b/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.css @@ -1,8 +1,4 @@ /* HeroesComponent's private CSS styles */ -.selected { - background-color: #CFD8DC !important; - color: white; -} .heroes { margin: 0 0 2em 0; list-style-type: none; @@ -19,18 +15,18 @@ height: 1.6em; border-radius: 4px; } -.heroes li.selected:hover { - background-color: #BBD8DC !important; - color: white; -} .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } -.heroes .text { - position: relative; - top: -3px; +.heroes li.selected { + background-color: #CFD8DC; + color: white; +} +.heroes li.selected:hover { + background-color: #BBD8DC; + color: white; } .heroes .badge { display: inline-block; diff --git a/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.ts b/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.ts index f367a9baa0..5beecb0e7d 100644 --- a/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.ts +++ b/aio/content/examples/toh-pt2/src/app/heroes/heroes.component.ts @@ -34,4 +34,7 @@ export class HeroesComponent implements OnInit { this.selectedHero = hero; } // #enddocregion on-select +// #docregion component } +// #enddocregion component + diff --git a/aio/content/examples/toh-pt3/src/app/heroes/heroes.component.css b/aio/content/examples/toh-pt3/src/app/heroes/heroes.component.css index 9121bec918..9759a4211b 100644 --- a/aio/content/examples/toh-pt3/src/app/heroes/heroes.component.css +++ b/aio/content/examples/toh-pt3/src/app/heroes/heroes.component.css @@ -1,8 +1,4 @@ /* HeroesComponent's private CSS styles */ -.selected { - background-color: #CFD8DC !important; - color: white; -} .heroes { margin: 0 0 2em 0; list-style-type: none; @@ -19,18 +15,18 @@ height: 1.6em; border-radius: 4px; } -.heroes li.selected:hover { - background-color: #BBD8DC !important; - color: white; -} .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } -.heroes .text { - position: relative; - top: -3px; +.heroes li.selected { + background-color: #CFD8DC; + color: white; +} +.heroes li.selected:hover { + background-color: #BBD8DC; + color: white; } .heroes .badge { display: inline-block; diff --git a/aio/content/examples/toh-pt4/src/app/heroes/heroes.component.css b/aio/content/examples/toh-pt4/src/app/heroes/heroes.component.css index 1d73d53659..9759a4211b 100644 --- a/aio/content/examples/toh-pt4/src/app/heroes/heroes.component.css +++ b/aio/content/examples/toh-pt4/src/app/heroes/heroes.component.css @@ -1,8 +1,4 @@ /* HeroesComponent's private CSS styles */ -.selected { - background-color: #CFD8DC !important; - color: white; -} .heroes { margin: 0 0 2em 0; list-style-type: none; @@ -19,18 +15,18 @@ height: 1.6em; border-radius: 4px; } -.heroes li.selected:hover { - background-color: #BBD8DC !important; - color: white; -} .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } -.heroes .text { - position: relative; - top: -3px; +.heroes li.selected { + background-color: #CFD8DC; + color: white; +} +.heroes li.selected:hover { + background-color: #BBD8DC; + color: white; } .heroes .badge { display: inline-block; @@ -43,8 +39,6 @@ left: -1px; top: -4px; height: 1.8em; - min-width: 16px; - text-align: right; margin-right: .8em; border-radius: 4px 0 0 4px; } diff --git a/aio/content/examples/toh-pt5/src/app/app-routing.module.1.ts b/aio/content/examples/toh-pt5/src/app/app-routing.module.1.ts new file mode 100644 index 0000000000..69ce7c03e9 --- /dev/null +++ b/aio/content/examples/toh-pt5/src/app/app-routing.module.1.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { HeroesComponent } from './heroes/heroes.component'; + +const routes: Routes = [ + { path: 'heroes', component: HeroesComponent } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/aio/content/examples/toh-pt5/src/app/app-routing.module.ts b/aio/content/examples/toh-pt5/src/app/app-routing.module.ts index bfdcb7d5ec..7e9ad1a4cf 100644 --- a/aio/content/examples/toh-pt5/src/app/app-routing.module.ts +++ b/aio/content/examples/toh-pt5/src/app/app-routing.module.ts @@ -7,9 +7,7 @@ import { RouterModule, Routes } from '@angular/router'; // #docregion import-dashboard import { DashboardComponent } from './dashboard/dashboard.component'; // #enddocregion import-dashboard -// #docregion heroes-route import { HeroesComponent } from './heroes/heroes.component'; -// #enddocregion heroes-route // #docregion import-herodetail import { HeroDetailComponent } from './hero-detail/hero-detail.component'; // #enddocregion import-herodetail @@ -39,7 +37,9 @@ const routes: Routes = [ imports: [ RouterModule.forRoot(routes) ], // #enddocregion ngmodule-imports // #docregion v1 +// #docregion export-routermodule exports: [ RouterModule ] +// #enddocregion export-routermodule }) export class AppRoutingModule {} // #enddocregion , v1 diff --git a/aio/content/examples/toh-pt6/src/app/app.module.ts b/aio/content/examples/toh-pt6/src/app/app.module.ts index 69afe100a8..f2ba5ba879 100644 --- a/aio/content/examples/toh-pt6/src/app/app.module.ts +++ b/aio/content/examples/toh-pt6/src/app/app.module.ts @@ -23,13 +23,17 @@ import { HeroSearchComponent } from './hero-search/hero-search.component'; // #docregion v1 import { MessagesComponent } from './messages/messages.component'; + // #docregion import-httpclientmodule @NgModule({ imports: [ + // #enddocregion import-httpclientmodule BrowserModule, FormsModule, AppRoutingModule, // #docregion in-mem-web-api-imports + // #docregion import-httpclientmodule HttpClientModule, + // #enddocregion import-httpclientmodule // The HttpClientInMemoryWebApiModule module intercepts HTTP requests // and returns simulated server responses. @@ -38,7 +42,9 @@ import { MessagesComponent } from './messages/messages.component'; InMemoryDataService, { dataEncapsulation: false } ) // #enddocregion in-mem-web-api-imports + // #docregion import-httpclientmodule ], + // #enddocregion import-httpclientmodule declarations: [ AppComponent, DashboardComponent, @@ -50,6 +56,9 @@ import { MessagesComponent } from './messages/messages.component'; // #docregion v1 ], bootstrap: [ AppComponent ] +// #docregion import-httpclientmodule }) +// #enddocregion import-httpclientmodule + export class AppModule { } // #enddocregion , v1 diff --git a/aio/content/examples/toh-pt6/src/app/hero.service.ts b/aio/content/examples/toh-pt6/src/app/hero.service.ts index 65afef4474..747ba05c80 100644 --- a/aio/content/examples/toh-pt6/src/app/hero.service.ts +++ b/aio/content/examples/toh-pt6/src/app/hero.service.ts @@ -13,11 +13,6 @@ import { catchError, map, tap } from 'rxjs/operators'; import { Hero } from './hero'; import { MessageService } from './message.service'; -// #docregion http-options -const httpOptions = { - headers: new HttpHeaders({ 'Content-Type': 'application/json' }) -}; -// #enddocregion http-options @Injectable({ providedIn: 'root' }) export class HeroService { @@ -26,6 +21,12 @@ export class HeroService { private heroesUrl = 'api/heroes'; // URL to web api // #enddocregion heroesUrl + // #docregion http-options + httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }) + }; + // #enddocregion http-options + // #docregion ctor constructor( private http: HttpClient, @@ -96,7 +97,7 @@ export class HeroService { // #docregion addHero /** POST: add a new hero to the server */ addHero (hero: Hero): Observable { - return this.http.post(this.heroesUrl, hero, httpOptions).pipe( + return this.http.post(this.heroesUrl, hero, this.httpOptions).pipe( tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)), catchError(this.handleError('addHero')) ); @@ -109,7 +110,7 @@ export class HeroService { const id = typeof hero === 'number' ? hero : hero.id; const url = `${this.heroesUrl}/${id}`; - return this.http.delete(url, httpOptions).pipe( + return this.http.delete(url, this.httpOptions).pipe( tap(_ => this.log(`deleted hero id=${id}`)), catchError(this.handleError('deleteHero')) ); @@ -119,7 +120,7 @@ export class HeroService { // #docregion updateHero /** PUT: update the hero on the server */ updateHero (hero: Hero): Observable { - return this.http.put(this.heroesUrl, hero, httpOptions).pipe( + return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe( tap(_ => this.log(`updated hero id=${hero.id}`)), catchError(this.handleError('updateHero')) ); diff --git a/aio/content/tutorial/toh-pt1.md b/aio/content/tutorial/toh-pt1.md index cb465d2c6e..428e28c3a9 100644 --- a/aio/content/tutorial/toh-pt1.md +++ b/aio/content/tutorial/toh-pt1.md @@ -13,13 +13,11 @@ Using the Angular CLI, generate a new component named `heroes`. The CLI creates a new folder, `src/app/heroes/`, and generates -the four files of the `HeroesComponent`. +the three files of the `HeroesComponent` along with a test file. The `HeroesComponent` class file is as follows: - + You always import the `Component` symbol from the Angular core library @@ -38,13 +36,13 @@ The CLI generated three metadata properties: The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors), `'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template. -The `ngOnInit` is a [lifecycle hook](guide/lifecycle-hooks#oninit). -Angular calls `ngOnInit` shortly after creating a component. +The `ngOnInit()` is a [lifecycle hook](guide/lifecycle-hooks#oninit). +Angular calls `ngOnInit()` shortly after creating a component. It's a good place to put initialization logic. Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`. -### Add a _hero_ property +### Add a `hero` property Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm." @@ -54,17 +52,17 @@ Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm." ### Show the hero Open the `heroes.component.html` template file. -Delete the default text generated by the Angular CLI and -replace it with a data binding to the new `hero` property. +Delete the default text generated by the Angular CLI and +replace it with a data binding to the new `hero` property. -## Show the _HeroesComponent_ view +## Show the `HeroesComponent` view To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`. -Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`. +Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`. So add an `` element to the `AppComponent` template file, just below the title. @@ -102,10 +100,7 @@ The page no longer displays properly because you changed the hero from a string Update the binding in the template to announce the hero's name and show both `id` and `name` in a details layout like this: - + The browser refreshes and displays the hero's information. @@ -113,14 +108,12 @@ The browser refreshes and displays the hero's information. ## Format with the _UppercasePipe_ Modify the `hero.name` binding like this. - + The browser refreshes and now the hero's name is displayed in capital letters. -The word `uppercase` in the interpolation binding, +The word `uppercase` in the interpolation binding, right after the pipe operator ( | ), activates the built-in `UppercasePipe`. @@ -133,7 +126,7 @@ Users should be able to edit the hero name in an `` textbox. The textbox should both _display_ the hero's `name` property and _update_ that property as the user types. -That means data flow from the component class _out to the screen_ and +That means data flows from the component class _out to the screen_ and from the screen _back to the class_. To automate that data flow, setup a two-way data binding between the `` form element and the `hero.name` property. @@ -146,7 +139,7 @@ Refactor the details area in the `HeroesComponent` template so it looks like thi -**[(ngModel)]** is Angular's two-way data binding syntax. +**[(ngModel)]** is Angular's two-way data binding syntax. Here it binds the `hero.name` property to the HTML textbox so that data can flow _in both directions:_ from the `hero.name` property to the textbox, and from the textbox back to the `hero.name`. @@ -162,7 +155,7 @@ Template parse errors: Can't bind to 'ngModel' since it isn't a known property of 'input'. -Although `ngModel` is a valid Angular directive, it isn't available by default. +Although `ngModel` is a valid Angular directive, it isn't available by default. It belongs to the optional `FormsModule` and you must _opt-in_ to using it. @@ -170,7 +163,7 @@ It belongs to the optional `FormsModule` and you must _opt-in_ to using it. Angular needs to know how the pieces of your application fit together and what other files and libraries the app requires. -This information is called _metadata_ +This information is called _metadata_. Some of the metadata is in the `@Component` decorators that you added to your component classes. Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators. @@ -182,7 +175,7 @@ This is where you _opt-in_ to the `FormsModule`. ### Import _FormsModule_ -Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library. +Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library. @@ -190,13 +183,13 @@ Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the Then add `FormsModule` to the `@NgModule` metadata's `imports` array, which contains a list of external modules that the app needs. - When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the `

` above the textbox. -### Declare _HeroesComponent_ +### Declare `HeroesComponent` Every component must be declared in _exactly one_ [NgModule](guide/ngmodules). @@ -206,11 +199,11 @@ So why did the application work? It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component. Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top. - + The `HeroesComponent` is declared in the `@NgModule.declarations` array. - + Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`. @@ -228,7 +221,7 @@ Your app should look like this . Here are the code - @@ -238,7 +231,7 @@ Your app should look like this . Here are the code - @@ -247,10 +240,10 @@ Your app should look like this . Here are the code ## Summary * You used the CLI to create a second `HeroesComponent`. -* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell. +* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell. * You applied the `UppercasePipe` to format the name. * You used two-way data binding with the `ngModel` directive. * You learned about the `AppModule`. -* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive. +* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive. * You learned the importance of declaring components in the `AppModule` and appreciated that the CLI declared it for you. diff --git a/aio/content/tutorial/toh-pt2.md b/aio/content/tutorial/toh-pt2.md index 7389494e91..a9902cafeb 100644 --- a/aio/content/tutorial/toh-pt2.md +++ b/aio/content/tutorial/toh-pt2.md @@ -21,19 +21,17 @@ header="src/app/mock-heroes.ts"> ## Displaying heroes -You're about to display the list of heroes at the top of the `HeroesComponent`. - Open the `HeroesComponent` class file and import the mock `HEROES`. -In the same file (`HeroesComponent` class), define a component property called `heroes` to expose `HEROES` array for binding. +In the same file (`HeroesComponent` class), define a component property called `heroes` to expose the `HEROES` array for binding. - + -### List heroes with _*ngFor_ +### List heroes with `*ngFor` Open the `HeroesComponent` template file and make the following changes: @@ -47,7 +45,7 @@ Make it look like this: -Now change the `
  • ` to this: +That shows one hero. To list them all, add an `*ngFor` to the `
  • ` to iterate through the list of heroes: @@ -55,10 +53,10 @@ Now change the `
  • ` to this: The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive. It repeats the host element for each element in a list. -In this example +The syntax in this example is as follows: -* `
  • ` is the host element -* `heroes` is the list from the `HeroesComponent` class. +* `
  • ` is the host element. +* `heroes` holds the mock heroes list from the `HeroesComponent` class, the mock heroes list. * `hero` holds the current hero object for each iteration through the list.
    @@ -127,9 +125,10 @@ This is an example of Angular's [event binding](guide/template-syntax#event-bind The parentheses around `click` tell Angular to listen for the `
  • ` element's `click` event. When the user clicks in the `
  • `, Angular executes the `onSelect(hero)` expression. -`onSelect()` is a `HeroesComponent` method that you're about to write. -Angular calls it with the `hero` object displayed in the clicked `
  • `, -the same `hero` defined previously in the `*ngFor` expression. + +In the next section, define an `onSelect()` method in `HeroesComponent` to +display the hero that was defined in the `*ngFor` expression. + ### Add the click event handler @@ -142,10 +141,11 @@ to the component's `selectedHero`. -### Update the details template +### Add a details section -The template still refers to the component's old `hero` property which no longer exists. -Rename `hero` to `selectedHero`. +Currently, you have a list in the component template. To click on a hero on the list +and reveal details about that hero, you need a section for the details to render in the +template. Add the following to `heroes.component.html` beneath the list section: @@ -162,7 +162,7 @@ Open the browser developer tools and look in the console for an error message li When the app starts, the `selectedHero` is `undefined` _by design_. -Binding expressions in the template that refer to properties of `selectedHero` — expressions like `{{selectedHero.name}}` — _must fail_ because there is no selected hero. +Binding expressions in the template that refer to properties of `selectedHero`—expressions like `{{selectedHero.name}}`—_must fail_ because there is no selected hero. #### The fix - hide empty details with _*ngIf_ @@ -192,7 +192,7 @@ The heroes appear in a list and details about the clicked hero appear at the bot #### Why it works -When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to worry about. +When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to consider. When the user picks a hero, `selectedHero` has a value and `ngIf` puts the hero detail into the DOM. @@ -240,7 +240,7 @@ Here are the code files discussed on this page, including the `HeroesComponent` - + diff --git a/aio/content/tutorial/toh-pt3.md b/aio/content/tutorial/toh-pt3.md index 82a714b640..3bfe92c0a0 100644 --- a/aio/content/tutorial/toh-pt3.md +++ b/aio/content/tutorial/toh-pt3.md @@ -72,7 +72,7 @@ Amend the `@angular/core` import statement to include the `Input` symbol. Add a `hero` property, preceded by the `@Input()` decorator. - + That's the only change you should make to the `HeroDetailComponent` class. diff --git a/aio/content/tutorial/toh-pt4.md b/aio/content/tutorial/toh-pt4.md index 21ba9d5ca0..b1d7b0af61 100644 --- a/aio/content/tutorial/toh-pt4.md +++ b/aio/content/tutorial/toh-pt4.md @@ -11,18 +11,18 @@ Components shouldn't fetch or save data directly and they certainly shouldn't kn They should focus on presenting data and delegate data access to a service. In this tutorial, you'll create a `HeroService` that all application classes can use to get heroes. -Instead of creating that service with `new`, -you'll rely on Angular [*dependency injection*](guide/dependency-injection) +Instead of creating that service with `new`, +you'll rely on Angular [*dependency injection*](guide/dependency-injection) to inject it into the `HeroesComponent` constructor. Services are a great way to share information among classes that _don't know each other_. You'll create a `MessageService` and inject it in two places: -1. in `HeroService` which uses the service to send a message. -2. in `MessagesComponent` which displays that message. +1. in `HeroService` which uses the service to send a message +2. in `MessagesComponent` which displays that message -## Create the _HeroService_ +## Create the `HeroService` Using the Angular CLI, create a service called `hero`. @@ -30,24 +30,24 @@ Using the Angular CLI, create a service called `hero`. ng generate service hero -The command generates skeleton `HeroService` class in `src/app/hero.service.ts` -The `HeroService` class should look like the following example. +The command generates a skeleton `HeroService` class in `src/app/hero.service.ts` as follows: -### _@Injectable()_ services + +### `@Injectable()` services Notice that the new service imports the Angular `Injectable` symbol and annotates the 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. It doesn't have any dependencies yet, but [it will soon](#inject-message-service). -The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes. +The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes. ### Get hero data -The `HeroService` could get hero data from anywhere—a web service, local storage, or a mock data source. +The `HeroService` could get hero data from anywhere—a web service, local storage, or a mock data source. Removing data access from components means you can change your mind about the implementation anytime, without touching any components. They don't know how the service works. @@ -56,27 +56,25 @@ The implementation in _this_ tutorial will continue to deliver _mock heroes_. Import the `Hero` and `HEROES`. - + Add a `getHeroes` method to return the _mock heroes_. - + {@a provide} ## Provide the `HeroService` -You must make the `HeroService` available to the dependency injection system -before Angular can _inject_ it into the `HeroesComponent`, -as you will do [below](#inject). You do this 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. +You must make the `HeroService` available to the dependency injection system +before 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. -Now, you need to make sure that the `HeroService` is registered as the provider of this service. -You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required. +To make sure that the `HeroService` can provide this service, register it +with the _injector_, which is the object that is responsible for choosing +and injecting the provider where the app requires it. -By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata in the `@Injectable` decorator. - -If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root': +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. ``` @Injectable({ @@ -84,8 +82,8 @@ If you look at the `@Injectable()` statement right before the `HeroService` clas }) ``` -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. -Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all. +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. +Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
    @@ -115,7 +113,7 @@ Import the `HeroService` instead. Replace the definition of the `heroes` property with a simple declaration. - + {@a inject} @@ -124,24 +122,24 @@ Replace the definition of the `heroes` property with a simple declaration. Add a private `heroService` parameter of type `HeroService` to the constructor. - + The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site. When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system -sets the `heroService` parameter to the singleton instance of `HeroService`. +sets the `heroService` parameter to the singleton instance of `HeroService`. -### Add _getHeroes()_ +### Add `getHeroes()` Create a function to retrieve the heroes from the service. - + {@a oninit} -### Call it in `ngOnInit` +### Call it in `ngOnInit()` While you could call `getHeroes()` in the constructor, that's not the best practice. @@ -150,29 +148,29 @@ The constructor shouldn't _do anything_. It certainly shouldn't call a function that makes HTTP requests to a remote server as a _real_ data service would. Instead, call `getHeroes()` inside the [*ngOnInit lifecycle hook*](guide/lifecycle-hooks) and -let Angular call `ngOnInit` at an appropriate time _after_ constructing a `HeroesComponent` instance. +let Angular call `ngOnInit()` at an appropriate time _after_ constructing a `HeroesComponent` instance. - + ### See it run -After the browser refreshes, the app should run as before, +After the browser refreshes, the app should run as before, showing a list of heroes and a hero detail view when you click on a hero name. ## Observable data The `HeroService.getHeroes()` method has a _synchronous signature_, which implies that the `HeroService` can fetch heroes synchronously. -The `HeroesComponent` consumes the `getHeroes()` result +The `HeroesComponent` consumes the `getHeroes()` result as if heroes could be fetched synchronously. - + This will not work in a real app. You're getting away with it now because the service currently returns _mock heroes_. -But soon the app will fetch heroes from a remote server, +But soon the app will fetch heroes from a remote server, which is an inherently _asynchronous_ operation. The `HeroService` must wait for the server to respond, @@ -181,13 +179,11 @@ and the browser will not block while the service waits. `HeroService.getHeroes()` must have an _asynchronous signature_ of some kind. -It can take a callback. It could return a `Promise`. It could return an `Observable`. - In this tutorial, `HeroService.getHeroes()` will return an `Observable` -in part because it will eventually use the Angular `HttpClient.get` method to fetch the heroes +because it will eventually use the Angular `HttpClient.get` method to fetch the heroes and [`HttpClient.get()` returns an `Observable`](guide/http). -### Observable _HeroService_ +### Observable `HeroService` `Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/). @@ -196,13 +192,12 @@ In this tutorial, you'll simulate getting data from the server with the RxJS `of Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS. - + -Replace the `getHeroes` method with this one. +Replace the `getHeroes()` method with the following: - + `of(HEROES)` returns an `Observable` that emits _a single value_, the array of mock heroes. @@ -212,7 +207,7 @@ In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get()`
    -### Subscribe in _HeroesComponent_ +### Subscribe in `HeroesComponent` The `HeroService.getHeroes` method used to return a `Hero[]`. Now it returns an `Observable`. @@ -224,11 +219,11 @@ Find the `getHeroes` method and replace it with the following code - - @@ -242,9 +237,9 @@ or the browser could freeze the UI while it waited for the server's response. That _won't work_ when the `HeroService` is actually making requests of a remote server. -The new version waits for the `Observable` to emit the array of heroes— -which could happen now or several minutes from now. -Then `subscribe` passes the emitted array to the callback, +The new version waits for the `Observable` to emit the array of heroes—which +could happen now or several minutes from now. +The `subscribe()` method passes the emitted array to the callback, which sets the component's `heroes` property. This asynchronous approach _will work_ when @@ -252,14 +247,14 @@ the `HeroService` requests heroes from the server. ## Show messages -In this section you will +This section guides you through the following: -* add a `MessagesComponent` that displays app messages at the bottom of the screen. -* create an injectable, app-wide `MessageService` for sending messages to be displayed -* inject `MessageService` into the `HeroService` -* display a message when `HeroService` fetches heroes successfully. +* adding a `MessagesComponent` that displays app messages at the bottom of the screen +* creating an injectable, app-wide `MessageService` for sending messages to be displayed +* injecting `MessageService` into the `HeroService` +* displaying a message when `HeroService` fetches heroes successfully -### Create _MessagesComponent_ +### Create `MessagesComponent` Use the CLI to create the `MessagesComponent`. @@ -269,18 +264,18 @@ Use the CLI to create the `MessagesComponent`. The CLI creates the component files in the `src/app/messages` folder and declares the `MessagesComponent` in `AppModule`. -Modify the `AppComponent` template to display the generated `MessagesComponent` +Modify the `AppComponent` template to display the generated `MessagesComponent`. You should see the default paragraph from `MessagesComponent` at the bottom of the page. -### Create the _MessageService_ +### Create the `MessageService` -Use the CLI to create the `MessageService` in `src/app`. +Use the CLI to create the `MessageService` in `src/app`. ng generate service message @@ -288,9 +283,7 @@ Use the CLI to create the `MessageService` in `src/app`. Open `MessageService` and replace its contents with the following. - + The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache. @@ -298,19 +291,19 @@ The service exposes its cache of `messages` and two methods: one to `add()` a me {@a inject-message-service} ### Inject it into the `HeroService` -Re-open the `HeroService` and import the `MessageService`. +In `HeroService`, import the `MessageService`. Modify the constructor with a parameter that declares a private `messageService` property. -Angular will inject the singleton `MessageService` into that property +Angular will inject the singleton `MessageService` into that property when it creates the `HeroService`. + path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor">
    @@ -322,32 +315,29 @@ you inject the `MessageService` into the `HeroService` which is injected into th ### Send a message from `HeroService` -Modify the `getHeroes` method to send a message when the heroes are fetched. +Modify the `getHeroes()` method to send a message when the heroes are fetched. - + ### Display the message from `HeroService` -The `MessagesComponent` should display all messages, +The `MessagesComponent` should display all messages, including the message sent by the `HeroService` when it fetches heroes. Open `MessagesComponent` and import the `MessageService`. - + Modify the constructor with a parameter that declares a **public** `messageService` property. -Angular will inject the singleton `MessageService` into that property +Angular will inject the singleton `MessageService` into that property when it creates the `MessagesComponent`. - + -The `messageService` property **must be public** because you're about to bind to it in the template. +The `messageService` property **must be public** because you're going to bind to it in the template.
    @@ -355,7 +345,7 @@ Angular only binds to _public_ component properties.
    -### Bind to the _MessageService_ +### Bind to the `MessageService` Replace the CLI-generated `MessagesComponent` template with the following. @@ -390,11 +380,11 @@ Here are the code files discussed on this page and your app should look like thi - - diff --git a/aio/content/tutorial/toh-pt5.md b/aio/content/tutorial/toh-pt5.md index cab0614f2d..d21c26147d 100644 --- a/aio/content/tutorial/toh-pt5.md +++ b/aio/content/tutorial/toh-pt5.md @@ -36,79 +36,74 @@ Use the CLI to generate it. The generated file looks like this: - + -You generally don't declare components in a routing module so you can delete the -`@NgModule.declarations` array and delete `CommonModule` references too. +Replace it with the following: -You'll configure the router with `Routes` in the `RouterModule` -so import those two symbols from the `@angular/router` library. - -Add an `@NgModule.exports` array with `RouterModule` in it. -Exporting `RouterModule` makes router directives available for use -in the `AppModule` components that will need them. - -`AppRoutingModule` looks like this now: - - + -### Add routes +First, `AppRoutingModule` imports `RouterModule` and `Routes` so the app can have routing functionality. The next import, `HeroesComponent`, will give the Router somewhere to go once you configure the routes. -*Routes* tell the router which view to display when a user clicks a link or +Notice that the `CommonModule` references and `declarations` array are unecessary, so are no +longer part of `AppRoutingModule`. The following sections explain the rest of the `AppRoutingModule` in more detail. + + +### Routes + +The next part of the file is where you configure your routes. +*Routes* tell the Router which view to display when a user clicks a link or pastes a URL into the browser address bar. -A typical Angular `Route` has two properties: +Since `AppRoutingModule` already imports `HeroesComponent`, you can use it in the `routes` array: -1. `path`: a string that matches the URL in the browser address bar. -1. `component`: the component that the router should create when navigating to this route. - -You intend to navigate to the `HeroesComponent` when the URL is something like `localhost:4200/heroes`. - -Import the `HeroesComponent` so you can reference it in a `Route`. -Then define an array of routes with a single `route` to that component. - - -Once you've finished setting up, the router will match that URL to `path: 'heroes'` -and display the `HeroesComponent`. +A typical Angular `Route` has two properties: -### _RouterModule.forRoot()_ +* `path`: a string that matches the URL in the browser address bar. +* `component`: the component that the router should create when navigating to this route. -You first must initialize the router and start it listening for browser location changes. +This tells the router to match that URL to `path: 'heroes'` +and display the `HeroesComponent` when the URL is something like `localhost:4200/heroes`. -Add `RouterModule` to the `@NgModule.imports` array and -configure it with the `routes` in one step by calling -`RouterModule.forRoot()` _within_ the `imports` array, like this: +### `RouterModule.forRoot()` - +The `@NgModule` metadata initializes the router and starts it listening for browser location changes. + +The following line adds the `RouterModule` to the `AppRoutingModule` `imports` array and +configures it with the `routes` in one step by calling +`RouterModule.forRoot()`: + +
    The method is called `forRoot()` because you configure the router at the application's root level. - The `forRoot()` method supplies the service providers and directives needed for routing, + The `forRoot()` method supplies the service providers and directives needed for routing, and performs the initial navigation based on the current browser URL.
    -## Add _RouterOutlet_ +Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app. Open the `AppComponent` template and replace the `` element with a `` element. - + -You removed `` because you will only display the `HeroesComponent` when the user navigates to it. +## Add `RouterOutlet` + +Open the `AppComponent` template and replace the `` element with a `` element. + + + + +The `AppComponent` template no longer needs `` because the app will only display the `HeroesComponent` when the user navigates to it. The `` tells the router where to display routed views. @@ -129,7 +124,7 @@ You should still be running with this CLI command. The browser should refresh and display the app title but not the list of heroes. -Look at the browser's address bar. +Look at the browser's address bar. The URL ends in `/`. The route path to `HeroesComponent` is `/heroes`. @@ -140,29 +135,26 @@ You should see the familiar heroes master/detail view. ## Add a navigation link (`routerLink`) -Users shouldn't have to paste a route URL into the address bar. -They should be able to click a link to navigate. +Ideally, users should be able to click a link to navigate rather +than pasting a route URL into the address bar. -Add a `