An Angular2 example « »
April 30, 2016
- Component
- Application Arhictecture
- Concepts
- Javascript Language Specification
- Typescript
- IDEs for Typescript
- Setting up Our Environment
- Modules
- Components
- Angular is Modular
- Bootstrapping
- Templates
- Include Component
- Binding
- Directive
- Property Binding vs Interpolation
- Event Binding
- Pipes
- Interface
- Encapsulating component Styles
- Component Lifecycle / Using a Lifecycle Hook
- OnInit
- OnChanges
- OnDestroy
- Nested Components
- Services and Dependency Injection
- Retrieving Data Using Http
- Navigation and Routing
- Angular2 Setup Revisited
- More
Component
Component = Template + Class + Metadata
Application Arhictecture
- App Component
- Product Data Service
- Welcome Component
- Product List Component
- Star Component
- Product Detail Component
- Star Component
Concepts
- Components
- Nested Components
- Templates, Interpolation, and Directives
- Data Binding & Pipes
- Services and Dependency Injection
- Retrieving Data Using Http
- Navigation and Routing
Javascript Language Specification
- ECMAScript (ES)
- ES 3
- ES 5
- ES 2015 (formerly known as ES6)
- Must be transpiled
Typescript
- Open soruce
- Superset of Javascript
- Transpiles to Javascript
- Strongly typed
- Typescript type definition files (*.d.ts)
- Class-based object-orientation
IDEs for Typescript
- Visual Studio
- Visual Studio Code
- WebStorm
- Atom
- Eclipse
- …
Setting up Our Environment
It is a bit laborious.
- Create the tsconfig.json file
- Create the package.json file
- Create the typings.json file
- Install the libraries and typings
- Create the host Web page (index.html)
- Create the main.ts file (bootstrapper)
Modules
- Angular1 Modules
- Typescript Modules
- ES 2015 Modules
- Angular2 Uses ES 2015 Modules
ES 2015 Modules
Export
// product.ts
export class Product {
}
Import
// product-list.ts
import {Product} from './product'
Components
Component = Template + Class + Metadata
Once we give the @Component metadata we get a Component.
Decorator: A function that adds metadata to a class, its members, or its method arguments.
import {Component} from 'angular2/core';
@Component({
selector: 'pm-app',
template: `
<div><h1></h1>
<div>My First Component</div>
</div>
`
})
export class AppComponent {
pageTitle: string = 'Product Management';
}
// main.ts
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);
<!-- indexx.html -->
<!-- System.import('app/main'); -->
<body>
<pm-app>Loading App ...</pm-app>
</body>
Angular is Modular
- core
- animate
- http
- router
Bootstrapping
// main.ts
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);
Templates
- Inline Template
- Linked Template
template:
"<h1></h1>"
// ES 2015 back tick
template: `
<div>
<h1></h1>
<div>
My First Component
</div>
</div>
templateUrl: 'product-list.component.html'
Include Component
import {Component} from 'angular2/core';
// 1
import {ProductListComponent} from './products/product-list.component'
@Component({
selector: 'pm-app',
template: `
<div><h1></h1>
<pm-products></pm-products>
</div>
`,
// 2
directives: [ProductListComponent]
})
export class AppComponent {
pageTitle: string = 'Product Management';
}
Binding
Coordinates communication between the component’s class and its template and often involves passing data.
Directive
Custom HTML element or attribute used to power up and extend our HTML.
- Custom
- Built-in (*ngIf, *ngFor)
<table *ngIf='products.length'>
<thead></thead>
<tbody></tbody>
</table>
// the hash # makes the product variable a local variable of the loop
<tr *ngFor='#product of products'>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
for…of
- Iterates over iterable objects, such as an array.
for…in
- Iterates over the properties of an object.
Property Binding vs Interpolation
<img [src]='product.imageUrl'>
<!-- Interpolation -->
<img src="" >
<img src="http://openclipart.org/">
// ...
export class ProductListComponent {
pageTitle: string = 'Product List';
imageWidth: number = 50;
imageMargin: number = 2;
// ...
<td><img [src]="product.imageUrl" [title]="product.productName"
[style.width.px]='imageWidth' [style.margin.px]='imageMargin'></td>
Event Binding
Template
<button (click)='toggleImage()' class="btn btn-primary"></button>
<!-- ... -->
<!-- check the ngIf -->
<td><img *ngIf='showImage' [src]="product.imageUrl" [title]="product.productName"
[style.width.px]='imageWidth' [style.margin.px]='imageMargin'></td>
<!-- ... -->
Component
// ...
export class ProductListComponent {
// ...
showImage: boolean = false;
// ...
toggleImage(): void {
this.showImage = !this.showImage;
}
// ...
Two-way Binding
Banana in a box
[()] <– Means type and event binding at the same time.
export class ProductListComponent {
// ...
listFilter: string = 'cart';
// ...
}
<input type="text" [(ngModel)]='listFilter'>
Pipes
Transform bound properties before display.
- date
- number, decimal, percent, currency, …
- json, slice
- custom
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
Writing a custom Pipe
@Pipe({
name: 'productFilter'
})
export class ProductFilterPipe implements PipeTransform {
transform(value: IProduct[], args: string[]): IProduct[] {}
}
<!-- listFilter is the variable from the class that has two way data binding -->
<tr *ngFor = '#product of products | productFilter:listFilter'>
import {PipeTransform,Pipe} from 'angular2/core';
import {IProduct} from './product';
@Pipe({
name: 'productFilter'
})
export class ProductFilterPipe implements PipeTransform {
transform(value: IProduct[], args: string[]): IProduct[] {
let filter: string = args[0] ? args[0].toLocaleLowerCase() : null;
// Array.filter() method
return filter ? value.filter((product: IProduct) =>
product.productName.toLocaleLowerCase().indexOf(filter) != -1): value;
}
}
Interface
A specification identifying a related set of properties and methods.
A class commits to supporting the specification by implementing the interface.
Use the interface as a data type.
export interface IProduct {
productId: number;
productName: string;
productCode: string;
releaseDate: Date;
price: number;
description: string;
starRating: number;
imageUrl: string;
calculateDiscount(percent: number): number;
}
import { IProduct } from './product';
export class ProductListComponent {
products: IProduct[] = [];
}
If we don’t need any methods we don’t have the need a creating a class and we use an interface instead.
Encapsulating component Styles
@Component({
selector: 'pm-products',
templateUrl: 'app/products/product-list.component.html',
styleUrls: ['app/products/product-list.component.css']
})
Component Lifecycle / Using a Lifecycle Hook
- Create
- Render
- Create and render children
- Process changes
- Destroy
OnInit
Perform component initialization, retrieve data.
OnChanges
Perform action after change to input properties.
OnDestroy
Perform cleanup.
import { OnInit } from 'angular2/core';
export class ProductListComponent implements OnInit {
// ...
ngOnInit(): void {
console.log('In OnInit');
}
// ...
}
Nested Components
Input Properties
- Attached to a property of any type
- Prefix with @; Suffix with ()
@Input() rating: number;
<ai-star [rating]='product.starRating'></ai-star>
Output Event
- Attached to a property declared as an EventEmitter
- Use the generic argument to define the event payload type
- Use the new keyword to create an instance of the EventEmitter
<div class="crop"
[style.width.px]="starWidth"
[title]="rating"
(click)='onClick()'>
import {Component,OnChanges,Input,Output,EventEmitter} from 'angular2/core';
@Component({
selector: 'ai-star',
templateUrl: 'app/shared/star.component.html',
styleUrls: ['app/shared/star.component.css']
})
export class StarComponent implements OnChanges {
@Input() rating: number;
starWidth: number;
@Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
ngOnChanges(): void {
this.starWidth = this.rating * 86 / 5;
}
onClick() {
this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
}
}
<td>
<ai-star [rating]='product.starRating' (ratingClicked)='onRatingClicked($event)'>
</ai-star>
</td>
onRatingClicked(message: string):void {
this.pageTitle = 'Product List: ' + message;
}
Services and Dependency Injection
A Service is a class with a focused purpose.
Used for features that:
- Are independent from any particular component
- Provide shared data or logic across components
- Encapsulate external interactions
Dependency Injection
A coding patterin in which a class receives the instances of objects it needs (called dependencies) from an external source rather than creating them itself.
Building a Service
- Create the service class
- Define the metadata with a decorator
- Import what we need
Register a service
- Register a provider
- Code that can create or return a service, typically te service class itself.
- Define as part of the component metadata
- Injectable to component AND any of its children
import {Component} from 'angular2/core';
import {ProductListComponent} from './products/product-list.component'
import {ProductService} from './products/product.service';
@Component({
selector: 'pm-app',
template: `
<div><h1></h1>
<pm-products></pm-products>
</div>
`,
directives: [ProductListComponent],
providers: [ProductService]
})
export class AppComponent {
pageTitle: string = 'Product Management';
}
import { Injectable } from 'angular2/core';
import { IProduct } from './product';
@Injectable()
export class ProductService {
getProducts(): IProduct[] {
return [
// ...
];
}
}
import {Component,OnInit} from 'angular2/core';
import {IProduct} from './product';
import {ProductFilterPipe} from './product-filter.pipe';
import {StarComponent} from '../shared/star.component';
import {ProductService} from './product.service';
@Component({
selector: 'pm-products',
templateUrl: 'app/products/product-list.component.html',
styleUrls: ['app/products/product-list.component.css'],
pipes: [ProductFilterPipe],
directives: [StarComponent]
})
export class ProductListComponent implements OnInit{
pageTitle: string = 'Product List';
imageWidth: number = 50;
imageMargin: number = 2;
showImage: boolean = false;
listFilter: string = null;
products: IProduct[] = null;
constructor(private _productService: ProductService) {
}
toggleImage(): void {
this.showImage = !this.showImage;
}
ngOnInit(): void {
this.products = this._productService.getProducts();
}
onRatingClicked(message: string):void {
this.pageTitle = 'Product List: ' + message;
}
}
Retrieving Data Using Http
- Setting up
- Sending an Http Request
- Observables and Reactive Extensions
-
Subscribing to an Observable
- An array whose items arrive asynchronously over time
- Helps manage asynchronous data
- Proposed feature for ES 2016
- Use Reactive Extensions (RxJS)
- Used within Angular
An Observable works like an Array, so we can use the map method. We use an arrow function.
Promise vs Observable
Promise | Observable |
---|---|
Returns a single value | Works with multiple values over time |
Not cancellable | Cancellable |
Supports map, filter, reduce and similar operators |
Setting Up
- Include the Angular 2 Http script
- Register HTTP_PROVIDERS (at the appropiate level)
- Import RxJS
<!-- HTTP -->
<script src="node_modules/angular2/bundles/http.dev.js"></script>
// add the level required e.g.: app.component
import {HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx'; // Load all features
@Component({
// ...
providers: [HTTP_PROVIDERS]
})
In the Service
- Import what we need
- Define a dependency for the http client service
- Use a constructor parameter
- Create a method for each http request
- Call the desired http method, such as get
- Pass in the Url
- Map the Http response to a JSON object
- Add error handling
In the Subscribing
- Call the subscribe method of the returned observable
- Provide a function to handle an emitted item
- Normally assigns a property to the returned JSON object
- Provide an error function to handle any returned errors
Sending an Http Request
import {Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable'
@Injectable()
export class ProductService {
private _productUrl = 'www.myWebService.com/api/products';
constructor(private _http: Http) {}
getProducts(): Observable<IProduct[]> {
return this._http.get(this._productUrl)
.map((response: Response => <IProuct[]>response.json());
}
}
Handling Errors
getProduts(): Observable<IProduct[]> {
return this._http.get(this._productUrl)
.map((response: Response) => <IProduct[]>response.json())
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
private handleError(error: Response) {}
Subscribing to an Observable
ngOnInit(): void {
this._productService.getProducts()
.subscribe(
products => this.products = products,
error => this.errorMessage = <any>error);
);
}
Navigation and Routing
- Configure a route for each component
- Define options/actions
- Tie a route to each option/action
- Activate the route based on user action
- Activating a route displays the component’s view
Setting Up
- Include the Angular 2 router script
- Define the base element
- Register ROUTER_PROVIDERS
<base href="/">
<!-- Required for routing -->
<script src="node_modules/angular2/bundles/router.dev.js"></script>
import {ROUTER_PROVIDERS,RouteConfig} from 'angular2/router';
@Component({
// ...
providers: [ROUTER_PROVIDERS]
})
Configuring Routes
- a route name
- a URL segment
- an associated component
The name must be in PascalCase.
@RouteConfig([
{path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true},
{path: '/products', name: 'Products', component: ProductListComponent}
])
Trying Routes to Actions
- Menu option, link, image or button that activates a route
- Typing the Url in the address bar / bookmark
- The browser’s forward or back buttons
// ...
import {ROUTER_PROVIDERS,RouteConfig} from 'angular2/router';
import {WelcomeComponent} from './home/welcome.component';
@Component({
selector: 'pm-app',
template: `
<ul class='nav navbar-nav'>
<li><a>Home</a></li>
<li><a>Product List</a></li>
</ul>
`,
directives: [ProductListComponent],
providers: [ProductHttpService,
HTTP_PROVIDERS,
ROUTER_PROVIDERS]
})
@RouteConfig([
{path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true},
{path: '/products', name: 'Products', component: ProductListComponent}
])
// ...
Placing the Views
<router-outlet></router-outlet>
import {Component} from 'angular2/core';
import {ProductListComponent} from './products/product-list.component'
import {ProductHttpService} from './products/product-http.service';
import {HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx'; // Load all features
import {ROUTER_PROVIDERS,ROUTER_DIRECTIVES,RouteConfig} from 'angular2/router';
import {WelcomeComponent} from './home/welcome.component';
@Component({
selector: 'pm-app',
template: `
<div>
<nav class='navbar navbar-default'>
<div class='container-fluid'>
<a class='navbar-brand'></a>
<ul class='nav navbar-nav'>
<li><a [routerLink]="['Welcome']">Home</a></li>
<li><a [routerLink]="['Products']">Product List</a></li>
</ul>
</div>
</nav>
<div class='container'>
<router-outlet></router-outlet>
</div>
</div>
`,
directives: [ROUTER_DIRECTIVES],
providers: [ProductHttpService,
HTTP_PROVIDERS,
ROUTER_PROVIDERS]
})
@RouteConfig([
{path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true},
{path: '/products', name: 'Products', component: ProductListComponent}
])
export class AppComponent {
pageTitle: string = 'Product Management';
}
Passing Parameters to a Route
// app.component
{path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent}
// component with the link
import {ROUTER_DIRECTIVES} from 'angular2/router';
@Component({
selector: 'pm-products',
templateUrl: 'app/products/product-list.component.html',
styleUrls: ['app/products/product-list.component.css'],
pipes: [ProductFilterPipe],
directives: [StarComponent,ROUTER_DIRECTIVES]
})
<!-- link -->
<td>
<a [routerLink]="['ProductDetail',{id: product.productId}]">
</a>
</td>
// some other component where we use the parameter
import {RouteParams} from 'angular2/router';
// ...
constructor(private _routeParams: RouteParams) {
console.log(this._routeParams.get('id'));
}
Complete example in the Component that uses the url parameter
import {Component} from 'angular2/core';
import {RouteParams} from 'angular2/router';
@Component({
templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent {
pageTitle: string = 'Product Detail';
constructor(private _routeParams: RouteParams) {
let id = +this._routeParams.get('id');
this.pageTitle += `: ${id}`;
}
}
Activating a Route with Code
import {Router} from 'angular2/router';
// ...
constructor(private _router: Router) {}
onBack(): void {
this._router.navigate(['Products']);
}
// ...
Angular2 Setup Revisited
tsconfig.json
Typescript configuration file. Indicates that the folder where it is contained is the root folder.
// these are needed so Angular2 can compile
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
{
"compilerOptions": {
"target": "es5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false // if set to true we strongly type
},
"exclude": [
"node_modules",
// we ignore typings/main because these are for node
// this also mean that we are compiling typings/browser
"typings/main",
"typings/main.d.ts"
]
}
package.json
- scripts
- dependencies
- devDependencies
scripts
E.g.
npm start # doesn't need the run command
npm run tsc
npm run lite
npm run lite # lite-server is specifically written for Angular2
Dependencies
If we want to install only the dependencies and not the devdependcies we can use: npm install --production
.
index.html
- router.dev.js
- http.dev.js
- system.src.js
- Rx.js
- angular2.dev.js
Polyfills
We add ES5 code so we can support the ES2015 syntax.
In web development, a polyfill (or polyfiller) is additional code which provides facilities that are not built into a web browser. It implements technology that a developer expects the browser to provide natively, providing a more uniform API landscape.
Shim
It lets us use an API to an older environment.
In computer programming, a shim is a small library that transparently intercepts API calls and changes the arguments passed, handles the operation itself, or redirects the operation elsewhere.
More
- Material Design
- Angular Universal
- Web Workers
- Ionic
- Nativescript