(docs) testing and toh formatting improvements
This commit is contained in:
parent
cdec7d1605
commit
93a646d63f
|
@ -3,7 +3,7 @@
|
|||
|
||||
// #docregion class-w-annotations
|
||||
var AppComponent = ng
|
||||
ng.Component({
|
||||
.Component({
|
||||
selector: 'my-app',
|
||||
template: '<h1>My First Angular 2 App</h1>'
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<script src="node_modules/angular2/bundles/angular2.sfx.dev.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Getting Started</title>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<script src="https://code.angularjs.org/tools/system.js"></script>
|
||||
<script src="https://code.angularjs.org/tools/typescript.js"></script>
|
||||
<script src="https://code.angularjs.org/2.0.0-alpha.42/angular2.dev.js"></script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>Getting Started</title>
|
||||
<title>Angular 2 QuickStart</title>
|
||||
<!-- #docregion libraries -->
|
||||
<script src="../node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
|
|
|
@ -2,22 +2,27 @@
|
|||
"_listtype": "ordered",
|
||||
|
||||
"index": {
|
||||
"title": "Testing Guides"
|
||||
"title": "Testing Guides",
|
||||
"intro": "Techniques and practices for testing an Angular 2 app"
|
||||
},
|
||||
|
||||
"jasmine-testing-101": {
|
||||
"title": "Jasmine Testing 101"
|
||||
"title": "Jasmine Testing 101",
|
||||
"intro": "The basics of testing anything with Jasmine"
|
||||
},
|
||||
|
||||
"application-under-test": {
|
||||
"title": "The Application Under Test"
|
||||
"title": "The Application Under Test",
|
||||
"intro": "A quick look at the application we will test"
|
||||
},
|
||||
|
||||
"first-app-tests": {
|
||||
"title": "First App Tests"
|
||||
"title": "First App Tests",
|
||||
"intro": "The first test of a simple, non-Angular part of our app"
|
||||
},
|
||||
|
||||
"testing-an-angular-pipe": {
|
||||
"title": "Testing an Angular Pipe"
|
||||
"title": "Testing an Angular Pipe",
|
||||
"intro": "We test an Angular-aware part of our app"
|
||||
}
|
||||
}
|
|
@ -32,5 +32,7 @@ figure.image-display
|
|||
|
||||
We’ll examine the implementation details as we evolve our tests.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## What’s Next?
|
||||
Now that we’re familiar with how the test app works, we’re ready to poke at it with our first application tests written in Jasmine.
|
||||
|
|
|
@ -16,6 +16,8 @@ include ../../../../_includes/_util-fns
|
|||
we introduced in the [QuickStart](../quickstart.html) and
|
||||
the [Tour of Heroes](../tutorial/) tutorial
|
||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Create the test-runner HTML
|
||||
|
||||
|
@ -44,6 +46,8 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
We’re picking up right where we left off. All we’ve done is change the title.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Update `package.json` for testing
|
||||
|
||||
We’ll assume that the application has `package.json` file that looks more or less like
|
||||
|
@ -66,6 +70,8 @@ pre.prettyprint.lang-bash
|
|||
|
||||
That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## First app tests
|
||||
|
||||
Believe it or not … we could start testing *some* of our app right away. For example, we can test the `Hero` class:
|
||||
|
@ -128,6 +134,8 @@ pre.prettyprint.lang-bash
|
|||
|
||||
The description should be sufficient to identify the tested application part and its source file. Almost any convention will do as long as you and your team follow it consistently and are never confused.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Run the tests
|
||||
|
||||
Open one terminal window and run the watching compiler command: `npm run tsc`
|
||||
|
@ -139,6 +147,8 @@ pre.prettyprint.lang-bash
|
|||
figure.image-display
|
||||
img(src='/resources/images/devguide/first-app-tests/passed-2-specs-0-failures.png' style="width:400px;" alt="Two passing tests")
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Critique
|
||||
|
||||
|
@ -150,6 +160,8 @@ figure.image-display
|
|||
|
||||
We need to relocate these tests to a separate file. Let’s do that next.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Where do tests go?
|
||||
|
||||
Some people like to keep their tests in a `tests` folder parallel to the application source folder.
|
||||
|
@ -165,6 +177,8 @@ figure.image-display
|
|||
|
||||
You may put your tests elsewhere if you wish. We’re putting ours inside the app, next to the source files that they test.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## First spec file
|
||||
|
||||
**Create** a new file, ** `hero.spec.ts` ** in `src/app` next to `hero.ts`.
|
||||
|
@ -209,7 +223,7 @@ figure.image-display
|
|||
<script src="app/hero.js"></script>
|
||||
<script src="app/hero.spec.js"></script>
|
||||
|
||||
## Run and Fail
|
||||
### Run and Fail
|
||||
|
||||
Look over at the browser (live-server will have reloaded it). The browser displays
|
||||
|
||||
|
@ -220,10 +234,12 @@ figure.image-display
|
|||
That’s Jasmine saying “**things are _so_ bad that _I’m not running any tests_.**”
|
||||
|
||||
Open the browser’s Developer Tools (F12, Ctrl-Shift-i). There’s an error:
|
||||
```
|
||||
|
||||
code-example(format="" language="html").
|
||||
Uncaught ReferenceError: exports is not defined
|
||||
```
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Load tests with systemjs
|
||||
|
||||
The immediate cause of the error is the `export` statement in `hero.ts`.
|
||||
|
@ -278,6 +294,8 @@ figure.image-display
|
|||
figure.image-display
|
||||
img(src='/resources/images/devguide/first-app-tests/test-passed-once-again.png' style="width:400px;" alt="Tests passed once again")
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Observations
|
||||
|
||||
|
@ -307,6 +325,8 @@ figure.image-display
|
|||
So we must wait until the import completes and only then call the window `onLoad` handler.
|
||||
Jasmine re-starts, this time with our imported test queued up.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## What’s Next?
|
||||
We are able to test a part of our application with simple Jasmine tests.
|
||||
The part was a stand-alone class that made no mention or use of Angular.
|
||||
|
|
|
@ -36,7 +36,6 @@ pre.prettyprint.lang-bash
|
|||
:markdown
|
||||
The browser is nice during development of a few tests. It’s not the best venue for working with a lot of tests and it won’t do at all for build automation. We’ll switch to the karma test-runner when the time comes. But the browser will do for now.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
Create a new file called`unit-tests.html` and enter the following:
|
||||
```
|
||||
|
@ -73,7 +72,9 @@ figure.image-display
|
|||
|
||||
:markdown
|
||||
It doesn’t get much simpler than that!
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## First TypeScript Test
|
||||
Perhaps too simple. We won’t write our entire test suite inside one HTML file.
|
||||
Let’s **extract** that line of test code to a **new file in `src` called `1st.spec.ts` ** .
|
||||
|
@ -82,7 +83,6 @@ figure.image-display
|
|||
:markdown
|
||||
Among Jasmine developers, a test is known as a “spec” and test filenames include the word “spec”. We’ll stick with that convention.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
The test we wrote is valid TypeScript because any JavaScript is valid TypeScript. But let’s make it more modern with an arrow function:
|
||||
```
|
||||
|
@ -96,6 +96,8 @@ figure.image-display
|
|||
|
||||
That’s a reminder that we need to compile our TypeScript test files as we do our TypeScript application files. Do that next.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Prepare for TypeScript
|
||||
|
||||
As we’ve seen before, we first have to tell the compiler how to compile our TypeScript files with
|
||||
|
@ -143,6 +145,8 @@ pre.prettyprint.lang-bash
|
|||
|
||||
We should get the same Jasmine test-runner output as before.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Add a describe and another test
|
||||
|
||||
We can’t tell what file produced these test results. We only have one file at the moment but soon we’ll write more.
|
||||
|
@ -189,6 +193,8 @@ figure.image-display
|
|||
:markdown
|
||||
We can re-run just the failing test by double-clicking it. Try it!
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Debug the test
|
||||
Suppose we didn’t know what was going on. We can debug it in the browser.
|
||||
|
||||
|
@ -214,10 +220,14 @@ figure.image-display
|
|||
|
||||
Congratulations … you’ve completed Jasmine testing 101.
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Learn more
|
||||
Learn more about basic Jasmine testing here
|
||||
[Resources TBD](./#)
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## What’s Next?
|
||||
Now that we’re familiar with Jasmine on its own, we’re ready to test an application.
|
||||
|
||||
|
|
|
@ -9,10 +9,8 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
We use it our `hero-detail.component.html` template to turn a hero name like “eeny weenie” into “Eeny Weenie”
|
||||
|
||||
The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief:
|
||||
|
||||
code-example(format="linenums").
|
||||
<h2>{{hero.name | initCaps}} is {{userName}}'s current super hero!</h2>
|
||||
code-example(format="linenums" language="html" escape="html").
|
||||
<h2>{{hero.name | initCaps}} is {{userName}}'s current super hero!</h2>
|
||||
|
||||
:markdown
|
||||
The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief:
|
||||
|
@ -61,9 +59,10 @@ figure.image-display
|
|||
in the console window, we would see that SystemJS
|
||||
tried to load Angular and couldn't find it.
|
||||
|
||||
```
|
||||
code-example(format="" language="html" escape="html").
|
||||
GET http://127.0.0.1:8080/src/angular2/angular2 404 (Not Found)
|
||||
```
|
||||
|
||||
:markdown
|
||||
We are writing an Angular application afterall and
|
||||
we were going to need Angular sooner or later. That time has come.
|
||||
The `InitCapsPiep` clearly depends on Angular as is clear in the first few lines:
|
||||
|
|
|
@ -24,7 +24,9 @@ include ../../../../_includes/_util-fns
|
|||
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.
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## The End Game
|
||||
|
||||
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||
|
@ -59,7 +61,9 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
We can click a hero and have the router race us over to a "Hero Details" view
|
||||
again.
|
||||
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## How We Roll
|
||||
|
||||
We’ll build this Tour of Heroes together, step by step.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
include ../../../../_includes/_util-fns
|
||||
|
||||
.l-main-section
|
||||
|
||||
:markdown
|
||||
# Once Upon a Time
|
||||
|
||||
|
@ -24,7 +23,7 @@ include ../../../../_includes/_util-fns
|
|||
└── package.json
|
||||
|
||||
:markdown
|
||||
## Keep the App Running
|
||||
### Keep the app running
|
||||
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
||||
|
||||
pre.prettyprint.lang-bash
|
||||
|
@ -40,7 +39,7 @@ include ../../../../_includes/_util-fns
|
|||
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
|
||||
.l-sub-section
|
||||
:markdown
|
||||
These two steps watch all project files. They recompile TypeScript files and re-run
|
||||
the app when any file changes.
|
||||
|
@ -48,9 +47,8 @@ include ../../../../_includes/_util-fns
|
|||
stop these commands in each terminal by typing `CTRL+C` and then re-run them.
|
||||
|
||||
.l-main-section
|
||||
|
||||
:markdown
|
||||
# Show our Hero
|
||||
## 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
|
||||
|
@ -66,7 +64,7 @@ include ../../../../_includes/_util-fns
|
|||
:markdown
|
||||
Now we update the template in the `@Component` decoration with data bindings to these new properties.
|
||||
|
||||
code-example(format="linenums").
|
||||
code-example(format="").
|
||||
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
|
||||
:markdown
|
||||
The browser should refresh and display our title and hero.
|
||||
|
@ -75,7 +73,7 @@ include ../../../../_includes/_util-fns
|
|||
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
|
||||
### 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.
|
||||
|
@ -103,12 +101,12 @@ include ../../../../_includes/_util-fns
|
|||
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").
|
||||
code-example(format="").
|
||||
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
|
||||
:markdown
|
||||
The browser refreshes and continues to display our hero’s name.
|
||||
|
||||
## **Adding more HTML**
|
||||
### 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`.
|
||||
|
||||
|
@ -117,7 +115,7 @@ include ../../../../_includes/_util-fns
|
|||
: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
|
||||
### 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
|
||||
|
@ -147,9 +145,8 @@ include ../../../../_includes/_util-fns
|
|||
is part of a single template string.
|
||||
|
||||
.l-main-section
|
||||
|
||||
:markdown
|
||||
# Editing Our Hero
|
||||
## Editing Our Hero
|
||||
|
||||
We want to be able to edit the hero name in a textbox.
|
||||
|
||||
|
@ -161,7 +158,7 @@ include ../../../../_includes/_util-fns
|
|||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<div><input value="{{hero.name}}" placeholder="name"></input></div>
|
||||
<div><input value="{{hero.name}}" placeholder="name"></div>
|
||||
</div>
|
||||
`
|
||||
:markdown
|
||||
|
@ -171,7 +168,7 @@ include ../../../../_includes/_util-fns
|
|||
is not reflected in the `<h2>`. We won't get the desired behavior
|
||||
with a one-way binding to `<input>`.
|
||||
|
||||
## Two-Way Binding
|
||||
### 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.
|
||||
|
@ -179,22 +176,22 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
Let’s update the template to use the **`ng-model`** built-in directive for two-way binding.
|
||||
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax.html#ng-model)
|
||||
:markdown
|
||||
Replace the `<input>` with the following HTML
|
||||
Replace the `<input>` with the following HTML
|
||||
|
||||
```
|
||||
<input [(ng-model)]="hero.name" placeholder="name"></input>
|
||||
```
|
||||
code-example(language="html").
|
||||
<input [(ng-model)]="hero.name" placeholder="name">
|
||||
|
||||
:markdown
|
||||
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
|
||||
## 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.
|
||||
|
@ -218,10 +215,10 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
Unfortunately when we view the app in the browser we still have an error:
|
||||
|
||||
```
|
||||
*EXCEPTION: No value accessor for ' ' in [null]*
|
||||
```
|
||||
|
||||
code-example(language="html").
|
||||
EXCEPTION: No value accessor for ' ' in [null]
|
||||
|
||||
:markdown
|
||||
Apparently declaring the `NgModel` is not quite enough.
|
||||
|
||||
## Declare Multiple Form Directives
|
||||
|
@ -253,7 +250,7 @@ include ../../../../_includes/_util-fns
|
|||
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
|
||||
### 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.
|
||||
|
||||
|
@ -261,7 +258,9 @@ include ../../../../_includes/_util-fns
|
|||
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
|
||||
.l-main-section
|
||||
:markdown
|
||||
## 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)
|
||||
|
@ -290,7 +289,7 @@ include ../../../../_includes/_util-fns
|
|||
<div><label>id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<div><input value="{{hero.name}}" placeholder="name"></input></div>
|
||||
<div><input [(ng-model)]="hero.name" placeholder="name"></div>
|
||||
</div>
|
||||
`,
|
||||
directives: [FORM_DIRECTIVES]
|
||||
|
@ -305,8 +304,9 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
bootstrap(AppComponent);
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
# The Road Ahead
|
||||
## 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
|
||||
|
|
|
@ -13,7 +13,7 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
.l-main-section
|
||||
:markdown
|
||||
# Where We Left Off
|
||||
## 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.
|
||||
|
@ -28,7 +28,7 @@ include ../../../../_includes/_util-fns
|
|||
| └── tsconfig.json
|
||||
└── package.json
|
||||
:markdown
|
||||
## Keep the App Running
|
||||
### Keep the app running
|
||||
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
||||
|
||||
pre.prettyprint.lang-bash
|
||||
|
@ -42,9 +42,11 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
:markdown
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
# Displaying Our Heroes
|
||||
## Creating Heroes
|
||||
|
||||
.l-main-section
|
||||
:markdown
|
||||
## Displaying Our Heroes
|
||||
### Creating heroes
|
||||
Let’s create an array of ten heroes at the bottom of `app.ts`.
|
||||
```
|
||||
var HEROES: Hero[] = [
|
||||
|
@ -65,20 +67,20 @@ include ../../../../_includes/_util-fns
|
|||
We aspire to get this list of heroes from a web service, but let’s take small steps
|
||||
on this road and start by displaying these mock heroes in the browser.
|
||||
|
||||
## Exposing Heroes
|
||||
### Exposing heroes
|
||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
```
|
||||
public heroes = HEROES;
|
||||
```
|
||||
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
We could have defined the heroes list here in this component class.
|
||||
But we know that we’ll get the heroes from a data service.
|
||||
Because know where we are heading, it makes sense to separate the hero data
|
||||
Because we know where we are heading, it makes sense to separate the hero data
|
||||
from the class implementation from the start.
|
||||
:markdown
|
||||
## Displaying Heroes in a Template
|
||||
### 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.
|
||||
```
|
||||
|
@ -91,7 +93,7 @@ include ../../../../_includes/_util-fns
|
|||
```
|
||||
Now we have a template that we can fill with our heroes.
|
||||
|
||||
## Iterating Over Our Heroes with `ng-for`
|
||||
### Listing heroes with ng-for
|
||||
|
||||
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
||||
and display them individually.
|
||||
|
@ -101,47 +103,48 @@ include ../../../../_includes/_util-fns
|
|||
```
|
||||
<li *ng-for="#hero of heroes">
|
||||
```
|
||||
.alert.is-important
|
||||
.alert.is-critical
|
||||
:markdown
|
||||
Do not neglect the leading asterisk (`*`) in front of `ng-for`!
|
||||
The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax.
|
||||
|
||||
Although it is not part of the `ng-for` directive name,
|
||||
it is a critical part of the syntax in this usage of `ng-for`.
|
||||
:markdown
|
||||
The (`*`) prefix to the `ng-for` indicates that the `<li>` element and its children
|
||||
constitute a master template.
|
||||
|
||||
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||
and stamps out instances of this template.
|
||||
|
||||
The quoted text assigned to `ng-for` 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 `#` prefix before "hero" identifies the `hero` as a local template variable.
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
The (`*`) prefix to `ng-for` indicates that the `<li>` element and its children
|
||||
constitute a master template.
|
||||
|
||||
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||
and stamps out instances of this template.
|
||||
|
||||
The quoted text assigned to `ng-for` 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 `#` prefix before "hero" identifies the `hero` as a local template variable.
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
Learn more about `ng-for` and local template variables in the
|
||||
[Template Syntax](../guide/template-syntax.html#ng-for) chapter.
|
||||
|
||||
:markdown
|
||||
With this background in mind, we now insert some content between the `<li>` tags
|
||||
that uses the `hero` template variable to display the hero’s properties.
|
||||
code-example.
|
||||
|
||||
code-example(format="linenums" language="html").
|
||||
<li *ng-for="#hero of heroes">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}</a>
|
||||
</li>
|
||||
|
||||
:markdown
|
||||
## Declaring NgFor
|
||||
### Declaring ng-for
|
||||
When we view the running app in the browser we see nothing … no heroes.
|
||||
We open the developer tools and see an error in the console.
|
||||
```
|
||||
|
||||
code-example(language="html" ).
|
||||
EXCEPTION:
|
||||
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
|
||||
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
|
||||
there are no matching directives with a corresponding property
|
||||
```
|
||||
|
||||
:markdown
|
||||
Thankfully we have a clear error message that indicates where we went wrong.
|
||||
We used `ng-for` in the template but we didn’t tell the component about it.
|
||||
From Angular's perspective, `ng-for` is a meaningless attribute.
|
||||
|
@ -159,14 +162,14 @@ include ../../../../_includes/_util-fns
|
|||
```
|
||||
After the browser refreshes, we see a list of heroes!
|
||||
|
||||
## Styling Our 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:
|
||||
```
|
||||
styles: `
|
||||
styles:[`
|
||||
.heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;}
|
||||
.heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; }
|
||||
.heroes li:hover {color: #369; background-color: #EEE; left: .2em;}
|
||||
|
@ -179,9 +182,9 @@ include ../../../../_includes/_util-fns
|
|||
position: relative;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
`
|
||||
}
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
`],
|
||||
```
|
||||
Notice that we again use the back-tick notation for multi-line strings.
|
||||
|
||||
|
@ -205,7 +208,7 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
.l-main-section
|
||||
:markdown
|
||||
# Selecting a Hero
|
||||
## 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.
|
||||
|
@ -214,7 +217,7 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||
|
||||
## Click Event
|
||||
### Click event
|
||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||
|
||||
code-example(format="linenums").
|
||||
|
@ -230,11 +233,11 @@ include ../../../../_includes/_util-fns
|
|||
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||
passing the local template variable `hero` as an argument.
|
||||
That’s the same `hero` variable we defined previously in the `ng-for`.
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
Learn more about Event Binding in the [Templating Syntax](../guide/template-syntax.html#event-binding) chapter.
|
||||
:markdown
|
||||
## Add the click handler
|
||||
### 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.
|
||||
|
||||
|
@ -242,7 +245,7 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||
|
||||
### Expose the Selected Hero
|
||||
### Expose the selected hero
|
||||
We no longer need the static `hero` property of the `AppComponent`.
|
||||
**Replace** it with this simple `selectedHero` property:
|
||||
```
|
||||
|
@ -269,14 +272,16 @@ include ../../../../_includes/_util-fns
|
|||
</div>
|
||||
|
||||
:markdown
|
||||
### Hide the empty detail with NgIf
|
||||
### Hide the empty detail with ng-if
|
||||
|
||||
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(language="html").
|
||||
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||
```
|
||||
|
||||
:markdown
|
||||
Remember that we are displaying `selectedHero.name` in the template.
|
||||
This name property does not exist because `selectedHero`itself is undefined.
|
||||
|
||||
|
@ -285,7 +290,7 @@ include ../../../../_includes/_util-fns
|
|||
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
|
||||
|
||||
code-example(format="linenums").
|
||||
code-example(format="linenums").
|
||||
<div *ng-if="selectedHero">
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
|
@ -294,18 +299,17 @@ include ../../../../_includes/_util-fns
|
|||
<input [(ng-model)]="selectedHero.name" placeholder="name"></input>
|
||||
</div>
|
||||
</div>
|
||||
.alert.is-important
|
||||
.alert.is-critical
|
||||
:markdown
|
||||
Do not neglect the leading asterisk (`*`) in front of `ng-if`.
|
||||
It's a critical part of the syntax in this usage of `ng-if`.
|
||||
Remember that the leading asterisk (`*`) in front of `ng-if` is
|
||||
a critical part of this syntax.
|
||||
:markdown
|
||||
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
|
||||
There will no hero detail elements and no bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` becomes "truthy" and
|
||||
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
`ng-if` and `ng-for` are called “structural directives” because they can change the
|
||||
structure of portions of the DOM.
|
||||
|
@ -333,7 +337,7 @@ include ../../../../_includes/_util-fns
|
|||
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
|
||||
### 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.
|
||||
|
@ -356,7 +360,7 @@ include ../../../../_includes/_util-fns
|
|||
```
|
||||
What do we do with this method and its peculiar result?
|
||||
|
||||
### NgClass
|
||||
### ng-class
|
||||
We’ll add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
|
||||
It’s no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
|
||||
to add or remove the `selected` class to each hero’s display.
|
||||
|
@ -367,20 +371,16 @@ include ../../../../_includes/_util-fns
|
|||
<span class="badge">{{hero.id}}</span> {{hero.name}}</a>
|
||||
</li>
|
||||
|
||||
.alert.is-helpful
|
||||
:markdown
|
||||
Learn more about `ng-class` in the
|
||||
[Template Syntax](../guide/template-syntax.html#ng-class) chapter
|
||||
|
||||
:markdown
|
||||
Notice in the template that the `ng-class` name 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 `getSelectedClass`) to a property of the `ng-class` directive.
|
||||
|
||||
.alert.is-helpful
|
||||
.l-sub-section
|
||||
:markdown
|
||||
Learn more about about one-way property data binding in the
|
||||
[Template Syntax](../guide/template-syntax.html#property-binding) chapter
|
||||
Learn more about [ng-class](../guide/template-syntax.html#ng-class)
|
||||
and [Property Binding](../guide/template-syntax.html#property-binding)
|
||||
in the Template Syntax chapter
|
||||
:markdown
|
||||
We've added yet another new directive to our template that we have to import and declare
|
||||
in the component’s `directives` array as we’ve done twice before.
|
||||
|
@ -428,14 +428,15 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
.l-main-section
|
||||
:markdown
|
||||
# The Road We’ve Travelled
|
||||
## 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 `ng-if`, `ng-for` and `ng-class` in a component’s template
|
||||
|
||||
## The Road Ahead
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We want to get data from an asynchronous source using promises, use shared services, and create reusable components.
|
||||
We’ll learn more about these tasks in the coming tutorial chapters.
|
||||
We’ll learn more about these tasks in the coming tutorial chapters.
|
||||
|
Loading…
Reference in New Issue