angular-cn/public/docs/ts/latest/tutorial/toh-pt1.jade

315 lines
11 KiB
Plaintext
Raw Normal View History

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.
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}}&lt/h1>&lth2>{{hero}} details!&lt/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.
```
class Hero {
id: number;
name: string;
}
```
Now that we have a `Hero` class, lets refactor our components `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 heros `name` property.
code-example(format="linenums").
template: '<h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>'
:markdown
The browser refreshes and continues to display our heros name.
## **Adding more HTML**
Displaying a name is good, but we want to see all of our heros properties.
Well add a `<div>` for our heros `id` property and another `<div>` for our heros `name`.
code-example(format="linenums").
template: '&lt;h1>{{title}}&lt/h1>&lth2>{{hero.name}} details!&lt/h2>&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/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,
lets 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:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>&ltlabel>name: &lt/label>{{hero.name}}&lt/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.
Replace the hero name `<label>` with an `<input>` element as shown below:
code-example(format="linenums").
template:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/div>
`
:markdown
We see in the browser that the heros name does appear in the `<input>` textbox.
But something doesnt 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 heros name.
In short, we want two-way data binding.
Lets 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)
: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.
Lets 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.
Lets 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 components 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 cant 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
-->
Lets 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 heros 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 Weve Travelled
Lets take stock of what weve 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 ES2015s template strings to make our template readable.
* We can both display and change the heros 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:`
&lt;h1>{{title}}&lt/h1>
&lth2>{{hero.name}} details!&lt/h2>
&ltdiv>&ltlabel>id: &lt/label>{{hero.id}}&lt/div>
&ltdiv>
&ltlabel>name: &lt/label>
&ltdiv>&ltinput value="{{hero.name}}" placeholder="name">&lt/input>&lt/div>
&lt/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.
Well learn more about how to retrieve lists, bind them to the
template, and allow a user to select it in the
[next tutorial chapter](./toh-pt2.html).