docs(aio): migrate content docs from angular.io
This content was generated by a tool: https://github.com/petebacondarwin/aio-migrator
This commit is contained in:

committed by
Igor Minar

parent
c3247c64a4
commit
3e34ba01bd
82
aio/content/tutorial/index.md
Normal file
82
aio/content/tutorial/index.md
Normal file
@ -0,0 +1,82 @@
|
||||
@title
|
||||
Tutorial: Tour of Heroes
|
||||
|
||||
@intro
|
||||
The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.
|
||||
|
||||
@description
|
||||
Our grand plan for this tutorial is to build an app to help a staffing agency manage its stable of heroes.
|
||||
Even heroes need to find work.
|
||||
|
||||
Of course we'll only make a little progress in this tutorial. What we do build will
|
||||
have many of the features we expect to find in a full-blown, data-driven application: acquiring and displaying
|
||||
a list of heroes, editing a selected hero's detail, and navigating among different
|
||||
views of heroic data.
|
||||
|
||||
The Tour of Heroes covers the core fundamentals of Angular.
|
||||
We’ll use built-in directives to show/hide elements and display lists of hero data.
|
||||
We’ll create a component to display hero details and another to show an array of heroes.
|
||||
We'll use one-way data binding for read-only data. We'll add editable fields to update a model
|
||||
with two-way data binding. We'll bind component methods to user events like key strokes and clicks.
|
||||
We’ll learn to select a hero from a master list and edit that hero in the details view. We'll
|
||||
format data with pipes. We'll create a shared service to assemble our heroes. And we'll use routing to navigate among different views and their components.
|
||||
|
||||
We’ll learn enough core Angular to get started and gain confidence that
|
||||
Angular can do whatever we need it to do.
|
||||
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
||||
to chapters with greater depth.
|
||||
|
||||
Run the <live-example name="toh-6"></live-example>.
|
||||
|
||||
## The End Game
|
||||
|
||||
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||
view and our most heroic heroes:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard"> </img>
|
||||
</figure>
|
||||
|
||||
Above the dashboard are two links ("Dashboard" and "Heroes").
|
||||
We could click them to navigate between this Dashboard and a Heroes view.
|
||||
|
||||
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
||||
of that hero where we can change the hero's name.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app"> </img>
|
||||
</figure>
|
||||
|
||||
Clicking the "Back" button would return us to the "Dashboard".
|
||||
Links at the top can take us to either of the main views.
|
||||
We'll click "Heroes". The app takes to the "Heroes" master list view.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app"> </img>
|
||||
</figure>
|
||||
|
||||
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
||||
|
||||
We click the "View Details" button to drill into the
|
||||
editable details of our selected hero.
|
||||
|
||||
The following diagram captures all of our navigation options.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations"> </img>
|
||||
</figure>
|
||||
|
||||
Here's our app in action
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
## Up Next
|
||||
|
||||
We’ll build this Tour of Heroes together, step by step.
|
||||
We'll motivate each step with a requirement that we've
|
||||
met in countless applications. Everything has a reason.
|
||||
|
||||
And we’ll meet many of the core fundamentals of Angular along the way.
|
229
aio/content/tutorial/toh-pt1.md
Normal file
229
aio/content/tutorial/toh-pt1.md
Normal file
@ -0,0 +1,229 @@
|
||||
@title
|
||||
The Hero Editor
|
||||
|
||||
@intro
|
||||
We build a simple hero editor
|
||||
|
||||
@description
|
||||
## Setup to develop locally
|
||||
Real application development takes place in a local development environment like your machine.
|
||||
|
||||
Follow the [setup](../guide/setup.html) instructions for creating a new project
|
||||
named <ngio-ex path="angular-tour-of-heroes"></ngio-ex>
|
||||
after which the file structure should look like this:
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
When we're done with this first episode, the app runs like this <live-example></live-example>.
|
||||
|
||||
## Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server.
|
||||
Do this by entering the following command in the terminal window.
|
||||
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm start
|
||||
|
||||
</code-example>
|
||||
|
||||
This command runs the compiler in watch mode, starts the server, launches the app in a browser,
|
||||
and keeps the app running while we continue to build the Tour of Heroes.
|
||||
|
||||
## Show our Hero
|
||||
We want to display Hero data in our app
|
||||
|
||||
Update the `AppComponent` so it has two properties: a `title` property for the application name and a `hero` property
|
||||
for a hero named "Windstorm".
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='app-component-1'}
|
||||
|
||||
Now update the template in the `@Component` decoration with data bindings to these new properties.
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero'}
|
||||
|
||||
The browser should refresh and display our title and hero.
|
||||
|
||||
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
|
||||
This is the "interpolation" form of one-way data binding.
|
||||
Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html).### Hero object
|
||||
|
||||
At the moment, our hero is just a name. Our hero needs more properties.
|
||||
Let's convert the `hero` from a literal string to a class.
|
||||
|
||||
Create a `Hero` class with `id` and `name` properties.
|
||||
For now put this near the top of the `app.component.ts` file, just below the import statement.
|
||||
|
||||
|
||||
{@example 'toh-1/ts/src/app/app.component.ts' region='hero-class-1'}
|
||||
|
||||
Now that we have a `Hero` class, let’s refactor our component’s `hero` property to be of type `Hero`.
|
||||
Then initialize it with an id of `1` and the name, "Windstorm".
|
||||
|
||||
|
||||
{@example 'toh-1/ts/src/app/app.component.ts' region='hero-property-1'}
|
||||
|
||||
Because we changed the hero from a string to an object,
|
||||
we update the binding in the template to refer to the hero’s `name` property.
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero-2'}
|
||||
|
||||
The browser refreshes and continues to display our hero’s name.
|
||||
|
||||
### Adding more HTML
|
||||
Displaying a name is good, but we want to see all of our hero’s properties.
|
||||
We’ll add a `<div>` for our hero’s `id` property and another `<div>` for our hero’s `name`.
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='show-hero-properties'}
|
||||
|
||||
Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template.
|
||||
|
||||
### Multi-line template strings
|
||||
|
||||
We could make a more readable template with string concatenation
|
||||
but that gets ugly fast, it is harder to read, and
|
||||
it is easy to make a spelling error. Instead,
|
||||
let’s take advantage of the template strings feature
|
||||
in ES2015 and TypeScript to maintain our sanity.
|
||||
|
||||
Change the quotes around the template to back-ticks and
|
||||
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='multi-line-strings'}
|
||||
|
||||
|
||||
## Editing Our Hero
|
||||
|
||||
We want to be able to edit the hero name in a textbox.
|
||||
|
||||
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
||||
|
||||
|
||||
{@example 'toh-1/ts-snippets/app.component.snippets.pt1.ts' region='editing-Hero'}
|
||||
|
||||
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
||||
But something doesn’t feel right.
|
||||
When we change the name, we notice that our change
|
||||
is not reflected in the `<h2>`. We won't get the desired behavior
|
||||
with a one-way binding to `<input>`.
|
||||
|
||||
### Two-Way Binding
|
||||
|
||||
We intend to display the name of the hero in the `<input>`, change it,
|
||||
and see those changes wherever we bind to the hero’s name.
|
||||
In short, we want two-way data binding.
|
||||
|
||||
Before we can use two-way data binding for **form inputs**, we need to import the `FormsModule`
|
||||
package in our Angular module. We add it to the `NgModule` decorator's `imports` array. This array contains the list
|
||||
of external modules used by our application.
|
||||
Now we have included the forms package which includes `ngModel`.
|
||||
|
||||
|
||||
{@example 'toh-1/ts/src/app/app.module.ts'}
|
||||
|
||||
|
||||
Learn more about the `FormsModule` and `ngModel` in the
|
||||
[Forms](../guide/forms.html#ngModel) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
|
||||
Let’s update the template to use the **`ngModel`** built-in directive for two-way binding.
|
||||
|
||||
Replace the `<input>` with the following HTML
|
||||
|
||||
<code-example language="html">
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
|
||||
</code-example>
|
||||
|
||||
The browser refreshes. We see our hero again. We can edit the hero’s name and
|
||||
see the changes reflected immediately in the `<h2>`.
|
||||
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
|
||||
* Our Tour of Heroes uses the double curly braces of interpolation (a kind of one-way data binding)
|
||||
to display the application title and properties of a `Hero` object.
|
||||
* We wrote a multi-line template using ES2015’s template strings to make our template readable.
|
||||
* We can both display and change the hero’s name after adding a two-way data binding to the `<input>` element
|
||||
using the built-in `ngModel` directive.
|
||||
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
|
||||
{@example 'toh-1/ts/src/app/app.component.ts' region='pt1'}
|
||||
|
||||
|
||||
## The Road Ahead
|
||||
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
|
||||
We also want to allow the user to select a hero and display their details.
|
||||
We’ll learn more about how to retrieve lists, bind them to the
|
||||
template, and allow a user to select a hero in the
|
||||
[next tutorial chapter](./toh-pt2.html).
|
356
aio/content/tutorial/toh-pt2.md
Normal file
356
aio/content/tutorial/toh-pt2.md
Normal file
@ -0,0 +1,356 @@
|
||||
@title
|
||||
Master/Detail
|
||||
|
||||
@intro
|
||||
We build a master/detail page with a list of heroes
|
||||
|
||||
@description
|
||||
Our story needs more heroes.
|
||||
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||
allow the user to select a hero, and display the hero’s details.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
Let’s take stock of what we’ll need to display a list of heroes.
|
||||
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||
so we’ll need a way to do that.
|
||||
|
||||
## Where We Left Off
|
||||
Before we continue with Part 2 of the Tour of Heroes,
|
||||
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
||||
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm start
|
||||
|
||||
</code-example>
|
||||
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
## Displaying Our Heroes
|
||||
### Creating heroes
|
||||
Let’s create an array of ten heroes.
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='hero-array'}
|
||||
|
||||
The `HEROES` array is of type `Hero`, the class defined in part one,
|
||||
to create an array of heroes.
|
||||
We aspire to fetch this list of heroes from a web service, but let’s take small steps
|
||||
first and display mock heroes.
|
||||
|
||||
### Exposing heroes
|
||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='hero-array-1'}
|
||||
|
||||
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||
We could have defined the heroes list here in this component class.
|
||||
But we know that ultimately we’ll get the heroes from a data service.
|
||||
Because we know where we are heading, it makes sense to separate the hero data
|
||||
from the class implementation from the start.### Displaying heroes in a template
|
||||
Our component has `heroes`. Let’s create an unordered list in our template to display them.
|
||||
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-template-1'}
|
||||
|
||||
Now we have a template that we can fill with our heroes.
|
||||
|
||||
### Listing heroes with ngFor
|
||||
|
||||
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
||||
and display them individually.
|
||||
We’ll need some help from Angular to do this. Let’s do this step by step.
|
||||
|
||||
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-ngfor-1'}
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-critical}
|
||||
|
||||
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
|
||||
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
|
||||
constitute a master template.
|
||||
|
||||
The `ngFor` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||
and stamps out instances of this template.
|
||||
|
||||
The quoted text assigned to `ngFor` means
|
||||
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
||||
and make it available to the corresponding template instance*”.
|
||||
|
||||
The `let` keyword before "hero" identifies `hero` as a template input variable.
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
Learn more about `ngFor` and template input variables in the
|
||||
[Displaying Data](../guide/displaying-data.html#ngFor) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
|
||||
Now we insert some content between the `<li>` tags
|
||||
that uses the `hero` template variable to display the hero’s properties.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-for'}
|
||||
|
||||
When the browser refreshes, we see a list of heroes!
|
||||
|
||||
### Styling our heroes
|
||||
Our list of heroes looks pretty bland.
|
||||
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
|
||||
|
||||
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
||||
to the following CSS classes:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='styles'}
|
||||
|
||||
Notice that we again use the back-tick notation for multi-line strings.
|
||||
|
||||
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
|
||||
We'll do this in a later chapter. For now let's keep rolling.
|
||||
|
||||
When we assign styles to a component they are scoped to that specific component.
|
||||
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
|
||||
|
||||
Our template for displaying the heroes should now look like this:
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='heroes-styled'}
|
||||
|
||||
|
||||
## Selecting a Hero
|
||||
We have a list of heroes and we have a single hero displayed in our app.
|
||||
The list and the single hero are not connected in any way.
|
||||
We want the user to select a hero from our list, and have the selected hero appear in the details view.
|
||||
This UI pattern is widely known as "master-detail".
|
||||
In our case, the master is the heroes list and the detail is the selected hero.
|
||||
|
||||
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||
|
||||
### Click event
|
||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-click'}
|
||||
|
||||
Focus on the event binding
|
||||
<code-example>
|
||||
(click)="onSelect(hero)"
|
||||
</code-example>
|
||||
|
||||
The parenthesis identify the `<li>` element’s `click` event as the target.
|
||||
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||
passing the template input variable `hero` as an argument.
|
||||
That’s the same `hero` variable we defined previously in the `ngFor`.
|
||||
Learn more about Event Binding in the
|
||||
[User Input](../guide/user-input.html) and
|
||||
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.### Add the click handler
|
||||
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||
We’ll add that method to our component now.
|
||||
|
||||
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
||||
|
||||
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||
|
||||
### Expose the selected hero
|
||||
|
||||
We no longer need the static `hero` property of the `AppComponent`.
|
||||
**Replace** it with this simple `selectedHero` property:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='selected-hero'}
|
||||
|
||||
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
||||
|
||||
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts' region='on-select'}
|
||||
|
||||
We will be showing the selected hero's details in our template.
|
||||
At the moment, it is still referring to the old `hero` property.
|
||||
Let’s fix the template to bind to the new `selectedHero` property.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='selectedHero-details'}
|
||||
|
||||
### Hide the empty detail with ngIf
|
||||
|
||||
When our app loads we see a list of heroes, but a hero is not selected.
|
||||
The `selectedHero` is `undefined`.
|
||||
That’s why we'll see the following error in the browser’s console:
|
||||
|
||||
<code-example format="nocode">
|
||||
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||
|
||||
</code-example>
|
||||
|
||||
Remember that we are displaying `selectedHero.name` in the template.
|
||||
This name property does not exist because `selectedHero` itself is undefined.
|
||||
|
||||
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
|
||||
|
||||
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
|
||||
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='ng-if'}
|
||||
|
||||
|
||||
|
||||
~~~ {.alert.is-critical}
|
||||
|
||||
Remember that the leading asterisk (`*`) in front of `ngIf` is
|
||||
a critical part of this syntax.
|
||||
|
||||
~~~
|
||||
|
||||
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||
There will be no hero detail elements and no bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` becomes "truthy" and
|
||||
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||
`ngIf` and `ngFor` are called “structural directives” because they can change the
|
||||
structure of portions of the DOM.
|
||||
In other words, they give structure to the way Angular displays content in the DOM.
|
||||
|
||||
Learn more about `ngIf`, `ngFor` and other structural directives in the
|
||||
[Structural Directives](../guide/structural-directives.html) and
|
||||
[Template Syntax](../guide/template-syntax.html#directives) chapters.
|
||||
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
|
||||
When we click on a hero in the list, the selected hero displays in the hero details.
|
||||
Everything is working as we expect.
|
||||
|
||||
### Styling the selection
|
||||
|
||||
We see the selected hero in the details area below but we can’t quickly locate that hero in the list above.
|
||||
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
|
||||
For example, when we select Magneta from the heroes list,
|
||||
we can make it pop out visually by giving it a subtle background color as shown here.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero"> </img>
|
||||
</figure>
|
||||
|
||||
We’ll add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
|
||||
|
||||
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-1'}
|
||||
|
||||
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
|
||||
This is the syntax for a **property binding**, a binding in which data flows one way
|
||||
from the data source (the expression `hero === selectedHero`) to a property of `class`.
|
||||
|
||||
{@example 'toh-2/ts-snippets/app.component.snippets.pt2.ts' region='class-selected-2'}
|
||||
|
||||
|
||||
Learn more about [property bindings](../guide/template-syntax.html#property-binding)
|
||||
in the Template Syntax chapter.
|
||||
The browser reloads our app.
|
||||
We select the hero Magneta and the selection is clearly identified by the background color.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app"> </img>
|
||||
</figure>
|
||||
|
||||
We select a different hero and the tell-tale color switches to that hero.
|
||||
|
||||
Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
|
||||
{@example 'toh-2/ts/src/app/app.component.ts'}
|
||||
|
||||
|
||||
## The Road We’ve Travelled
|
||||
Here’s what we achieved in this chapter:
|
||||
|
||||
* Our Tour of Heroes now displays a list of selectable heroes
|
||||
* We added the ability to select a hero and show the hero’s details
|
||||
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We can't put the entire app into a single component.
|
||||
We need to break it up into sub-components and teach them to work together
|
||||
as we learn in the [next chapter](toh-pt3.html).
|
365
aio/content/tutorial/toh-pt3.md
Normal file
365
aio/content/tutorial/toh-pt3.md
Normal file
@ -0,0 +1,365 @@
|
||||
@title
|
||||
Multiple Components
|
||||
|
||||
@intro
|
||||
We refactor the master/detail view into separate components
|
||||
|
||||
@description
|
||||
Our app is growing.
|
||||
Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
## Where We Left Off
|
||||
Before we continue with our Tour of Heroes, let’s verify we have the following structure. If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
### Keep the app transpiling and running
|
||||
We want to start the TypeScript compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm start
|
||||
|
||||
</code-example>
|
||||
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
## Making a Hero Detail Component
|
||||
Our heroes list and our hero details are in the same component in the same file.
|
||||
They're small now but each could grow.
|
||||
We are sure to receive new requirements for one and not the other.
|
||||
Yet every change puts both components at risk and doubles the testing burden without benefit.
|
||||
If we had to reuse the hero details elsewhere in our app,
|
||||
the heroes list would tag along for the ride.
|
||||
|
||||
Our current component violates the
|
||||
[Single Responsibility Principle](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
|
||||
It's only a tutorial but we can still do things right —
|
||||
especially if doing them right is easy and we learn how to build Angular apps in the process.
|
||||
|
||||
Let’s break the hero details out into its own component.
|
||||
|
||||
### Separating the Hero Detail Component
|
||||
Add a new file named `hero-detail.component.ts` to the `app` folder and create `HeroDetailComponent` as follows.
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='v1'}
|
||||
|
||||
|
||||
### Naming conventions
|
||||
We like to identify at a glance which classes are components and which files contain components.
|
||||
|
||||
Notice that we have an `AppComponent` in a file named `app.component.ts` and our new
|
||||
`HeroDetailComponent` is in a file named `hero-detail.component.ts`.
|
||||
|
||||
All of our component names end in "Component". All of our component file names end in ".component".
|
||||
|
||||
We spell our file names in lower **[dash case](../guide/glossary.html#dash-case)**
|
||||
(AKA **[kebab-case](../guide/glossary.html#kebab-case)**) so we don't worry about
|
||||
case sensitivity on the server or in source control.
|
||||
|
||||
<!-- TODO
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about naming conventions in the chapter [Naming Conventions]
|
||||
:marked
|
||||
-->We begin by importing the `Component` and `Input` decorators from Angular because we're going to need them soon.
|
||||
|
||||
We create metadata with the `@Component` decorator where we
|
||||
specify the selector name that identifies this component's element.
|
||||
Then we export the class to make it available to other components.
|
||||
|
||||
When we finish here, we'll import it into `AppComponent` and create a corresponding `<my-hero-detail>` element.#### Hero Detail Template
|
||||
At the moment, the *Heroes* and *Hero Detail* views are combined in one template in `AppComponent`.
|
||||
Let’s **cut** the *Hero Detail* content from `AppComponent` and **paste** it into the new template property of `HeroDetailComponent`.
|
||||
|
||||
We previously bound to the `selectedHero.name` property of the `AppComponent`.
|
||||
Our `HeroDetailComponent` will have a `hero` property, not a `selectedHero` property.
|
||||
So we replace `selectedHero` with `hero` everywhere in our new template. That's our only change.
|
||||
The result looks like this:
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='template'}
|
||||
|
||||
Now our hero detail layout exists only in the `HeroDetailComponent`.
|
||||
|
||||
#### Add the *hero* property
|
||||
Let’s add that `hero` property we were talking about to the component class.
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero'}
|
||||
|
||||
Uh oh. We declared the `hero` property as type `Hero` but our `Hero` class is over in the `app.component.ts` file.
|
||||
We have two components, each in their own file, that need to reference the `Hero` class.
|
||||
|
||||
We solve the problem by relocating the `Hero` class from `app.component.ts` to its own `hero.ts` file.
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero.ts'}
|
||||
|
||||
We export the `Hero` class from `hero.ts` because we'll need to reference it in both component files.
|
||||
Add the following import statement near the top of **both `app.component.ts` and `hero-detail.component.ts`**.
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero-import'}
|
||||
|
||||
#### The *hero* property is an ***input***
|
||||
|
||||
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
|
||||
|
||||
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
|
||||
The user's selection is in its `selectedHero` property.
|
||||
|
||||
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
|
||||
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
|
||||
<code-example language="html">
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
</code-example>
|
||||
|
||||
Notice that the `hero` property is the ***target*** of a property binding — it's in square brackets to the left of the (=).
|
||||
|
||||
Angular insists that we declare a ***target*** property to be an ***input*** property.
|
||||
If we don't, Angular rejects the binding and throws an error.
|
||||
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
|
||||
where we also explain why *target* properties require this special treatment and
|
||||
*source* properties do not.There are a couple of ways we can declare that `hero` is an *input*.
|
||||
We'll do it the way we *prefer*, by annotating the `hero` property with the `@Input` decorator that we imported earlier.
|
||||
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts' region='hero-input'}
|
||||
|
||||
|
||||
Learn more about the `@Input()` decorator in the
|
||||
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
|
||||
|
||||
## Refresh the AppModule
|
||||
We return to the `AppModule`, the application's root module, and teach it to use the `HeroDetailComponent`.
|
||||
|
||||
We begin by importing the `HeroDetailComponent` so we can refer to it.
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/app.module.ts' region='hero-detail-import'}
|
||||
|
||||
Then we add `HeroDetailComponent` to the `NgModule` decorator's `declarations` array.
|
||||
This array contains the list of all components, pipes, and directives that we created
|
||||
and that belong in our application's module.
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/app.module.ts' region='declarations'}
|
||||
|
||||
|
||||
## Refresh the AppComponentNow that the application knows about our `HeroDetailComponent`,
|
||||
find the location in the `AppComponent` template where we removed the *Hero Detail* content
|
||||
and add an element tag that represents the `HeroDetailComponent`.
|
||||
<code-example language="html">
|
||||
<my-hero-detail></my-hero-detail>
|
||||
</code-example>
|
||||
|
||||
|
||||
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
|
||||
to the `HeroDetailComponent` element's `hero` property like this:
|
||||
<code-example language="html">
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
</code-example>
|
||||
|
||||
The `AppComponent`’s template should now look like this
|
||||
|
||||
|
||||
{@example 'toh-3/ts/src/app/app.component.ts' region='hero-detail-template'}
|
||||
|
||||
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
|
||||
The detail should update every time the user picks a new hero.
|
||||
### It works!
|
||||
When we view our app in the browser we see the list of heroes.
|
||||
When we select a hero we can see the selected hero’s details.
|
||||
|
||||
What's fundamentally new is that we can use this `HeroDetailComponent`
|
||||
to show hero details anywhere in the app.
|
||||
|
||||
We’ve created our first reusable component!
|
||||
|
||||
### Reviewing the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero-detail.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
Here are the code files we discussed in this chapter.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/hero-detail.component.ts">
|
||||
{@example 'toh-3/ts/src/app/hero-detail.component.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'toh-3/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/hero.ts">
|
||||
{@example 'toh-3/ts/src/app/hero.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.module.ts">
|
||||
{@example 'toh-3/ts/src/app/app.module.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
|
||||
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
|
||||
* We created a reusable component
|
||||
* We learned how to make a component accept input
|
||||
* We learned to declare the application directives we need in an Angular module. We
|
||||
list the directives in the `NgModule` decorator's `declarations` array.
|
||||
* We learned to bind a parent component to a child component.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
## The Road Ahead
|
||||
Our Tour of Heroes has become more reusable with shared components.
|
||||
|
||||
We're still getting our (mock) data within the `AppComponent`.
|
||||
That's not sustainable.
|
||||
We should refactor data access to a separate service
|
||||
and share it among the components that need data.
|
||||
|
||||
We’ll learn to create services in the [next tutorial](toh-pt4.html) chapter.
|
473
aio/content/tutorial/toh-pt4.md
Normal file
473
aio/content/tutorial/toh-pt4.md
Normal file
@ -0,0 +1,473 @@
|
||||
@title
|
||||
Services
|
||||
|
||||
@intro
|
||||
We create a reusable service to manage our hero data calls
|
||||
|
||||
@description
|
||||
The Tour of Heroes is evolving and we anticipate adding more components in the near future.
|
||||
|
||||
Multiple components will need access to hero data and we don't want to copy and
|
||||
paste the same code over and over.
|
||||
Instead, we'll create a single reusable data service and learn to
|
||||
inject it in the components that need it.
|
||||
|
||||
Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
|
||||
It also makes it easier to unit test the component with a mock service.
|
||||
|
||||
Because data services are invariably asynchronous,
|
||||
we'll finish the chapter with a **!{_Promise}**-based version of the data service.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
## Where We Left Off
|
||||
Before we continue with our Tour of Heroes, let’s verify we have the following structure.
|
||||
If not, we’ll need to go back and follow the previous chapters.
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero-detail.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
### Keep the app transpiling and running
|
||||
Open a terminal/console window.
|
||||
Start the TypeScript compiler, watch for changes, and start our server by entering the command:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm start
|
||||
|
||||
</code-example>
|
||||
|
||||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||
|
||||
## Creating a Hero Service
|
||||
Our stakeholders have shared their larger vision for our app.
|
||||
They tell us they want to show the heroes in various ways on different pages.
|
||||
We already can select a hero from a list.
|
||||
Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
|
||||
All three views need hero data.
|
||||
|
||||
At the moment the `AppComponent` defines mock heroes for display.
|
||||
We have at least two objections.
|
||||
First, defining heroes is not the component's job.
|
||||
Second, we can't easily share that list of heroes with other components and views.
|
||||
|
||||
We can refactor this hero data acquisition business to a single service that provides heroes, and
|
||||
share that service with all components that need heroes.
|
||||
|
||||
### Create the HeroService
|
||||
Create a file in the `app` folder called `hero.service.ts`.
|
||||
We've adopted a convention in which we spell the name of a service in lowercase followed by `.service`.
|
||||
If the service name were multi-word, we'd spell the base filename in lower [dash-case](../guide/glossary.html#dash-case).
|
||||
The `SpecialSuperHeroService` would be defined in the `special-super-hero.service.ts` file.We name the class `HeroService` and export it for others to import.
|
||||
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='empty-class'}
|
||||
|
||||
### Injectable Services
|
||||
Notice that we imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator.
|
||||
|
||||
~~~ {.callout.is-helpful}
|
||||
|
||||
**Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
|
||||
|
||||
~~~
|
||||
|
||||
TypeScript sees the `@Injectable()` decorator and emits metadata about our service,
|
||||
metadata that Angular may need to inject other dependencies into this service.
|
||||
|
||||
The `HeroService` doesn't have any dependencies *at the moment*. Add the decorator anyway.
|
||||
It is a "best practice" to apply the `@Injectable()` decorator *from the start*
|
||||
both for consistency and for future-proofing.
|
||||
### Getting Heroes
|
||||
Add a `getHeroes` method stub.
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='getHeroes-stub'}
|
||||
|
||||
We're holding back on the implementation for a moment to make an important point.
|
||||
|
||||
The consumer of our service doesn't know how the service gets the data.
|
||||
Our `HeroService` could get `Hero` data from anywhere.
|
||||
It could get the data from a web service or local storage
|
||||
or from a mock data source.
|
||||
|
||||
That's the beauty of removing data access from the component.
|
||||
We can change our minds about the implementation as often as we like,
|
||||
for whatever reason, without touching any of the components that need heroes.
|
||||
|
||||
|
||||
### Mock Heroes
|
||||
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
|
||||
We'll move the mock data to its own file.
|
||||
|
||||
Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`.
|
||||
We copy the `import {Hero} ...` statement as well because the heroes array uses the `Hero` class.
|
||||
|
||||
|
||||
{@example 'toh-4/ts/src/app/mock-heroes.ts'}
|
||||
|
||||
We export the `HEROES` constant so we can import it elsewhere — such as our `HeroService`.
|
||||
|
||||
Meanwhile, back in `app.component.ts` where we cut away the `HEROES` array,
|
||||
we leave behind an uninitialized `heroes` property:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='heroes-prop'}
|
||||
|
||||
### Return Mocked Heroes
|
||||
Back in the `HeroService` we import the mock `HEROES` and return it from the `getHeroes` method.
|
||||
Our `HeroService` looks like this:
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.1.ts' region='full'}
|
||||
|
||||
### Use the Hero Service
|
||||
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
|
||||
|
||||
We begin, as usual, by importing the thing we want to use, the `HeroService`.Importing the service allows us to *reference* it in our code.
|
||||
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
|
||||
|
||||
### Do we *new* the *HeroService*? No way!
|
||||
We could create a new instance of the `HeroService` with `new` like this:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='new-service'}
|
||||
|
||||
That's a bad idea for several reasons including
|
||||
|
||||
* Our component has to know how to create a `HeroService`.
|
||||
If we ever change the `HeroService` constructor,
|
||||
we'll have to find every place we create the service and fix it.
|
||||
Running around patching code is error prone and adds to the test burden.
|
||||
|
||||
* We create a new service each time we use `new`.
|
||||
What if the service should cache heroes and share that cache with others?
|
||||
We couldn't do that.
|
||||
|
||||
* We're locking the `AppComponent` into a specific implementation of the `HeroService`.
|
||||
It will be hard to switch implementations for different scenarios.
|
||||
Can we operate offline?
|
||||
Will we need different mocked versions under test?
|
||||
Not easy.
|
||||
|
||||
*What if ... what if ... Hey, we've got work to do!*
|
||||
|
||||
We get it. Really we do.
|
||||
But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
|
||||
|
||||
### Inject the *HeroService*
|
||||
|
||||
Two lines replace the one line that created with *new*:
|
||||
1. We add a constructor that also defines a private property.
|
||||
1. We add to the component's `providers` metadata.
|
||||
|
||||
Here's the constructor:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='ctor'}
|
||||
|
||||
The constructor itself does nothing. The parameter simultaneously
|
||||
defines a private `heroService` property and identifies it as a `HeroService` injection site.Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
|
||||
|
||||
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.The *injector* does not know yet how to create a `HeroService`.
|
||||
If we ran our code now, Angular would fail with an error:
|
||||
<code-example format="nocode">
|
||||
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
|
||||
</code-example>
|
||||
|
||||
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
|
||||
Do that by adding the following `providers` array property to the bottom of the component metadata
|
||||
in the `@Component` call.
|
||||
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
|
||||
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
|
||||
|
||||
{@a child-component}
|
||||
### *getHeroes* in the *AppComponent*
|
||||
We've got the service in a `heroService` private variable. Let's use it.
|
||||
|
||||
We pause to think. We can call the service and get the data in one line.
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='get-heroes'}
|
||||
|
||||
We don't really need a dedicated method to wrap one line. We write it anyway:<a id="oninit"></a>### The *ngOnInit* Lifecycle Hook
|
||||
`AppComponent` should fetch and display heroes without a fuss.
|
||||
Where do we call the `getHeroes` method? In a constructor? We do *not*!
|
||||
|
||||
Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
|
||||
especially anything that might call a server as a data access method is sure to do.
|
||||
|
||||
The constructor is for simple initializations like wiring constructor parameters to properties.
|
||||
It's not for heavy lifting. We should be able to create a component in a test and not worry that it
|
||||
might do real work — like calling a server! — before we tell it to do so.
|
||||
|
||||
If not the constructor, something has to call `getHeroes`.
|
||||
|
||||
Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
|
||||
Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
|
||||
at creation, after each change, and at its eventual destruction.
|
||||
|
||||
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
|
||||
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.Here's the essential outline for the `OnInit` interface:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='on-init'}
|
||||
|
||||
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
|
||||
at the right time. In our case, we initialize by calling `getHeroes`.Our application should be running as expected, showing a list of heroes and a hero detail view
|
||||
when we click on a hero name.
|
||||
|
||||
We're getting closer. But something isn't quite right.
|
||||
|
||||
<a id="async"></a>
|
||||
## Async Services and !{_Promise}s
|
||||
Our `HeroService` returns a list of mock heroes immediately.
|
||||
Its `getHeroes` signature is synchronous
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='get-heroes'}
|
||||
|
||||
Ask for heroes and they are there in the returned result.
|
||||
|
||||
Someday we're going to get heroes from a remote server. We don’t call http yet, but we aspire to in later chapters.
|
||||
|
||||
When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
|
||||
even if we want to (which we shouldn't) because the browser won't block.
|
||||
|
||||
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
|
||||
|
||||
We'll use *!{_Promise}s*.
|
||||
|
||||
### The Hero Service makes a !{_Promise}
|
||||
|
||||
A **!{_Promise}** is ... well it's a promise to call us back later when the results are ready.
|
||||
We ask an asynchronous service to do some work and give it a callback function.
|
||||
It does that work (somewhere) and eventually it calls our function with the results of the work or an error.
|
||||
We are simplifying. Learn about ES2015 Promises [here](http://exploringjs.com/es6/ch_promises.html) and elsewhere on the web.Update the `HeroService` with this !{_Promise}-returning `getHeroes` method:
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.ts' region='get-heroes'}
|
||||
|
||||
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
|
||||
by returning an **immediately resolved !{_Promise}** with our mock heroes as the result.
|
||||
|
||||
### Act on the !{_Promise}
|
||||
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.1.ts' region='getHeroes'}
|
||||
|
||||
As a result of our change to `HeroService`, we're now setting `this.heroes` to a !{_Promise} rather than an array of heroes.
|
||||
|
||||
We have to change our implementation to *act on the !{_Promise} when it resolves*.
|
||||
When the !{_Promise} resolves successfully, *then* we will have heroes to display.
|
||||
|
||||
We pass our callback function as an argument to the !{_Promise}'s **then** method:
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.ts' region='get-heroes'}
|
||||
|
||||
|
||||
The [ES2015 arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
|
||||
in the callback is more succinct than the equivalent function expression and gracefully handles *this*.Our callback sets the component's `heroes` property to the array of heroes returned by the service. That's all there is to it!
|
||||
|
||||
Our app should still be running, still showing a list of heroes, and still
|
||||
responding to a name selection with a detail view.
|
||||
Checkout the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.### Review the App Structure
|
||||
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
|
||||
|
||||
<aio-filetree>
|
||||
|
||||
<aio-folder>
|
||||
angular-tour-of-heroes
|
||||
<aio-folder>
|
||||
src
|
||||
<aio-folder>
|
||||
app
|
||||
<aio-file>
|
||||
app.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
app.module.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero-detail.component.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
hero.service.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
mock-heroes.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
main.ts
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
index.html
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
styles.css
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
systemjs.config.js
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
tsconfig.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
<aio-file>
|
||||
node_modules ...
|
||||
</aio-file>
|
||||
|
||||
|
||||
<aio-file>
|
||||
package.json
|
||||
</aio-file>
|
||||
|
||||
|
||||
</aio-folder>
|
||||
|
||||
|
||||
</aio-filetree>
|
||||
|
||||
Here are the code files we discussed in this chapter.
|
||||
|
||||
<md-tab-group>
|
||||
|
||||
<md-tab label="src/app/hero.service.ts">
|
||||
{@example 'toh-4/ts/src/app/hero.service.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/app.component.ts">
|
||||
{@example 'toh-4/ts/src/app/app.component.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
<md-tab label="src/app/mock-heroes.ts">
|
||||
{@example 'toh-4/ts/src/app/mock-heroes.ts'}
|
||||
</md-tab>
|
||||
|
||||
|
||||
</md-tab-group>
|
||||
|
||||
## The Road We’ve Travelled
|
||||
Let’s take stock of what we’ve built.
|
||||
|
||||
* We created a service class that can be shared by many components.
|
||||
* We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates.
|
||||
* We defined our `HeroService` as a provider for our `AppComponent`.
|
||||
* We created mock hero data and imported them into our service.
|
||||
* We designed our service to return a !{_Promise} and our component to get our data from the !{_Promise}.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has become more reusable using shared components and services.
|
||||
We want to create a dashboard, add menu links that route between the views, and format data in a template.
|
||||
As our app evolves, we’ll learn how to design it to make it easier to grow and maintain.
|
||||
|
||||
We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
|
||||
|
||||
<a id="slow"></a>### Appendix: Take it slow
|
||||
|
||||
We can simulate a slow connection.
|
||||
|
||||
Import the `Hero` symbol and add the following `getHeroesSlowly` method to the `HeroService`
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero.service.ts' region='get-heroes-slowly'}
|
||||
|
||||
Like `getHeroes`, it also returns a !{_Promise}.
|
||||
But this !{_Promise} waits 2 seconds before resolving the !{_Promise} with mock heroes.
|
||||
|
||||
Back in the `AppComponent`, replace `heroService.getHeroes` with `heroService.getHeroesSlowly`
|
||||
and see how the app behaves.
|
510
aio/content/tutorial/toh-pt5.md
Normal file
510
aio/content/tutorial/toh-pt5.md
Normal file
@ -0,0 +1,510 @@
|
||||
@title
|
||||
Routing
|
||||
|
||||
@intro
|
||||
We add the Angular Router and learn to navigate among the views
|
||||
|
||||
@description
|
||||
We received new requirements for our Tour of Heroes application:
|
||||
|
||||
* Add a *Dashboard* view.
|
||||
* Navigate between the *Heroes* and *Dashboard* views.
|
||||
* Clicking on a hero in either view navigates to a detail view of the selected hero.
|
||||
* Clicking a *deep link* in an email opens the detail view for a particular hero.
|
||||
|
||||
When we’re done, users will be able to navigate the app like this:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations"> </img>
|
||||
</figure>
|
||||
|
||||
We'll add Angular’s *Router* to our app to satisfy these requirements.
|
||||
|
||||
The [Routing and Navigation](../guide/router.html) chapter covers the router
|
||||
in more detail than we will in this tutorial.
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
## Where We Left Off
|
||||
|
||||
Before we continue with our Tour of Heroes, let’s verify that
|
||||
we have the following structure after adding our hero service
|
||||
and hero detail component. If not, we’ll need to go back and follow the previous chapters.
|
||||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||
|
||||
## Action plan
|
||||
|
||||
Here's our plan:
|
||||
|
||||
* Turn `AppComponent` into an application shell that only handles navigation
|
||||
* Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`
|
||||
* Add routing
|
||||
* Create a new `DashboardComponent`
|
||||
* Tie the *Dashboard* into the navigation structure
|
||||
|
||||
*Routing* is another name for *navigation*. The *router* is the mechanism for navigating from view to view.
|
||||
|
||||
## Splitting the *AppComponent*
|
||||
|
||||
Our current app loads `AppComponent` and immediately displays the list of heroes.
|
||||
|
||||
Our revised app should present a shell with a choice of views (*Dashboard* and *Heroes*)
|
||||
and then default to one of them.
|
||||
|
||||
The `AppComponent` should only handle navigation.
|
||||
Let's move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`.
|
||||
|
||||
### *HeroesComponent*
|
||||
|
||||
`AppComponent` is already dedicated to *Heroes*.
|
||||
Instead of moving anything out of `AppComponent`, we'll just rename it `HeroesComponent`
|
||||
and create a new `AppComponent` shell separately.
|
||||
|
||||
The steps are to rename:
|
||||
* <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>
|
||||
* `AppComponent` class to `HeroesComponent` (rename locally, _only_ in this file)
|
||||
* Selector `my-app` to `my-heroes`
|
||||
## Create *AppComponent*
|
||||
|
||||
The new `AppComponent` will be the application shell.
|
||||
It will have some navigation links at the top and a display area below for the pages we navigate to.
|
||||
|
||||
The initial steps are:
|
||||
|
||||
* Create the file <span ngio-ex>src/app/app.component.ts</span>.
|
||||
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
|
||||
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
|
||||
* Move the following from `HeroesComponent` to `AppComponent`:
|
||||
* `title` class property
|
||||
* `@Component` template `<h1>` element, which contains a binding to `title`
|
||||
* Add a `<my-heroes>` element to the app template just below the heading so we still see the heroes.
|
||||
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
|
||||
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because we'll need it in every other view.
|
||||
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it has been promoted.
|
||||
* Add the supporting `import` statements for `AppComponent`.
|
||||
|
||||
Our first draft looks like this:
|
||||
The app still runs and still displays heroes.
|
||||
Our refactoring of `AppComponent` into a new `AppComponent` and a `HeroesComponent` worked!
|
||||
We have done no harm.
|
||||
## Add Routing
|
||||
|
||||
We're ready to take the next step.
|
||||
Instead of displaying heroes automatically, we'd like to show them *after* the user clicks a button.
|
||||
In other words, we'd like to navigate to the list of heroes.
|
||||
|
||||
We'll need the Angular *Router*.
|
||||
|
||||
|
||||
{@a configure-routes}
|
||||
*Routes* tell the router which views to display when a user clicks a link or
|
||||
pastes a URL into the browser address bar.
|
||||
|
||||
Let's define our first route as a route to the heroes component:
|
||||
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
||||
We have only one route definition at the moment but rest assured, we'll add more.
|
||||
|
||||
This *route definition* has the following parts:
|
||||
|
||||
- **path**: the router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}heroes`).
|
||||
<li if-docs="dart"> **name**: the official name of the route;
|
||||
it *must* begin with a capital letter to avoid confusion with the *path* (`Heroes`).</li>
|
||||
- **component**: the component that the router should create when navigating to this route (`HeroesComponent`).
|
||||
|
||||
Learn more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing](../guide/router.html) chapter.
|
||||
### Router Outlet
|
||||
|
||||
If we paste the path, `/heroes`, into the browser address bar,
|
||||
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
||||
But where?
|
||||
|
||||
We have to ***tell it where*** by adding a `<router-outlet>` element to the bottom of the template.
|
||||
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
||||
The router displays each component immediately below the `<router-outlet>` as we navigate through the application.
|
||||
|
||||
### Router Links
|
||||
|
||||
We don't really expect users to paste a route URL into the address bar.
|
||||
We add an anchor tag to the template which, when clicked, triggers navigation to the `HeroesComponent`.
|
||||
|
||||
The revised template looks like this:
|
||||
Refresh the browser. We see only the app title and heroes link. We don't see the heroes list.
|
||||
|
||||
The browser's address bar shows `/`.
|
||||
The route path to `HeroesComponent` is `/heroes`, not `/`.
|
||||
We don't have a route that matches the path `/`, so there is nothing to show.
|
||||
That's something we'll want to fix.
|
||||
We click the *Heroes* navigation link, the browser bar updates to `/heroes`,
|
||||
and now we see the list of heroes. We are navigating at last!
|
||||
|
||||
At this stage, our `AppComponent` looks like this.
|
||||
|
||||
|
||||
{@example 'toh-pt5/ts/src/app/app.component.1.ts' region='v2'}
|
||||
|
||||
The *AppComponent* is now attached to a router and displaying routed views.
|
||||
For this reason and to distinguish it from other kinds of components,
|
||||
we call this type of component a *Router Component*.
|
||||
## Add a *Dashboard*
|
||||
|
||||
Routing only makes sense when we have multiple views. We need another view.
|
||||
|
||||
Create a placeholder `DashboardComponent` that gives us something to navigate to and from.
|
||||
We’ll come back and make it more useful later.
|
||||
|
||||
### Configure the dashboard route
|
||||
|
||||
Go back to `!{_appRoutingTsVsAppComp}` and teach it to navigate to the dashboard.
|
||||
|
||||
Import the dashboard component and
|
||||
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
||||
#### !{_redirectTo}
|
||||
|
||||
We want the app to show the dashboard when it starts and
|
||||
we want to see a nice URL in the browser address bar that says `/dashboard`.
|
||||
Remember that the browser launches with `/` in the address bar.
|
||||
#### Add navigation to the template
|
||||
|
||||
Finally, add a dashboard navigation link to the template, just above the *Heroes* link.
|
||||
|
||||
We nested the two links within `<nav>` tags.
|
||||
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
|
||||
To see these changes in your browser, go to the application root (`/`) and reload.
|
||||
The app displays the dashboard and we can navigate between the dashboard and the heroes.
|
||||
|
||||
## Dashboard Top Heroes
|
||||
|
||||
Let’s spice up the dashboard by displaying the top four heroes at a glance.
|
||||
|
||||
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||||
template file.Create that file with this content:
|
||||
|
||||
|
||||
{@example 'toh-pt5/ts/src/app/dashboard.component.1.html'}
|
||||
|
||||
We use `*ngFor` once again to iterate over a list of heroes and display their names.
|
||||
We added extra `<div>` elements to help with styling later in this chapter.
|
||||
|
||||
### Share the *HeroService*
|
||||
|
||||
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}.
|
||||
|
||||
Recall earlier in the chapter that we removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
||||
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
||||
|
||||
That move created a singleton `HeroService` instance, available to *all* components of the application.
|
||||
Angular will inject `HeroService` and we'll use it here in the `DashboardComponent`.
|
||||
|
||||
### Get heroes
|
||||
|
||||
Open <span ngio-ex>dashboard.component.ts</span> and add the requisite `import` statements.
|
||||
Now implement the `DashboardComponent` class like this:
|
||||
We've seen this kind of logic before in the `HeroesComponent`:
|
||||
|
||||
* Define a `heroes` !{_array} property.
|
||||
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
||||
* Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
|
||||
|
||||
In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice` method</span>.
|
||||
|
||||
Refresh the browser and see four heroes in the new dashboard.
|
||||
|
||||
## Navigate to Hero Details
|
||||
|
||||
Although we display the details of a selected hero at the bottom of the `HeroesComponent`,
|
||||
we don't yet *navigate* to the `HeroDetailComponent` in the three ways specified in our requirements:
|
||||
|
||||
1. from the *Dashboard* to a selected hero.
|
||||
1. from the *Heroes* list to a selected hero.
|
||||
1. from a "deep link" URL pasted into the browser address bar.
|
||||
|
||||
Adding a hero-detail route seems like an obvious place to start.
|
||||
|
||||
### Routing to a hero detail
|
||||
|
||||
We'll add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}` where our other routes are configured.
|
||||
|
||||
The new route is a bit unusual in that we must tell the `HeroDetailComponent` *which hero to show*.
|
||||
We didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||||
|
||||
At the moment the parent `HeroesComponent` sets the component's `hero` property to a
|
||||
hero object with a binding like this.
|
||||
|
||||
<code-example language="html">
|
||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||
|
||||
</code-example>
|
||||
|
||||
That clearly won't work in any of our routing scenarios.
|
||||
Certainly not the last one; we can't embed an entire hero object in the URL! Nor would we want to.
|
||||
|
||||
### Parameterized route
|
||||
|
||||
We *can* add the hero's `id` to the URL. When routing to the hero whose `id` is 11,
|
||||
we could expect to see a URL such as this:
|
||||
|
||||
<code-example format="nocode">
|
||||
/detail/11
|
||||
|
||||
</code-example>
|
||||
|
||||
The `/detail/` part of that URL is constant. The trailing numeric `id` part changes from hero to hero.
|
||||
We need to represent that variable part of the route with a *parameter* (or *token*) that stands for the hero's `id`.
|
||||
|
||||
### Configure a Route with a Parameter
|
||||
|
||||
Here's the *route definition* we'll use.
|
||||
The colon (:) in the path indicates that `:id` is a placeholder to be filled with a specific hero `id`
|
||||
when navigating to the `HeroDetailComponent`.
|
||||
We're finished with the application routes.
|
||||
|
||||
We won't add a `'Hero Detail'` link to the template because users
|
||||
don't click a navigation *link* to view a particular hero.
|
||||
They click a *hero* whether that hero is displayed on the dashboard or in the heroes list.
|
||||
|
||||
We'll get to those *hero* clicks later in the chapter.
|
||||
There's no point in working on them until the `HeroDetailComponent`
|
||||
is ready to be navigated *to*.
|
||||
|
||||
That will require an `HeroDetailComponent` overhaul.
|
||||
|
||||
## Revise the *HeroDetailComponent*
|
||||
|
||||
Before we rewrite the `HeroDetailComponent`, let's review what it looks like now:
|
||||
|
||||
|
||||
{@example 'toh-4/ts/src/app/hero-detail.component.ts'}
|
||||
|
||||
The template won't change. We'll display a hero the same way.
|
||||
The big changes are driven by how we get the hero.
|
||||
First, add the requisite imports:
|
||||
Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
|
||||
into the constructor, saving their values in private fields:
|
||||
We tell the class that we want to implement the `OnInit` interface.
|
||||
The hero `id` is a number. Route parameters are *always strings*.
|
||||
So we convert the route parameter value to a number with the !{_str2int}.
|
||||
### Add *HeroService.getHero*
|
||||
|
||||
The problem with this bit of code is that `HeroService` doesn't have a `getHero` method!
|
||||
We better fix that quickly before someone notices that we broke the app.
|
||||
|
||||
Open `HeroService` and add a `getHero` method that filters the heroes list from `getHeroes` by `id`:
|
||||
Let's return to the `HeroDetailComponent` to clean up loose ends.
|
||||
|
||||
### Find our way back
|
||||
|
||||
We can navigate *to* the `HeroDetailComponent` in several ways.
|
||||
How do we navigate somewhere else when we're done?
|
||||
|
||||
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
|
||||
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
|
||||
using the `Location` service we injected previously.
|
||||
|
||||
Going back too far could take us out of the application.
|
||||
That's acceptable in a demo. We'd guard against it in a real application,
|
||||
perhaps with the [!{_CanDeactivateGuard}](../api/!{_CanDeactivateGuardUri}.html).
|
||||
Then we wire this method with an event binding to a *Back* button that we
|
||||
add to the bottom of the component template.
|
||||
Modifying the template to add this button spurs us to take one more
|
||||
incremental improvement and migrate the template to its own file,
|
||||
called <span ngio-ex>hero-detail.component.html</span>:
|
||||
|
||||
|
||||
{@example 'toh-pt5/ts/src/app/hero-detail.component.html'}
|
||||
|
||||
We update the component metadata with a <span if-docs="ts">`moduleId` and a </span>`templateUrl` pointing to the template file that we just created.
|
||||
Refresh the browser and see the results.
|
||||
|
||||
## Select a *Dashboard* Hero
|
||||
|
||||
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
|
||||
|
||||
Although the dashboard heroes are presented as button-like blocks, they should behave like anchor tags.
|
||||
When hovering over a hero block, the target URL should display in the browser status bar
|
||||
and the user should be able to copy the link or open the hero detail view in a new tab.
|
||||
|
||||
To achieve this effect, reopen the `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
|
||||
with `<a>` tags. The opening `<a>` tag looks like this:
|
||||
|
||||
|
||||
{@example 'toh-pt5/ts/src/app/dashboard.component.html' region='click'}
|
||||
|
||||
Notice the `[routerLink]` binding.
|
||||
|
||||
Top level navigation in the [`AppComponent`
|
||||
template](#router-links) has router links set to fixed !{_pathVsName}s of the
|
||||
destination routes, "/dashboard" and "/heroes".
|
||||
|
||||
This time, we're binding to an expression containing a **link parameters !{_array}**.
|
||||
The !{_array} has two elements, the ***!{_pathVsName}*** of
|
||||
the destination route and a ***route parameter*** set to the value of the current hero's `id`.
|
||||
|
||||
The two !{_array} items align with the ***!{_pathVsName}*** and ***:id***
|
||||
token in the parameterized hero detail route definition we added to
|
||||
`!{_appRoutingTsVsAppComp}` earlier in the chapter:
|
||||
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’s details.
|
||||
|
||||
## Select a Hero in the *HeroesComponent*
|
||||
|
||||
Earlier we added the ability to select a hero from the dashboard.
|
||||
We'll do something similar in the `HeroesComponent`.
|
||||
|
||||
The `HeroesComponent` template exhibits a "master/detail" style with the list of heroes
|
||||
at the top and details of the selected hero below.
|
||||
|
||||
|
||||
{@example 'toh-4/ts/src/app/app.component.ts' region='template'}
|
||||
|
||||
Our goal is to move the detail to its own view and navigate to it when the user decides to edit a selected hero.
|
||||
|
||||
Delete the `<h1>` at the top (we forgot about it during the `AppComponent`-to-`HeroesComponent` conversion).
|
||||
|
||||
Delete the last line of the template with the `<my-hero-detail>` tags.
|
||||
|
||||
We'll no longer show the full `HeroDetailComponent` here.
|
||||
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
|
||||
|
||||
We'll throw in a small twist for variety.
|
||||
We are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
|
||||
When the user selects a hero from the list, we *don't* go to the detail page.
|
||||
We show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
|
||||
|
||||
### Add the *mini-detail*
|
||||
|
||||
Add the following HTML fragment at the bottom of the template where the `<my-hero-detail>` used to be:
|
||||
After clicking a hero, the user should see something like this below the hero list:
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/mini-hero-detail.png' alt="Mini Hero Detail" height="70"> </img>
|
||||
</figure>
|
||||
|
||||
### Format with the *uppercase* pipe
|
||||
|
||||
Notice that the hero's name is displayed in CAPITAL LETTERS. That's the effect of the `uppercase` pipe
|
||||
that we slipped into the interpolation binding. Look for it right after the pipe operator ( | ).
|
||||
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||||
Angular ships with several pipes and we can write our own.
|
||||
|
||||
Learn about pipes in the [Pipes](../guide/pipes.html) chapter.
|
||||
### Move content out of the component file
|
||||
|
||||
We are not done. We still have to update the component class to support navigation to the
|
||||
`HeroDetailComponent` when the user clicks the *View Details* button.
|
||||
|
||||
This component file is really big. Most of it is either template or CSS styles.
|
||||
It's difficult to find the component logic amidst the noise of HTML and CSS.
|
||||
|
||||
Let's migrate the template and the styles to their own files before we make any more changes:
|
||||
|
||||
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
|
||||
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
||||
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
|
||||
<li if-docs="ts">. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component.</li>
|
||||
|
||||
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
||||
We could list multiple style files from different locations if we needed them.
|
||||
### Update the _HeroesComponent_ class.
|
||||
|
||||
The `HeroesComponent` navigates to the `HeroDetailComponent` in response to a button click.
|
||||
The button's _click_ event is bound to a `gotoDetail` method that navigates _imperatively_
|
||||
by telling the router where to go.
|
||||
|
||||
This approach requires some changes to the component class:
|
||||
|
||||
1. Import the `router` from the Angular router library
|
||||
1. Inject the `router` in the constructor (along with the `HeroService`)
|
||||
1. Implement `gotoDetail` by calling the `router.navigate` method
|
||||
Note that we're passing a two-element **link parameters !{_array}**
|
||||
— a path and the route parameter — to
|
||||
the `router.navigate` method just as we did in the `[routerLink]` binding
|
||||
back in the `DashboardComponent`.
|
||||
Here's the fully revised `HeroesComponent` class:
|
||||
Refresh the browser and start clicking.
|
||||
We can navigate around the app, from the dashboard to hero details and back,
|
||||
for heroes list to the mini-detail to the hero details and back to the heroes again.
|
||||
We can jump back and forth between the dashboard and the heroes.
|
||||
|
||||
We've met all of the navigational requirements that propelled this chapter.
|
||||
|
||||
## Styling the App
|
||||
|
||||
The app is functional but pretty ugly.
|
||||
Our creative designer team provided some CSS files to make it look better.
|
||||
|
||||
### A Dashboard with Style
|
||||
|
||||
The designers think we should display the dashboard heroes in a row of rectangles.
|
||||
They've given us ~60 lines of CSS for this purpose including some simple media queries for responsive design.
|
||||
|
||||
If we paste these ~60 lines into the component `styles` metadata,
|
||||
they'll completely obscure the component logic.
|
||||
Let's not do that. It's easier to edit CSS in a separate `*.css` file anyway.
|
||||
|
||||
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference
|
||||
that file in the component metadata's `styleUrls` !{_array} property like this:
|
||||
### Stylish Hero Details
|
||||
|
||||
The designers also gave us CSS styles specifically for the `HeroDetailComponent`.
|
||||
|
||||
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
||||
folder and refer to that file inside
|
||||
the `styleUrls` !{_array} as we did for `DashboardComponent`.
|
||||
Let's also remove the `hero` property `@Input` !{_decorator}
|
||||
<span if-docs="ts">and its import</span>
|
||||
while we are at it.
|
||||
|
||||
Here's the content for the aforementioned component CSS files.
|
||||
### Style the Navigation Links
|
||||
|
||||
The designers gave us CSS to make the navigation links in our `AppComponent` look more like selectable buttons.
|
||||
We cooperated by surrounding those links in `<nav>` tags.
|
||||
|
||||
Add a <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
||||
|
||||
### Global application styles
|
||||
|
||||
When we add styles to a component, we're keeping everything a component needs
|
||||
— HTML, the CSS, the code — together in one convenient place.
|
||||
It's pretty easy to package it all up and re-use the component somewhere else.
|
||||
|
||||
We can also create styles at the *application level* outside of any component.
|
||||
|
||||
Our designers provided some basic styles to apply to elements across the entire app.
|
||||
These correspond to the full set of master styles that we installed earlier during [setup](../guide/setup.html).
|
||||
Here is an excerpt:
|
||||
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already.
|
||||
Ensure that it contains the [master styles given here](!{styles_css}).
|
||||
|
||||
If necessary, also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
||||
Look at the app now. Our dashboard, heroes, and navigation links are styling!
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
## Application structure and code
|
||||
|
||||
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||
Verify that we have the following structure:
|
||||
|
||||
## Recap
|
||||
|
||||
### The Road Behind
|
||||
|
||||
We travelled a great distance in this chapter
|
||||
|
||||
- We added the Angular *Router* to navigate among different components.
|
||||
- We learned how to create router links to represent navigation menu items.
|
||||
- We used router link parameters to navigate to the details of user selected hero.
|
||||
- We shared the `HeroService` among multiple components.
|
||||
- We moved HTML and CSS out of the component file and into their own files.
|
||||
- We added the `uppercase` pipe to format data.
|
||||
<li if-docs="ts"> We refactored routes into a `Routing Module` that we import.</li>
|
||||
|
||||
### The Road Ahead
|
||||
|
||||
We have much of the foundation we need to build an application.
|
||||
We're still missing a key piece: remote data access.
|
||||
|
||||
In the next chapter,
|
||||
we’ll replace our mock data with data retrieved from a server using http.
|
293
aio/content/tutorial/toh-pt6.md
Normal file
293
aio/content/tutorial/toh-pt6.md
Normal file
@ -0,0 +1,293 @@
|
||||
@title
|
||||
HTTP
|
||||
|
||||
@intro
|
||||
We convert our service and components to use Angular's HTTP service
|
||||
|
||||
@description
|
||||
Our stakeholders appreciate our progress.
|
||||
Now they want to get the hero data from a server, let users add, edit, and delete heroes,
|
||||
and save these changes back to the server.
|
||||
|
||||
In this chapter we teach our application to make the corresponding HTTP calls to a remote server's web API.
|
||||
|
||||
Run the <live-example></live-example> for this part.
|
||||
|
||||
## Where We Left Off
|
||||
|
||||
In the [previous chapter](toh-pt5.html), we learned to navigate between the dashboard and the fixed heroes list, editing a selected hero along the way.
|
||||
That's our starting point for this chapter.
|
||||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||||
|
||||
|
||||
<h1>
|
||||
Providing HTTP Services
|
||||
</h1>
|
||||
|
||||
### Register for HTTP services
|
||||
|
||||
## Simulating the web API
|
||||
|
||||
We recommend registering application-wide services in the root
|
||||
`!{_AppModuleVsAppComp}` *providers*. <span if-docs="dart">Here we're
|
||||
registering in `main` for a special reason.</span>
|
||||
|
||||
Our application is in the early stages of development and far from ready for production.
|
||||
We don't even have a web server that can handle requests for heroes.
|
||||
Until we do, *we'll have to fake it*.
|
||||
|
||||
We're going to *trick* the HTTP client into fetching and saving data from
|
||||
a mock service, the *in-memory web API*.
|
||||
<span if-docs="dart"> The application itself doesn't need to know and
|
||||
shouldn't know about this. So we'll slip the in-memory web API into the
|
||||
configuration *above* the `AppComponent`.</span>
|
||||
|
||||
Here is a version of <span ngio-ex>!{_appModuleTsVsMainTs}</span> that performs this trick:
|
||||
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/in-memory-data.service.ts' region='init'}
|
||||
|
||||
|
||||
<p>
|
||||
This file replaces the <code> </code> which is now safe to delete.
|
||||
</p>
|
||||
|
||||
|
||||
## Heroes and HTTP
|
||||
|
||||
Look at our current `HeroService` implementation
|
||||
We returned a !{_Promise} resolved with mock heroes.
|
||||
It may have seemed like overkill at the time, but we were anticipating the
|
||||
day when we fetched heroes with an HTTP client and we knew that would have to be an asynchronous operation.
|
||||
|
||||
That day has arrived! Let's convert `getHeroes()` to use HTTP.
|
||||
Our updated import statements are now:
|
||||
Refresh the browser, and the hero data should be successfully loaded from the
|
||||
mock server.
|
||||
|
||||
<h3 id="!{_h3id}">HTTP !{_Promise}</h3>
|
||||
|
||||
We're still returning a !{_Promise} but we're creating it differently.
|
||||
That response JSON has a single `data` property.
|
||||
The `data` property holds the !{_array} of *heroes* that the caller really wants.
|
||||
So we grab that !{_array} and return it as the resolved !{_Promise} value.
|
||||
|
||||
|
||||
~~~ {.alert.is-important}
|
||||
|
||||
Pay close attention to the shape of the data returned by the server.
|
||||
This particular *in-memory web API* example happens to return an object with a `data` property.
|
||||
Your API might return something else. Adjust the code to match *your web API*.
|
||||
|
||||
|
||||
~~~
|
||||
|
||||
The caller is unaware of these machinations. It receives a !{_Promise} of *heroes* just as it did before.
|
||||
It has no idea that we fetched the heroes from the (mock) server.
|
||||
It knows nothing of the twists and turns required to convert the HTTP response into heroes.
|
||||
Such is the beauty and purpose of delegating data access to a service like this `HeroService`.
|
||||
|
||||
### Error Handling
|
||||
|
||||
At the end of `getHeroes()` we `catch` server failures and pass them to an error handler:
|
||||
This is a critical step!
|
||||
We must anticipate HTTP failures as they happen frequently for reasons beyond our control.
|
||||
In this demo service we log the error to the console; we would do better in real life.
|
||||
|
||||
We've also decided to return a user friendly form of the error to
|
||||
the caller in a !{rejected_promise} so that the caller can display a proper error message to the user.
|
||||
|
||||
### Get hero by id
|
||||
The `HeroDetailComponent` asks the `HeroService` to fetch a single hero to edit.
|
||||
|
||||
The `HeroService` currently fetches all heroes and then finds the desired hero
|
||||
by filtering for the one with the matching `id`.
|
||||
That's fine in a simulation. It's wasteful to ask a real server for _all_ heroes when we only want one.
|
||||
Most web APIs support a _get-by-id_ request in the form `api/hero/:id` (e.g., `api/hero/11`).
|
||||
|
||||
Update the `HeroService.getHero` method to make a _get-by-id_ request,
|
||||
applying what we just learned to write `getHeroes`:It's almost the same as `getHeroes`.
|
||||
The URL identifies _which_ hero the server should update by encoding the hero id into the URL
|
||||
to match the `api/hero/:id` pattern.
|
||||
|
||||
We also adjust to the fact that the `data` in the response is a single hero object rather than !{_an} !{_array}.
|
||||
|
||||
### Unchanged _getHeroes_ API
|
||||
|
||||
Although we made significant *internal* changes to `getHeroes()` and `getHero()`,
|
||||
the public signatures did not change.
|
||||
We still return a !{_Promise} from both methods.
|
||||
We won't have to update any of the components that call them.
|
||||
|
||||
Our stakeholders are thrilled with the web API integration so far.
|
||||
Now they want the ability to create and delete heroes.
|
||||
|
||||
Let's see first what happens when we try to update a hero's details.
|
||||
|
||||
## Update hero details
|
||||
|
||||
We can edit a hero's name already in the hero detail view. Go ahead and try
|
||||
it. As we type, the hero name is updated in the view heading.
|
||||
But when we hit the `Back` button, the changes are lost!
|
||||
|
||||
Updates weren't lost before. What changed?
|
||||
When the app used a list of mock heroes, updates were applied directly to the
|
||||
hero objects within the single, app-wide, shared list. Now that we are fetching data
|
||||
from a server, if we want changes to persist, we'll need to write them back to
|
||||
the server.
|
||||
|
||||
### Save hero details
|
||||
|
||||
Let's ensure that edits to a hero's name aren't lost. Start by adding,
|
||||
to the end of the hero detail template, a save button with a `click` event
|
||||
binding that invokes a new component method named `save`:
|
||||
The `save` method persists hero name changes using the hero service
|
||||
`update` method and then navigates back to the previous view:
|
||||
### Hero service `update` method
|
||||
|
||||
The overall structure of the `update` method is similar to that of
|
||||
`getHeroes`, although we'll use an HTTP _put_ to persist changes
|
||||
server-side:
|
||||
We identify _which_ hero the server should update by encoding the hero id in
|
||||
the URL. The put body is the JSON string encoding of the hero, obtained by
|
||||
calling `!{_JSON_stringify}`. We identify the body content type
|
||||
(`application/json`) in the request header.
|
||||
|
||||
Refresh the browser and give it a try. Changes to hero names should now persist.
|
||||
|
||||
## Add a hero
|
||||
|
||||
To add a new hero we need to know the hero's name. Let's use an input
|
||||
element for that, paired with an add button.
|
||||
|
||||
Insert the following into the heroes component HTML, first thing after
|
||||
the heading:
|
||||
In response to a click event, we call the component's click handler and then
|
||||
clear the input field so that it will be ready to use for another name.
|
||||
When the given name is non-blank, the handler delegates creation of the
|
||||
named hero to the hero service, and then adds the new hero to our !{_array}.
|
||||
|
||||
Finally, we implement the `create` method in the `HeroService` class.Refresh the browser and create some new heroes!
|
||||
|
||||
## Delete a hero
|
||||
|
||||
Too many heroes?
|
||||
Let's add a delete button to each hero in the heroes view.
|
||||
|
||||
Add this button element to the heroes component HTML, right after the hero
|
||||
name in the repeated `<li>` tag:
|
||||
The `<li>` element should now look like this:
|
||||
In addition to calling the component's `delete` method, the delete button
|
||||
click handling code stops the propagation of the click event — we
|
||||
don't want the `<li>` click handler to be triggered because that would
|
||||
select the hero that we are going to delete!
|
||||
|
||||
The logic of the `delete` handler is a bit trickier:
|
||||
Of course, we delegate hero deletion to the hero service, but the component
|
||||
is still responsible for updating the display: it removes the deleted hero
|
||||
from the !{_array} and resets the selected hero if necessary.
|
||||
We want our delete button to be placed at the far right of the hero entry.
|
||||
This extra CSS accomplishes that:
|
||||
### Hero service `delete` method
|
||||
|
||||
The hero service's `delete` method uses the _delete_ HTTP method to remove the hero from the server:
|
||||
Refresh the browser and try the new delete functionality.
|
||||
|
||||
<div id='observables'>
|
||||
|
||||
</div>
|
||||
|
||||
## !{_Observable}s
|
||||
But requests aren't always "one and done". We may start one request,
|
||||
then cancel it, and make a different request before the server has responded to the first request.
|
||||
Such a _request-cancel-new-request_ sequence is difficult to implement with *!{_Promise}s*.
|
||||
It's easy with *!{_Observable}s* as we'll see.
|
||||
|
||||
### Search-by-name
|
||||
|
||||
We're going to add a *hero search* feature to the Tour of Heroes.
|
||||
As the user types a name into a search box, we'll make repeated HTTP requests for heroes filtered by that name.
|
||||
|
||||
We start by creating `HeroSearchService` that sends search queries to our server's web api.
|
||||
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/hero-search.service.ts'}
|
||||
|
||||
The `!{_priv}http.get()` call in `HeroSearchService` is similar to the one
|
||||
in the `HeroService`, although the URL now has a query string.
|
||||
### HeroSearchComponent
|
||||
|
||||
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
|
||||
|
||||
The component template is simple — just a text box and a list of matching search results.
|
||||
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/hero-search.component.html'}
|
||||
|
||||
We'll also want to add styles for the new component.
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/hero-search.component.css'}
|
||||
|
||||
As the user types in the search box, a *keyup* event binding calls the component's `search` method with the new search box value.
|
||||
|
||||
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
|
||||
|
||||
But, as we'll soon see, the `heroes` property is now !{_an} *!{_Observable}* of hero !{_array}s, rather than just a hero !{_array}.
|
||||
The `*ngFor` can't do anything with !{_an} `!{_Observable}` until we flow it through the `async` pipe (`AsyncPipe`).
|
||||
The `async` pipe subscribes to the `!{_Observable}` and produces the !{_array} of heroes to `*ngFor`.
|
||||
|
||||
Time to create the `HeroSearchComponent` class and metadata.
|
||||
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/hero-search.component.ts'}
|
||||
|
||||
#### Search terms
|
||||
|
||||
Let's focus on the `!{_priv}searchTerms`:
|
||||
<a id="ngoninit"></a>
|
||||
#### Initialize the _**heroes**_ property (_**ngOnInit**_)
|
||||
|
||||
<span if-docs="ts">A `Subject` is also an `Observable`.</span>
|
||||
We're going to turn the stream
|
||||
of search terms into a stream of `Hero` !{_array}s and assign the result to the `heroes` property.
|
||||
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of HTTP requests.
|
||||
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
|
||||
### Add the search component to the dashboard
|
||||
|
||||
We add the hero search HTML element to the bottom of the `DashboardComponent` template.
|
||||
|
||||
|
||||
{@example 'toh-pt6/ts/src/app/dashboard.component.html'}
|
||||
|
||||
Finally, we import `HeroSearchComponent` from
|
||||
<span ngio-ex>hero-search.component.ts</span>
|
||||
and add it to the `!{_declarations}` !{_array}:
|
||||
Run the app again, go to the *Dashboard*, and enter some text in the search box.
|
||||
At some point it might look like this.
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component"> </img>
|
||||
</figure>
|
||||
|
||||
|
||||
## Application structure and code
|
||||
|
||||
Review the sample source code in the <live-example></live-example> for this chapter.
|
||||
Verify that we have the following structure:
|
||||
|
||||
## Home Stretch
|
||||
|
||||
We are at the end of our journey for now, but we have accomplished a lot.
|
||||
- We added the necessary dependencies to use HTTP in our application.
|
||||
- We refactored `HeroService` to load heroes from a web API.
|
||||
- We extended `HeroService` to support post, put and delete methods.
|
||||
- We updated our components to allow adding, editing and deleting of heroes.
|
||||
- We configured an in-memory web API.
|
||||
- We learned how to use !{_Observable}s.
|
||||
|
||||
Here are the files we _added or changed_ in this chapter.
|
||||
|
||||
### Next Step
|
||||
|
||||
Return to the [learning path](../guide/learning-angular.html#architecture) where
|
||||
you can read about the concepts and practices you discovered in this tutorial.
|
Reference in New Issue
Block a user