263 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| include ../_util-fns
 | |
| 
 | |
| <!-- http://plnkr.co/edit/x9JYbC -->
 | |
| 
 | |
| :marked
 | |
|   We typically display data in Angular by binding controls in an HTML template
 | |
|   to properties of an Angular component.
 | |
| 
 | |
|   In this chapter, we'll create a component with a list of heroes. Each hero has a name.
 | |
|   We'll display the list of hero names and
 | |
|   conditionally show a selected hero in a detail area below the list.
 | |
| 
 | |
|   The final UI looks like this:
 | |
| 
 | |
| figure.image-display
 | |
|   img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI")
 | |
| 
 | |
| :marked
 | |
|   [Run the live example](/resources/live-examples/displaying-data/ts/plnkr.html)
 | |
| 
 | |
| <a id="interpolation"></a>
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Showing component properties with interpolation
 | |
|   The easiest way to display a component property
 | |
|   is to bind the property name through interpolation.
 | |
|   With interpolation, we put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
 | |
| 
 | |
|   Let's build a small illustrative example together.
 | |
| 
 | |
|   Create a new project folder (`displaying-data`) and follow the steps in the [QuickStart](../quickstart.html).
 | |
| 
 | |
| include ../_quickstart_repo
 | |
| :marked
 | |
|   Then modify the `app.component.ts` file by changing the template and the body of the component.
 | |
|   When we're done, it should look like this:
 | |
| 
 | |
| +makeExample('displaying-data/ts/app/app.component.1.ts', null, 'app/app.component.ts')
 | |
| 
 | |
| :marked
 | |
|   We added two properties to the formerly empty component: `title` and `myHero`.
 | |
| 
 | |
|   Our revised template displays the two component properties using double curly brace
 | |
|   interpolation:
 | |
| 
 | |
| +makeExample('displaying-data/ts/app/app.component.1.ts', 'template')(format=".")
 | |
| .l-sub-section
 | |
|   :marked
 | |
|     The template is a multi-line string within ECMAScript 2015 backticks (\`).
 | |
|     The backtick (\`) — which is *not* the same character as a single
 | |
|     quote (') — has many nice features. The feature we're exploiting here
 | |
|     is the ability to compose the string over several lines, which makes for
 | |
|     much more readable HTML.
 | |
| 
 | |
| :marked
 | |
|   Angular automatically pulls the value of the `title` and `myHero` properties from the component and
 | |
|   inserts those values into the browser. Angular updates the display
 | |
|   when these properties change.
 | |
| .l-sub-section
 | |
|   :marked
 | |
|     More precisely, the redisplay occurs after some kind of asynchronous event related to
 | |
|     the view such as a keystroke, a timer completion, or an async `XHR` response.
 | |
|     We don't have those in this sample.
 | |
|     But then the properties aren't changing on their own either. For the moment we must operate on faith.
 | |
| :marked
 | |
|   Notice that we haven't called **new** to create an instance of the `AppComponent` class.
 | |
|   Angular is creating an instance for us. How?
 | |
| 
 | |
|   Notice the CSS `selector` in the `@Component` decorator that specifies an element named "my-app".
 | |
|   Remember back in QuickStart that we added the `<my-app>` element to the body of our `index.html`
 | |
| +makeExample('displaying-data/ts/index.html', 'my-app')(format=".")
 | |
| 
 | |
| :marked
 | |
|   When we bootstrap with the `AppComponent` class (see `main.ts`), Angular looks for a `<my-app>`
 | |
|   in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
 | |
|   inside the `<my-app>` tag.
 | |
| 
 | |
|   We're ready to see changes in a running app by firing up the npm script that both compiles and serves our applications
 | |
|   while watching for changes.
 | |
| code-example(format="").
 | |
|   npm start
 | |
| :marked
 | |
|   We should see the title and hero name:
 | |
| figure.image-display
 | |
|   img(src="/resources/images/devguide/displaying-data/title-and-hero.png" alt="Title and Hero")
 | |
| :marked
 | |
|   Let's review some of the choices we made and consider alternatives.
 | |
| 
 | |
|   ## Template inline or template file?
 | |
| 
 | |
|   We can store our component's template in one of two places.
 | |
|   We can define it *inline* using the `template` property, as we do here.
 | |
|   Or we can define the template in a separate HTML file and link to it in
 | |
|   the component metadata using the `@Component` decorator's `templateUrl` property.
 | |
| 
 | |
|   The choice between inline and separate HTML is a matter of taste,
 | |
|   circumstances, and organization policy.
 | |
|   Here we're using inline HTML because the template is small, and the demo
 | |
|   is simpler without the HTML file.
 | |
| 
 | |
|   In either style, the template data bindings have the same access to the component's properties.
 | |
| 
 | |
|   ## Constructor or variable initialization?
 | |
| 
 | |
|   We initialized our component properties using variable assignment.
 | |
|   This is a wonderfully concise and compact technique.
 | |
| 
 | |
|   Some folks prefer to declare the properties and initialize them within a constructor like this:
 | |
| +makeExample('displaying-data/ts/app/app-ctor.component.ts', 'app-ctor')(format=".")
 | |
| 
 | |
| :marked
 | |
|   That's fine too. The choice is a matter of taste and organization policy.
 | |
|   We'll adopt the more terse "variable assignment" style in this chapter simply because
 | |
|   there will be less code to read.
 | |
| 
 | |
| <a id="ngFor"></a>
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Showing an array property with NgFor
 | |
| 
 | |
|   We want to display a list of heroes. We begin by adding a mock heroes name array to the component,
 | |
|   just above `myHero`, and redefine `myHero` to be the first name in the array.
 | |
| +makeExample('displaying-data/ts/app/app.component.2.ts', 'mock-heroes', 'app/app.component.ts (class)')(format=".")
 | |
| 
 | |
| :marked
 | |
|   Now we use the Angular `NgFor` "repeater" directive in the template to display
 | |
|   each item in the `heroes` list.
 | |
| 
 | |
| +makeExample('displaying-data/ts/app/app.component.2.ts', 'template','app/app.component.ts (template)')(format=".")
 | |
| 
 | |
| :marked
 | |
|   Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag.
 | |
| +makeExample('displaying-data/ts/app/app.component.2.ts', 'li-repeater')(format=".")
 | |
| 
 | |
| :marked
 | |
|   We added a somewhat mysterious `*ngFor` to the `<li>` element.
 | |
|   That's the Angular "repeater" directive.
 | |
|   Its presence on the `<li>` tag marks that `<li>` element (and its children) as the "repeater template".
 | |
| 
 | |
| .alert.is-important
 | |
|   :marked
 | |
|     Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
 | |
|     Learn more about this and `NgFor` in the [Template Syntax](./template-syntax.html#ngFor) chapter.
 | |
| 
 | |
| :marked
 | |
|   Notice the `#hero` in the `NgFor` double-quoted instruction.
 | |
|   The `#hero` is a [local template variable](./template-syntax.html#local-vars) declaration.
 | |
|   The `#` prefix declares a local variable name named `hero`.
 | |
| 
 | |
|   Angular duplicates the `<li>` for each item in the list, setting the `hero` variable
 | |
|   to the item (the hero) in the current iteration. Angular uses that variable as the
 | |
|   context for the interpolation in the double curly braces.
 | |
| 
 | |
| .l-sub-section
 | |
|   :marked
 | |
|     We happened to give `NgFor` an array to display.
 | |
|     In fact, `NgFor` can repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
 | |
|     object.
 | |
| :marked
 | |
|   Assuming we're still running under the `npm start` command,
 | |
|   we should see heroes appearing in an unordered list.
 | |
| 
 | |
| figure.image-display
 | |
|   img(src="/resources/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor")
 | |
| 
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Creating a class for the data
 | |
| 
 | |
|   We are defining our data directly inside our component.
 | |
|   That's fine for a demo but certainly isn't a best practice. It's not even a good practice.
 | |
|   Although we won't do anything about that in this chapter, we'll make a mental note to fix this down the road.
 | |
| 
 | |
|   At the moment, we're binding to an array of strings. We do that occasionally in real applications, but
 | |
|   most of the time we're displaying objects — potentially instances of classes.
 | |
| 
 | |
|   Let's turn our array of hero names into an array of `Hero` objects. For that we'll need a `Hero` class.
 | |
| 
 | |
|   Create a new file in the `app/` folder called `hero.ts` with the following short bit of code.
 | |
| +makeExample('displaying-data/ts/app/hero.ts', null, 'app/hero.ts')(format = ".")
 | |
| 
 | |
| :marked
 | |
|   We've defined a class with a constructor and two properties: `id` and `name`.
 | |
| 
 | |
|   It might not look like we have properties, but we do. We're taking
 | |
|   advantage of a TypeScript shortcut in our declaration of the constructor parameters.
 | |
| 
 | |
|   Consider the first parameter:
 | |
| +makeExample('displaying-data/ts/app/hero.ts', 'id-parameter')
 | |
| 
 | |
| :marked
 | |
|   That brief syntax does a lot:
 | |
|   * declares a constructor parameter and its type
 | |
|   * declares a public property of the same name
 | |
|   * initializes that property with the corresponding argument when we "new" an instance of the class
 | |
| 
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Using the Hero class
 | |
|   Let's redefine the `heroes` property in our component to return an array of these Hero objects
 | |
|   and also set the `myHero` property with the first of these mock heroes.
 | |
| +makeExample('displaying-data/ts/app/app.component.3.ts', 'heroes', 'app.component.ts (excerpt)')(format=".")
 | |
| 
 | |
| :marked
 | |
|   We'll have to update the template.
 | |
|   At the moment it displays the entire `hero` object, which used to be a string value.
 | |
|   Let's fix that so we interpolate the `hero.name` property.
 | |
| +makeExample('displaying-data/ts/app/app.component.3.ts', 'template','app.component.ts (template)')(format=".")
 | |
| 
 | |
| :marked
 | |
|   Our display looks the same, but now we know much better what a hero really is.
 | |
| 
 | |
| <a id="ngIf"></a>
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Conditional display with NgIf
 | |
| 
 | |
|   Sometimes the app should display a view or a portion of a view only under specific circumstances.
 | |
| 
 | |
|   In our example, we'd like to display a message if we have a large number of heroes — say, more than 3.
 | |
| 
 | |
|   The Angular `NgIf` directive inserts or removes an element based on a truthy/falsey condition.
 | |
|   We can see it in action by adding the following paragraph at the bottom of the template:
 | |
| +makeExample('displaying-data/ts/app/app.component.ts', 'message')
 | |
| .alert.is-important
 | |
|   :marked
 | |
|     Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
 | |
|     Learn more about this and `NgIf` in the [Template Syntax](./template-syntax.html#ngIf) chapter.
 | |
| 
 | |
| :marked
 | |
|   The [template expression](./template-syntax.html#template-expressions) inside the double quotes
 | |
|   looks much like JavaScript and it _is_ much like JavaScript.
 | |
|   When the component's list of heroes has more than 3 items, Angular adds the paragraph to the DOM and the message appears.
 | |
|   If there are 3 or fewer items, Angular omits the paragraph, so no message appears.
 | |
| 
 | |
| .alert.is-helpful
 | |
|   :marked
 | |
|     Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM.
 | |
|     That hardly matters here. It would matter a great deal from a performance perspective if
 | |
|     we were conditionally including or excluding a big chunk of HTML with many data bindings.
 | |
| 
 | |
| :marked
 | |
|   Try it out. Because the array has four items, the message should appear.
 | |
|   Go back into `app.component.ts` and delete or comment out one of the elements from the hero array.
 | |
|   The browser should refresh automatically and the message should disappear.
 | |
| 
 | |
| .l-main-section
 | |
| :marked
 | |
|   ## Summary
 | |
|   Now we know how to use:
 | |
|   - **interpolation** with double curly braces to display a component property
 | |
|   - **`NgFor`** to display a list of items
 | |
|   - a TypeScript class to shape the **model data** for our component and display properties of that model
 | |
|   - **`NgIf`** to conditionally display a chunk of HTML based on a boolean expression
 | |
| 
 | |
|   Here's our final code:
 | |
| 
 | |
| +makeTabs(`displaying-data/ts/app/app.component.ts,
 | |
|            displaying-data/ts/app/hero.ts,
 | |
|            displaying-data/ts/app/main.ts`,
 | |
|            'final,,',
 | |
|            'app/app.component.ts, app/hero.ts, main.ts')
 |