2015-10-15 03:51:24 -04:00
|
|
|
|
include ../../../../_includes/_util-fns
|
|
|
|
|
|
|
|
|
|
.l-main-section
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
# Once Upon a Time
|
|
|
|
|
|
2015-10-15 15:25:36 -04:00
|
|
|
|
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
|
|
|
|
|
Follow the "QuickStart" steps. They provide the prerequisites, the folder structure,
|
|
|
|
|
and the core files for our Tour of Heroes.
|
|
|
|
|
|
|
|
|
|
Copy the "QuickStart" code to a new folder and rename the folder `angular2-tour-of-heroes`.
|
|
|
|
|
We should have the following structure:
|
|
|
|
|
|
|
|
|
|
code-example.
|
|
|
|
|
angular2-tour-of-heroes
|
|
|
|
|
├── node_modules
|
|
|
|
|
├── src
|
|
|
|
|
| ├── app
|
|
|
|
|
| | └── app.ts
|
|
|
|
|
| ├── index.html
|
|
|
|
|
| └── tsconfig.json
|
|
|
|
|
└── package.json
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
## Keep the App Running
|
|
|
|
|
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
|
|
|
|
|
|
|
|
|
pre.prettyprint.lang-bash
|
|
|
|
|
code npm run tsc
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
Now open another terminal window and start the server by typing
|
|
|
|
|
|
|
|
|
|
pre.prettyprint.lang-bash
|
|
|
|
|
code npm start
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
This command starts the server, launches the app in a browser,
|
|
|
|
|
and keeps the app running while we continue to build the Tour of Heroes.
|
|
|
|
|
|
|
|
|
|
.alert.is-helpful
|
|
|
|
|
:markdown
|
|
|
|
|
These two steps watch all project files. They recompile TypeScript files and re-run
|
|
|
|
|
the app when any file changes.
|
|
|
|
|
If the watchers fail to detect renamed or new files,
|
|
|
|
|
stop these commands in each terminal by typing `CTRL+C` and then re-run them.
|
|
|
|
|
|
|
|
|
|
.l-main-section
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
# Show our Hero
|
|
|
|
|
We want to display Hero data in our app
|
|
|
|
|
|
|
|
|
|
Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property
|
|
|
|
|
for a hero named "Windstorm".
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
class AppComponent {
|
|
|
|
|
public title = 'Tour of Heroes';
|
|
|
|
|
public hero = 'Windstorm';
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
Now we update the template in the `@Component` decoration with data bindings to these new properties.
|
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
|
|
|
|
|
:markdown
|
|
|
|
|
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;
|
|
|
|
|
we can learn more about interpolation in the [Displaying Data chapter](displaying-data).
|
|
|
|
|
|
|
|
|
|
## 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.
|
2015-10-15 21:51:02 -04:00
|
|
|
|
Keep this near the top of the `app.ts` file for now.
|
2015-10-15 03:51:24 -04:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
class Hero {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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".
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
public hero: Hero = {
|
|
|
|
|
id: 1,
|
|
|
|
|
name: 'Windstorm'
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
|
|
|
|
|
:markdown
|
|
|
|
|
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`.
|
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
|
|
|
|
|
:markdown
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
template:`
|
|
|
|
|
<h1>{{title}}</h1>
|
|
|
|
|
<h2>{{hero.name}} details!</h2>
|
|
|
|
|
<div><label>id: </label>{{hero.id}}</div>
|
|
|
|
|
<div><label>name: </label>{{hero.name}}</div>
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
.callout.is-important
|
|
|
|
|
header A back-tick is not a single quote
|
|
|
|
|
:markdown
|
|
|
|
|
**Be careful!** A back-tick (`) looks a lot like a single quote (').
|
|
|
|
|
It's actually a completely different character.
|
|
|
|
|
Back-ticks can do more than demarcate a string.
|
|
|
|
|
Here we use them in a limited way to spread the template over multiple lines.
|
|
|
|
|
Everything between the back-ticks at the beginning and end of the template
|
|
|
|
|
is part of a single template string.
|
|
|
|
|
|
|
|
|
|
.l-main-section
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
# Editing Our Hero
|
|
|
|
|
|
|
|
|
|
We want to be able to edit the hero name in a textbox.
|
|
|
|
|
|
2015-10-15 22:01:59 -04:00
|
|
|
|
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
|
2015-10-15 03:51:24 -04:00
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
template:`
|
|
|
|
|
<h1>{{title}}</h1>
|
|
|
|
|
<h2>{{hero.name}} details!</h2>
|
|
|
|
|
<div><label>id: </label>{{hero.id}}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>name: </label>
|
|
|
|
|
<div><input value="{{hero.name}}" placeholder="name"></input></div>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
:markdown
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
Let’s update the template to use the **`ng-model`** built-in directive for two-way binding.
|
|
|
|
|
|
|
|
|
|
.alert.is-helpful
|
|
|
|
|
:markdown
|
2015-10-15 15:25:36 -04:00
|
|
|
|
Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax.html#ng-model)
|
2015-10-15 03:51:24 -04:00
|
|
|
|
:markdown
|
|
|
|
|
Replace the `<input>` with the following HTML
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
<input [(ng-model)]="hero.name" placeholder="name"></input>
|
|
|
|
|
```
|
|
|
|
|
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
|
|
|
|
|
Let’s fix that next.
|
|
|
|
|
|
|
|
|
|
.l-main-section
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
# Declaring Template Directives
|
|
|
|
|
|
|
|
|
|
We added the `ng-model` directive but we didn't tell Angular about it.
|
|
|
|
|
A component must disclose every directive that appears in its template.
|
|
|
|
|
|
|
|
|
|
Let’s first gain access to the `NgModel` directive class by importing it from Angular as shown below:
|
|
|
|
|
|
|
|
|
|
````
|
|
|
|
|
import {bootstrap, Component, NgModel} from 'angular2/angular2';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Now tell the component that we will use the `ng-model` directive in the template
|
|
|
|
|
by adding the `directives` property to the `@Component` decoration
|
|
|
|
|
immediately below the `template` string:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
directives: [NgModel]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `directives` property is an array holding all directive classes that
|
|
|
|
|
are used by the component’s template.
|
|
|
|
|
|
|
|
|
|
Unfortunately when we view the app in the browser we still have an error:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
*EXCEPTION: No value accessor for ' ' in [null]*
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Apparently declaring the `NgModel` is not quite enough.
|
|
|
|
|
|
|
|
|
|
## Declare Multiple Form Directives
|
|
|
|
|
|
|
|
|
|
We learned from our latest error message that we can’t the import `NgModel` alone.
|
|
|
|
|
We need additional directives to enable two-way data binding with `NgModel`.
|
|
|
|
|
|
|
|
|
|
We could hunt them down and add each of them to the `directives` array one by one.
|
|
|
|
|
That's painful. No one wants to remember all of the necessary directives and
|
|
|
|
|
type them correctly. Fortunately, there is a shortcut.
|
|
|
|
|
|
|
|
|
|
The `ng-model` directive is one of many Forms directives which happen to be
|
|
|
|
|
bundled in a convenient array called `FORM_DIRECTIVES`.
|
|
|
|
|
<!-- TODO
|
|
|
|
|
.alert.is-helpful
|
|
|
|
|
:markdown
|
|
|
|
|
Learn more about Angular Forms in the [Forms chapter]()
|
|
|
|
|
:markdown
|
|
|
|
|
-->
|
|
|
|
|
Let’s forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
|
|
|
|
|
```
|
|
|
|
|
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
|
|
|
|
|
```
|
|
|
|
|
Now we tell the component that our template can use `FORM_DIRECTIVES`
|
|
|
|
|
by updating the `directives` property of the `@Component` decorator.
|
|
|
|
|
```
|
|
|
|
|
directives: [FORM_DIRECTIVES]
|
|
|
|
|
```
|
|
|
|
|
The browser refreshes. We see our hero again. We can edit the hero’s name and
|
|
|
|
|
see the changes reflected immediately in the `<h2>`.
|
|
|
|
|
|
|
|
|
|
### Bundled Directives
|
|
|
|
|
Angular bundled the Form-related directives together in a convenient `FORM_DIRECTIVES` array.
|
|
|
|
|
That's all we need to remember to light up our template.
|
|
|
|
|
|
|
|
|
|
We may wish to use this trick ourselves someday.
|
|
|
|
|
We too can bundle a collection of directives in an array, give it a catchy name,
|
|
|
|
|
and plug that array into the `directives` property.
|
|
|
|
|
|
|
|
|
|
# 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 form 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 `ng-model` directive.
|
|
|
|
|
* The `ng-model` directive also propagates changes to every other binding of the `hero.name`.
|
|
|
|
|
* We declared our use of `NgModel` and other Form directives
|
|
|
|
|
by setting the component's `directives` metadata property to the `FORMS_DIRECTIVES` array.
|
|
|
|
|
|
|
|
|
|
Here's the complete `AppComponent.ts` as it stands now:
|
|
|
|
|
|
|
|
|
|
code-example(format="linenums").
|
|
|
|
|
import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2';
|
|
|
|
|
|
|
|
|
|
class Hero {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
template:`
|
|
|
|
|
<h1>{{title}}</h1>
|
|
|
|
|
<h2>{{hero.name}} details!</h2>
|
|
|
|
|
<div><label>id: </label>{{hero.id}}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>name: </label>
|
|
|
|
|
<div><input value="{{hero.name}}" placeholder="name"></input></div>
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
directives: [FORM_DIRECTIVES]
|
|
|
|
|
})
|
|
|
|
|
class AppComponent {
|
|
|
|
|
public title = 'Tour of Heroes';
|
|
|
|
|
public hero: Hero = {
|
|
|
|
|
id: 1,
|
|
|
|
|
name: 'Windstorm'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bootstrap(AppComponent);
|
|
|
|
|
|
|
|
|
|
:markdown
|
|
|
|
|
# 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 it in the
|
2015-10-15 14:33:43 -04:00
|
|
|
|
[next tutorial chapter](./toh-pt2.html).
|