246 lines
9.3 KiB
Markdown
Raw Normal View History

# The hero editor
2017-11-06 19:02:18 +01:00
The application now has a basic title.
Next you will create a new component to display hero information
and place that component in the application shell.
2017-04-01 01:57:13 +02:00
<div class="alert is-helpful">
For the sample app that this page describes, see the <live-example></live-example>.
</div>
2017-11-06 19:02:18 +01:00
## Create the heroes component
2017-08-29 11:01:56 +08:00
2017-11-06 19:02:18 +01:00
Using the Angular CLI, generate a new component named `heroes`.
<code-example language="sh" class="code-shell">
2017-11-06 19:02:18 +01:00
ng generate component heroes
</code-example>
The CLI creates a new folder, `src/app/heroes/`, and generates
the three files of the `HeroesComponent` along with a test file.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
The `HeroesComponent` class file is as follows:
2017-04-01 01:57:13 +02:00
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1" header="app/heroes/heroes.component.ts (initial version)"></code-example>
2017-11-06 19:02:18 +01:00
You always import the `Component` symbol from the Angular core library
and annotate the component class with `@Component`.
2017-11-06 19:02:18 +01:00
`@Component` is a decorator function that specifies the Angular metadata for the component.
2017-11-06 19:02:18 +01:00
The CLI generated three metadata properties:
2017-04-01 01:57:13 +02:00
1. `selector`&mdash; the component's CSS element selector
2017-11-06 19:02:18 +01:00
1. `templateUrl`&mdash; the location of the component's template file.
1. `styleUrls`&mdash; the location of the component's private CSS styles.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
{@a selector}
2017-11-06 19:02:18 +01:00
The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors),
`'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template.
The `ngOnInit()` is a [lifecycle hook](guide/lifecycle-hooks#oninit).
Angular calls `ngOnInit()` shortly after creating a component.
2017-11-06 19:02:18 +01:00
It's a good place to put initialization logic.
2017-11-06 19:02:18 +01:00
Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`.
### Add a `hero` property
2017-11-06 19:02:18 +01:00
Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm."
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="add-hero" header="heroes.component.ts (hero property)"></code-example>
2017-11-06 19:02:18 +01:00
### Show the hero
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
Open the `heroes.component.html` template file.
Delete the default text generated by the Angular CLI and
replace it with a data binding to the new `hero` property.
2017-04-01 01:57:13 +02:00
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" header="heroes.component.html" region="show-hero-1"></code-example>
## Show the `HeroesComponent` view
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`.
2017-04-01 01:57:13 +02:00
Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`.
2017-11-06 19:02:18 +01:00
So add an `<app-heroes>` element to the `AppComponent` template file, just below the title.
<code-example path="toh-pt1/src/app/app.component.html" header="src/app/app.component.html"></code-example>
2017-11-06 19:02:18 +01:00
Assuming that the CLI `ng serve` command is still running,
the browser should refresh and display both the application title and the hero name.
## Create a Hero interface
2017-11-06 19:02:18 +01:00
A real hero is more than a name.
Create a `Hero` interface in its own file in the `src/app` folder.
2017-11-06 19:02:18 +01:00
Give it `id` and `name` properties.
Merge remote-tracking branch 'en/master' into aio # Conflicts: # .nvmrc # aio/content/cli/index.md # aio/content/guide/ajs-quick-reference.md # aio/content/guide/animations.md # aio/content/guide/aot-compiler.md # aio/content/guide/app-shell.md # aio/content/guide/architecture-components.md # aio/content/guide/architecture-modules.md # aio/content/guide/architecture-services.md # aio/content/guide/architecture.md # aio/content/guide/attribute-directives.md # aio/content/guide/bazel.md # aio/content/guide/bootstrapping.md # aio/content/guide/browser-support.md # aio/content/guide/build.md # aio/content/guide/cheatsheet.md # aio/content/guide/cli-builder.md # aio/content/guide/comparing-observables.md # aio/content/guide/complex-animation-sequences.md # aio/content/guide/component-interaction.md # aio/content/guide/component-styles.md # aio/content/guide/creating-libraries.md # aio/content/guide/dependency-injection-in-action.md # aio/content/guide/dependency-injection-navtree.md # aio/content/guide/dependency-injection-providers.md # aio/content/guide/dependency-injection.md # aio/content/guide/deployment.md # aio/content/guide/deprecations.md # aio/content/guide/displaying-data.md # aio/content/guide/dynamic-component-loader.md # aio/content/guide/dynamic-form.md # aio/content/guide/elements.md # aio/content/guide/entry-components.md # aio/content/guide/feature-modules.md # aio/content/guide/file-structure.md # aio/content/guide/form-validation.md # aio/content/guide/forms-overview.md # aio/content/guide/forms.md # aio/content/guide/frequent-ngmodules.md # aio/content/guide/glossary.md # aio/content/guide/hierarchical-dependency-injection.md # aio/content/guide/http.md # aio/content/guide/i18n.md # aio/content/guide/ivy.md # aio/content/guide/language-service.md # aio/content/guide/lazy-loading-ngmodules.md # aio/content/guide/lifecycle-hooks.md # aio/content/guide/module-types.md # aio/content/guide/ngmodule-api.md # aio/content/guide/ngmodule-faq.md # aio/content/guide/ngmodule-vs-jsmodule.md # aio/content/guide/ngmodules.md # aio/content/guide/npm-packages.md # aio/content/guide/observables-in-angular.md # aio/content/guide/observables.md # aio/content/guide/pipes.md # aio/content/guide/providers.md # aio/content/guide/reactive-forms.md # aio/content/guide/releases.md # aio/content/guide/reusable-animations.md # aio/content/guide/route-animations.md # aio/content/guide/router.md # aio/content/guide/rx-library.md # aio/content/guide/schematics-authoring.md # aio/content/guide/schematics-for-libraries.md # aio/content/guide/schematics.md # aio/content/guide/security.md # aio/content/guide/service-worker-communications.md # aio/content/guide/service-worker-config.md # aio/content/guide/service-worker-devops.md # aio/content/guide/service-worker-getting-started.md # aio/content/guide/service-worker-intro.md # aio/content/guide/set-document-title.md # aio/content/guide/setup-local.md # aio/content/guide/sharing-ngmodules.md # aio/content/guide/singleton-services.md # aio/content/guide/static-query-migration.md # aio/content/guide/structural-directives.md # aio/content/guide/styleguide.md # aio/content/guide/template-syntax.md # aio/content/guide/testing.md # aio/content/guide/transition-and-triggers.md # aio/content/guide/typescript-configuration.md # aio/content/guide/universal.md # aio/content/guide/updating.md # aio/content/guide/upgrade-setup.md # aio/content/guide/upgrade.md # aio/content/guide/user-input.md # aio/content/guide/using-libraries.md # aio/content/guide/visual-studio-2015.md # aio/content/guide/web-worker.md # aio/content/guide/workspace-config.md # aio/content/marketing/events.html # aio/content/marketing/resources.json # aio/content/navigation.json # aio/content/start/data.md # aio/content/start/deployment.md # aio/content/start/forms.md # aio/content/start/index.md # aio/content/start/routing.md # aio/content/tutorial/index.md # aio/content/tutorial/toh-pt0.md # aio/content/tutorial/toh-pt1.md # aio/content/tutorial/toh-pt2.md # aio/content/tutorial/toh-pt3.md # aio/content/tutorial/toh-pt4.md # aio/content/tutorial/toh-pt5.md # aio/content/tutorial/toh-pt6.md # aio/package.json # aio/src/app/app.component.spec.ts # aio/src/app/custom-elements/api/api-list.component.html # aio/src/app/layout/doc-viewer/doc-viewer.component.ts # aio/src/app/layout/mode-banner/mode-banner.component.ts # aio/src/app/layout/nav-item/nav-item.component.html # aio/src/app/shared/toc.service.ts # aio/src/styles/0-base/_typography.scss # aio/src/styles/2-modules/_api-pages.scss # aio/tools/transforms/templates/api/lib/memberHelpers.html # aio/yarn.lock # packages/common/http/src/headers.ts # packages/common/http/src/interceptor.ts # packages/common/http/src/params.ts # packages/common/http/src/response.ts # packages/common/src/common_module.ts # packages/common/src/directives/ng_for_of.ts # packages/common/src/location/location.ts # packages/common/src/pipes/date_pipe.ts # packages/core/src/change_detection/pipe_transform.ts # packages/core/src/di/injectable.ts # packages/core/src/metadata/di.ts # packages/core/src/metadata/directives.ts # packages/core/src/metadata/ng_module.ts # packages/core/src/render3/component_ref.ts # packages/forms/src/directives/reactive_directives/form_control_name.ts # packages/forms/src/form_builder.ts # packages/forms/src/model.ts # packages/forms/src/validators.ts # packages/router/src/config.ts # packages/router/src/directives/router_outlet.ts # packages/router/src/events.ts # packages/router/src/router.ts # packages/router/src/router_module.ts # packages/router/src/router_state.ts # packages/router/src/shared.ts
2020-01-11 18:10:12 +08:00
<code-example path="toh-pt1/src/app/hero.ts" header="src/app/hero.ts"></code-example>
2017-04-01 01:57:13 +02:00
Return to the `HeroesComponent` class and import the `Hero` interface.
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
Refactor the component's `hero` property to be of type `Hero`.
Initialize it with an `id` of `1` and the name `Windstorm`.
2017-11-06 19:02:18 +01:00
The revised `HeroesComponent` class file should look like this:
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts"></code-example>
2017-11-06 19:02:18 +01:00
The page no longer displays properly because you changed the hero from a string to an object.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
## Show the hero object
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
Update the binding in the template to announce the hero's name
and show both `id` and `name` in a details layout like this:
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" region="show-hero-2" header="heroes.component.html (HeroesComponent's template)"></code-example>
The browser refreshes and displays the hero's information.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
## Format with the _UppercasePipe_
2017-11-06 19:02:18 +01:00
Modify the `hero.name` binding like this.
<code-example path="toh-pt1/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="pipe">
</code-example>
2017-11-06 19:02:18 +01:00
The browser refreshes and now the hero's name is displayed in capital letters.
The word `uppercase` in the interpolation binding,
2017-11-06 19:02:18 +01:00
right after the pipe operator ( | ),
activates the built-in `UppercasePipe`.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
[Pipes](guide/pipes) are a good way to format strings, currency amounts, dates and other display data.
Angular ships with several built-in pipes and you can create your own.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
## Edit the hero
Users should be able to edit the hero name in an `<input>` textbox.
The textbox should both _display_ the hero's `name` property
and _update_ that property as the user types.
That means data flows from the component class _out to the screen_ and
2017-11-06 19:02:18 +01:00
from the screen _back to the class_.
2017-11-06 19:02:18 +01:00
To automate that data flow, setup a two-way data binding between the `<input>` form element and the `hero.name` property.
### Two-way binding
2017-11-06 19:02:18 +01:00
Refactor the details area in the `HeroesComponent` template so it looks like this:
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" region="name-input" header="src/app/heroes/heroes.component.html (HeroesComponent's template)"></code-example>
**[(ngModel)]** is Angular's two-way data binding syntax.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
Here it binds the `hero.name` property to the HTML textbox so that data can flow _in both directions:_ from the `hero.name` property to the textbox, and from the textbox back to the `hero.name`.
2017-11-06 19:02:18 +01:00
### The missing _FormsModule_
2017-11-06 19:02:18 +01:00
Notice that the app stopped working when you added `[(ngModel)]`.
2017-11-06 19:02:18 +01:00
To see the error, open the browser development tools and look in the console
for a message like
2017-11-06 19:02:18 +01:00
<code-example language="sh" class="code-shell">
Template parse errors:
Can't bind to 'ngModel' since it isn't a known property of 'input'.
</code-example>
Although `ngModel` is a valid Angular directive, it isn't available by default.
2017-11-06 19:02:18 +01:00
It belongs to the optional `FormsModule` and you must _opt-in_ to using it.
2017-11-06 19:02:18 +01:00
## _AppModule_
2017-11-06 19:02:18 +01:00
Angular needs to know how the pieces of your application fit together
and what other files and libraries the app requires.
This information is called _metadata_.
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
Some of the metadata is in the `@Component` decorators that you added to your component classes.
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
2017-04-01 01:57:13 +02:00
The most important `@NgModule` decorator annotates the top-level **AppModule** class.
2017-11-06 19:02:18 +01:00
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
This is where you _opt-in_ to the `FormsModule`.
2017-11-06 19:02:18 +01:00
### Import _FormsModule_
Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library.
2017-04-01 01:57:13 +02:00
<code-example path="toh-pt1/src/app/app.module.ts" header="app.module.ts (FormsModule symbol import)"
2017-11-06 19:02:18 +01:00
region="formsmodule-js-import">
</code-example>
2017-04-01 01:57:13 +02:00
2017-11-06 19:02:18 +01:00
Then add `FormsModule` to the `@NgModule` metadata's `imports` array, which contains a list of external modules that the app needs.
2017-04-01 01:57:13 +02:00
<code-example path="toh-pt1/src/app/app.module.ts" header="app.module.ts (@NgModule imports)"
2017-11-06 19:02:18 +01:00
region="ng-imports">
</code-example>
2017-11-06 19:02:18 +01:00
When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the `<h2>` above the textbox.
### Declare `HeroesComponent`
Every component must be declared in _exactly one_ [NgModule](guide/ngmodules).
2017-11-06 19:02:18 +01:00
_You_ didn't declare the `HeroesComponent`.
So why did the application work?
2017-11-06 19:02:18 +01:00
It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component.
2017-11-06 19:02:18 +01:00
Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top.
<code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="heroes-import" >
</code-example>
2017-11-06 19:02:18 +01:00
The `HeroesComponent` is declared in the `@NgModule.declarations` array.
<code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="declarations">
2017-11-06 19:02:18 +01:00
</code-example>
2017-11-06 19:02:18 +01:00
Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`.
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
## Final code review
Here are the code files discussed on this page.
2018-03-08 17:07:55 +08:00
2017-11-06 19:02:18 +01:00
<code-tabs>
<code-pane header="src/app/heroes/heroes.component.ts" path="toh-pt1/src/app/heroes/heroes.component.ts">
2017-11-06 19:02:18 +01:00
</code-pane>
<code-pane header="src/app/heroes/heroes.component.html" path="toh-pt1/src/app/heroes/heroes.component.html">
2017-11-06 19:02:18 +01:00
</code-pane>
<code-pane header="src/app/app.module.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt1/src/app/app.module.ts">
</code-pane>
<code-pane header="src/app/app.component.ts" path="toh-pt1/src/app/app.component.ts">
2017-11-06 19:02:18 +01:00
</code-pane>
2017-04-01 01:57:13 +02:00
<code-pane header="src/app/app.component.html" path="toh-pt1/src/app/app.component.html">
2017-11-06 19:02:18 +01:00
</code-pane>
2017-04-01 01:57:13 +02:00
<code-pane header="src/app/hero.ts"
2017-11-06 19:02:18 +01:00
path="toh-pt1/src/app/hero.ts">
</code-pane>
2017-11-06 19:02:18 +01:00
</code-tabs>
## Summary
2017-11-06 19:02:18 +01:00
* You used the CLI to create a second `HeroesComponent`.
* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell.
2017-11-06 19:02:18 +01:00
* You applied the `UppercasePipe` to format the name.
* You used two-way data binding with the `ngModel` directive.
* You learned about the `AppModule`.
* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive.
2017-11-06 19:02:18 +01:00
* You learned the importance of declaring components in the `AppModule`
2018-03-03 21:06:01 +08:00
and appreciated that the CLI declared it for you.