Angular 2: Templates, Interpolation, and Directives

Angular 2: Getting Started

https://app.pluralsight.com/player?course=angular-2-getting-started-update&author=deborah-kurata&name=angular-2-getting-started-update-m4&clip=7&mode=live

1 Templates, Interpolation, and Directives

1.1 Building a template

So far, the way we’ve seen a template built is inline like this:

@Component({
    selector: 'pm-app',
    template: `
    <div><h1>{{pageTitle}}</h1>
        <div>My First Component</div>
    </div>
    `
})

This is one way of building a template: using ES2015’s backticks to allow multiline strings.

Here’s our HTML (Bootstrap, of course) for the heading. Create a folder in app, called products. Save this file as “product-list.component.html”.

<div>
    <div>
        Product List
    </div>
</div>

Now, expanding it with the Filter:

<div>
    <div>
        Product List
    </div>
    <div>
        <div>
            <div>Filter by:</div>
            <div>
                
            </div>
        </div>
        <div>
            <div>
                <h3>Filtered by: </h3>
            </div>
        </div>
        
    </div>
</div>

Now we build out the table headers:

<div>
    <div>
        Product List
    </div>
    <div>
        <div>
            <div>Filter by:</div>
            <div>
                
            </div>
        </div>
        <div>
            <div>
                <h3>Filtered by: </h3>
            </div>
        </div>
        <table>
            <thead>
                <tr>
                    <th>
                        
                            Show Image
                        
                    </th>
                    <th>Product</th>
                    <th>Code</th>
                    <th>Available</th>
                    <th>Price</th>
                    <th>5 Star Rating</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
</div>

We leave the table body empty for now because we don’t want to hard-code in the products. The author of the tutorial sounds like every TV robot ever.

1.2 Building the Component

“Remember the steps for building a component that we covered in the last module?”

NO I DON”T BECAUSE I’M NOT DIGESTING ANYTHING YOU SAY, ROBOT.

First we have to import Component from @angular/core

Then we make a decorator using @Component, where we pass in an object that includes the name of the directive and the templateUrl that points to the html file we just created.

Then we make the component available to other components in the app by exporting it.

Now it’s time to build the component for realz. Create a new file in the same products directory, called product-list.component.ts …and if you haven’t been paying attention so far, the .ts is for TypeScript, which will be converted into JS magically. Actually, the moment you hit save in it, two new files should pop up: product-list.component.js.map and product-list.component.js

We begin with:

export class ProductListComponent {
    
}

Next, we’ll decorate the class with a component decorator. See what we’re doing here? We’re writing this backwards just like we learned about it.

@Component({})
export class ProductListComponent {

}

And since we have this decorator, we have to import the decorator. Do I have doctor’s appointment today? I feel like I’m forgetting something.

import { Component } from '@angular/core';
@Component({})
export class ProductListComponent {

}

Now it’s time to add some properties to the decorator:

import { Component } from '@angular/core';
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html'
})
export class ProductListComponent {

}

The pm in pm-products refers to “product management” or something like that. It’s not jargon. Also, the templateUrl is relative to the root.

Done.

1.3 Using a Component as a Directive

By typing

<pm-products></pm-products>

in any other template, it’ll insert the product list component. How?

First, Angular looks for the component whose selector is pm-products. How does it know where to look for the selector within the hundreds of components?

It looks to the Angular module that owns the component. Because remember, each component resides within a module. So in order for Angular to find it, it’ll need to find the component within the current module, which means we need to declare it in that module. We can either declare the component if it resides in this module, or if it resides in another module, we can import it.

So maybe we’re about to do this, or maybe we’re about to do something else. In any case, go to app.components.ts, and look at this section:

@Component({
    selector: 'pm-app',
    template: `
    <div><h1>{{pageTitle}}</h1>
        <div>My First Component</div>
    </div>
    `
})

We’re going to replace the

<div>My First Component</div>

line with a pm-products tag. I'd write it out here, but WordPress keeps destroying it when I write it and replacing it with a blank. Go figure.

So…

@Component({
    selector: 'pm-app',
    template: `
    <div><h1>{{pageTitle}}</h1>
        THIS IS WHERE THE TAG WOULD GO, WHICH IT KEEPS DELETING.    
    </div>
    `
})

In case you were wondering how things are looking, my browser is stuck on “Loading App…” – but good news! The tutorial does the same thing, and now we have to figure out why.

Unhandled Promise rejection: Template parse errors:
'pm-products' is not a known element:
1. If 'pm-products' is an Angular component, then verify that it is part of this module.
2. If 'pm-products' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schema' of this component to suppress this message. ("
    <div><h1>{{pageTitle}}</h1>
        [ERROR ->]
    </div>
    "

Surely this is because we didn’t declare it in the module like we just learned about? Don’t call me Shirley! We need to go back to the app.module and add it there, just like the error message suggests.

This is what’s currently in the file:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

If we add the component to the declarations, like this:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent, ProductListComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

we still have a problem: we haven’t imported the component. You can see that the other component (AppComponent) is imported above. By the way, how come it’s called ProductListComponent here? Is that just some renaming magic? No. If you look back at product-list.component.ts, you’ll see that’s what we call it when we export it.

export class ProductListComponent {

}

Anyway, let’s import this now.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';
import { ProductListComponent } from './products/product-list.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent, ProductListComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

And now the page loads up correctly.

1.4 Binding With Interpolation

Binding coordinates communication between the component’s class and its template and often involves passing data.

Interpolation is this sort of thing: <h1>{{pageTitle}}</h1>. The page title would be bound to a property in the component’s class, like so:

export class AppComponent {
    pageTitle: string = 'Acme Product Management';
}

It’s one-way, that is, read-only. So the property shows up in the HTML, and that’s all. You can do what you’d expect, like 1 + 1 or someFunction() whether that’s as a text node or like this <div class="{{something}}">.

Let’s open product-list.component.html and you’ll find that we hard-coded the page title in there.

Now, let’s look at product-list.component.ts and you’ll find this:

export class ProductListComponent {

}

which we can rewrite as:

export class ProductListComponent {
    pageTitle: string = 'Product List';
}

And back in the HTML, this

<div>
        Product List
    </div>

becomes

    <div>
        {{pageTitle}}
    </div>

It works! Which you can test by switching Product List to a dirty word.

1.5 Adding Logic With Directives

Directive – custom HTML element or attribute used to power up and extend our HTML.

There are built-in directives. Just like we learned when we were doing AngularJS. But they look different. If they’re structural directives, they begin with a *. So we’re going to jump right into *ngIf.

Using ngIf, if the statement evaluates to true, the HTML below it (or a copy of it) is inserted into the DOM. If false, it is removed or not inserted. For example:

<table class="table" *ngIf='products && products.length'>

Where does the *ngIf directive come from? When we imported the BrowserModule, it exposed that directive. Hooray.

Back in our demo, we want to only display our products table if there are products to display. So the first thing we need is a property to hold the products. We’d define this in the component’s class ( product-list.component.ts ).

We know we want to call it products. Now we have to decide its type…which I guess would be an array, but we don’t know what the array will contain. That is, what the type of things inside the array would be. So instead, we use any[].

export class ProductListComponent {
    pageTitle: string = 'Product List';
    products: any[];
}

And then we’re just copying some of pre-written products into a hard-coded array:

export class ProductListComponent {
    pageTitle: string = 'Product List';
    products: any[] = [
    {
        "productId": 1,
        "productName": "Leaf Rake",
        "productCode": "GDN-0011",
        "releaseDate": "March 19, 2016",
        "description": "Leaf rake with 48-inch wooden handle.",
        "price": 19.95,
        "starRating": 3.2,
        "imageUrl": "http://openclipart.org/image/300px/svg_to_png/26215/Anonymous_Leaf_Rake.png"
    },
    {
        "productId": 2,
        "productName": "Garden Cart",
        "productCode": "GDN-0023",
        "releaseDate": "March 18, 2016",
        "description": "15 gallon capacity rolling garden cart",
        "price": 32.99,
        "starRating": 4.2,
        "imageUrl": "http://openclipart.org/image/300px/svg_to_png/58471/garden_cart.png"
    },
    {
        "productId": 5,
        "productName": "Hammer",
        "productCode": "TBX-0048",
        "releaseDate": "May 21, 2016",
        "description": "Curved claw steel hammer",
        "price": 8.9,
        "starRating": 4.8,
        "imageUrl": "http://openclipart.org/image/300px/svg_to_png/73/rejon_Hammer.png"
    },
    {
        "productId": 8,
        "productName": "Saw",
        "productCode": "TBX-0022",
        "releaseDate": "May 15, 2016",
        "description": "15-inch steel blade hand saw",
        "price": 11.55,
        "starRating": 3.7,
        "imageUrl": "http://openclipart.org/image/300px/svg_to_png/27070/egore911_saw.png"
    },
    {
        "productId": 10,
        "productName": "Video Game Controller",
        "productCode": "GMG-0042",
        "releaseDate": "October 15, 2015",
        "description": "Standard two-button video game controller",
        "price": 35.95,
        "starRating": 4.6,
        "imageUrl": "http://openclipart.org/image/300px/svg_to_png/120337/xbox-controller_01.png"
    }
];
}

So now, with the *ngif directive in place, we’d expect it to work, right? It doesn’t show the products, but we can tell that it is or isn’t working based on whether or not the array of items are commented out. The headers appear/disappear.

1.6 *NGFOR

*ngFor is our iterator. Here’s an example:

<tr *ngFor='let product of products'>
    <td></td>
    <td>{{ product.productName }}</td>
    <td>{{ product.productCode }}</td>
    <td>{{ product.releaseDate }}</td>
    <td>{{ product.price }}</td>
    <td>{{ product.starRating }}</td>
</tr>

Things worth noting: the let and of keywords. Why of instead of in? Because in ES6 a for…in loop iterates over an object’s index numbers. So 0, 1, 2. Since *ngFor is meant to iterate over properties, the developers chose of instead of in.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s