On Github thoughtram / exploring-ng2-slides
Syntactic sugar for JavaScript prototypes introduced in ES2015.
class Car { manufacturer:string; constructor(manufacturer:string = 'BMW') { this.manufacturer = manufacturer; } drive(miles:number) {} } let bmw = new Car();
class Car { manufacturer:string; constructor(manufacturer:string = 'BMW') { this.manufacturer = manufacturer; } drive(miles:number) {} } let bmw = new Car();
class Car { manufacturer:string; constructor(manufacturer:string = 'BMW') { this.manufacturer = manufacturer; } drive(miles:number) {} } let bmw = new Car();
class Car { manufacturer:string; constructor(manufacturer:string = 'BMW') { this.manufacturer = manufacturer; } drive(miles:number) {} } let bmw = new Car();
class Car { manufacturer:string; constructor(manufacturer:string = 'BMW') { this.manufacturer = manufacturer; } drive(miles:number) {} } let bmw = new Car();
class Car { ... } class Convertible extends Car { } let cabrio = new Convertible();
class Car { ... } class Convertible extends Car { } let cabrio = new Convertible();
ES2015 brings a module system to the table that enables us to write modular code.
// Car.js export class Car { ... } export class Convertible extends Car { ... }
// Car.js export class Car { ... } export class Convertible extends Car { ... }
// App.js import {Car, Convertible} from 'Car'; let bmw = new Car(); let cabrio = new Convertible();
// App.js import {Car, Convertible} from 'Car'; let bmw = new Car(); let cabrio = new Convertible();
// App.js import {Car, Convertible} from 'Car'; let bmw = new Car(); let cabrio = new Convertible();
Type annotations provide optional static typing. Applied using : T syntax
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
var height:number = 6; var isDone:boolean = true; var name:string = 'thoughtram'; var list:number[] = [1, 2, 3]; var list:Array<number> = [1, 2, 3]; function add(x: number, y: number): number { return x+y; }
A decorator is an expression that is evaluated after a class has been defined, that can be used to annotate or modify the class in some fashion.
@someDecoratorExpression() class Car { @propertyDecorator() manufacturer: string; constructor(@paramDecorator() manufacturer: string) { } @methodDecorator() drive() { } }
@someDecoratorExpression() class Car { @propertyDecorator() manufacturer: string; constructor(@paramDecorator() manufacturer: string) { } @methodDecorator() drive() { } }
Everything in this workshop is based on alpha developer previews. Things might change in the future.
(Do not try this in production!)
A component in Angular 2 is a class with a @Component and @View decorator.
class ContactsApp { }
import {Component, View} from 'angular2/core'; @Component({ selector: 'contacts-app' }) @View({ template: 'Hello World!' }) class ContactsApp { }
How to instantiate that component?
import {Component, View} from 'angular2/core'; @Component({ selector: 'contacts-app' }) @View({ template: 'Hello World!' }) class ContactsApp { }
import {Component, View} from 'angular2/core'; import {bootstrap} from 'angular2/core'; @Component({ selector: 'contacts-app' }) @View({ template: 'Hello World!' }) class ContactsApp { } bootstrap(ContactsApp);
import {Component, View} from 'angular2/core'; import {bootstrap} from 'angular2/core'; @Component({ selector: 'contacts-app' }) @View({ template: 'Hello World!' }) class ContactsApp { } bootstrap(ContactsApp);
<html lang="en"> <head> <meta charset="utf-8"> <title>My first Angular 2 App!</title> </head> <body> <script src="..."></script> </body> </html>
<html lang="en"> <head> <meta charset="utf-8"> <title>My first Angular 2 App!</title> </head> <body> <contacts-app>Loading...</contacts-app> <script src="..."></script> </body> </html>
Angular performs the following tasks to bootstrap an application (simplified):
Upgrades located DOM element into Angular component Creates injector for the application Creates (emulated) Shadow DOM on component's host element Instantiates specified component Performs change detectionWe can bind data to elements in HTML templates and Angular automatically updates the UI as data changes.
@Component({...}) @View({ template: 'Hello {{name}}' }) class ContactsApp { name:string = 'AngularJS Days'; }
@Component({...}) @View({ template: 'Hello {{name}}' }) class ContactsApp { name:string = 'AngularJS Days'; }
Which is the equivalent of...
@Component({...}) @View({ template: 'Hello {{name}}' }) class ContactsApp { name:string; constructor() { this.name = 'AngularJS Days'; } }
@Component({...}) @View({ template: 'Hello {{name}}' }) class ContactsApp { name:string; constructor() { this.name = 'AngularJS Days'; } }
interface Contact { id: Number; firstname?: string; lastname?: string; street?: string; zip?: string; city?: string; image?: string; }
@Component() @View() class ContactsApp { contact: Contact = { id: 1, firstname: 'Christoph', lastname: 'Burgdorf', street: 'thoughtroad 2', zip: '30149', city: 'thoughtworld', image: 'path/to/image' } }
@Component() @View() class ContactsApp { contact: Contact = { id: 1, firstname: 'Christoph', lastname: 'Burgdorf', street: 'thoughtroad 2', zip: '30149', city: 'thoughtworld', image: 'path/to/image' } }
<div> <img [src]="contact.image"> <span> {{contact.firstname}} {{contact.lastname}} </span> </div>
Let's make it a list!
contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, { id: 2, firstname: 'Pascal', ...}, { id: 3, firstname: 'Julie', ...}, { id: 4, firstname: 'Igor', ...}, ... ];
contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, { id: 2, firstname: 'Pascal', ...}, { id: 3, firstname: 'Misko', ...}, { id: 4, firstname: 'Igor', ...}, ... ];
Remember our component's template?
<div> <img [src]="contact.image"> <span> {{contact.firstname}} {{contact.lastname}} </span> </div>
<ul class="collection"> <li> <!-- each contact goes here --> </li> </ul>
The NgFor directive instantiates a template once per item from an iterable.
@Component() @View({ ... }) class ContactsApp { contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, ... ]; }
import {NgFor} from 'angular2/core'; @Component() @View({ directives: [NgFor], ... }) class ContactsApp { contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, ... ]; }
import {NgFor} from 'angular2/core'; @Component() @View({ directives: [NgFor], ... }) class ContactsApp { contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, ... ]; }
<ul class="collection"> <li> <!-- each contact goes here --> </li> </ul>
<ul class="collection"> <li *ng-for="#contact in contacts"> <!-- each contact goes here --> </li> </ul>
<ul class="collection"> <li *ng-for="#contact in contacts"> <!-- each contact goes here --> </li> </ul>
Services in Angular 2 are simpy ES2015 classes.
import {Contact} from '../models/contact'; class ContactsService { private contacts: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, { id: 2, firstname: 'Pascal', ...}, { id: 3, firstname: 'Misko', ...}, ]; getContacts() { ... } }
@Component() @View() class ContactsApp { contacts: Contact[]; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
@Component() @View() class ContactsApp { contacts: Contact[]; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
@Component() @View() class ContactsApp { contacts: Contact[]; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
But how do we get there?
We can configure the root component's injector to make any service available for DI.
import {Component, View} from 'angular2/core'; import {bootstrap} from 'angular2/core'; import {ContactsService} from './common/contacts-service'; @Component() @View() class ContactsApp { ... } bootstrap(ContactsApp);
import {Component, View} from 'angular2/core'; import {bootstrap} from 'angular2/core'; import {ContactsService} from './common/contacts-service'; @Component() @View() class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
import {Component, View} from 'angular2/core'; import {bootstrap} from 'angular2/core'; import {ContactsService} from './common/contacts-service'; @Component() @View() class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
It turns out that our application consists of three components:
In order to make routing in Angular 2 work, the router module provides the following components:
We use the @RouteConfig decorator to configure routes for our application on the root component.
@Component() @View() class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
import {RouteConfig} from '...'; @Component() @View() @RouteConfig([ // route definitions go here ]) class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
import {RouteConfig} from '...'; @Component() @View() @RouteConfig([ // route definitions go here ]) class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
A RouteDefinition has a path, a component, and an optional alias.
import {RouteConfig} from '...'; @Component() @View() @RouteConfig([ // route definitions go here ]) class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
import {RouteConfig} from '...'; import {ContactsList} from '...'; @Component() @View() @RouteConfig([ { path: '/', component: ContactsList, as: 'ContactList' } ]) class ContactsApp { ... } bootstrap(ContactsApp, [ContactsService]);
The component router comes with a <router-outlet> directive, to specify a viewport where components should be loaded into.
@Component() @View({ }) @RouteConfig() class ContactsApp { ... }
import {ROUTER_DIRECTIVES} from '...'; @Component() @View({ directives: [ROUTER_DIRECTIVES], template: '<router-outlet></router-outlet>' }) @RouteConfig() class ContactsApp { ... }
import {ROUTER_DIRECTIVES} from '...'; @Component() @View({ directives: [ROUTER_DIRECTIVES], template: '<router-outlet></router-outlet>' }) @RouteConfig() class ContactsApp { ... }
import {ROUTER_DIRECTIVES} from '...'; @Component() @View({ directives: [ROUTER_DIRECTIVES], template: '<router-outlet></router-outlet>' }) @RouteConfig() class ContactsApp { ... }
The router-link directive can be used to declaratively link to a specific part of our application using a DSL.
@Component() @View() @RouteConfig([ { path: '/', component: ContactsList, as: 'ContactList' } ]) class ContactsApp { ... }
import {ContactDetail} from '...'; @Component() @View() @RouteConfig([ { path: '/', component: ContactsList, as: 'ContactList' }, { path: '/contact/:id', component: ContactDetail, as: 'ContactDetail' } ]) class ContactsApp { ... }
import {ContactDetail} from '...'; @Component() @View() @RouteConfig([ { path: '/', component: ContactsList, as: 'ContactList' }, { path: '/contact/:id', component: ContactDetail, as: 'ContactDetail' } ]) class ContactsApp { ... }
<ul class="collection"> <li *ng-for="#contact in contacts"> <img [src]="contact.image"> <span> {{contact.firstname}} {{contact.lastname}} </span> </li> </ul>
<ul class="collection"> <li *ng-for="#contact in contacts"> <a [router-link]="['/ContactDetail', { id: contact.id }]"> <img [src]="contact.image"> <span> {{contact.firstname}} {{contact.lastname}} </span> </a> </li> </ul>
<ul class="collection"> <li *ng-for="#contact in contacts"> <a [router-link]="['/ContactDetail', { id: contact.id }]"> <img [src]="contact.image"> <span> {{contact.firstname}} {{contact.lastname}} </span> </a> </li> </ul≷
@Component({selector: 'contact-detail'}) @View({ templateUrl: 'contact-detail.html' }) export class ContactDetail { contact:Contact; constructor() { } }
How do we get access to route params?
@Component({selector: 'contact-detail'}) @View({ templateUrl: 'contact-detail.html' }) export class ContactDetail { contact:Contact; constructor() { } }
import {RouteParams} from 'angular2/router'; @Component({selector: 'contact-detail'}) @View({ templateUrl: 'contact-detail.html' }) export class ContactDetail { contact:Contact; constructor(params:RouteParams, contactsService:ContactsService) { this.contact = contactsService.getContact(params.get('id')); } }
import {RouteParams} from 'angular2/router'; @Component({selector: 'contact-detail'}) @View({ templateUrl: 'contact-detail.html' }) export class ContactDetail { contact:Contact; constructor(params:RouteParams, contactsService:ContactsService) { this.contact = contactsService.getContact(params.get('id')); } }
import {RouteParams} from 'angular2/router'; @Component({selector: 'contact-detail'}) @View({ templateUrl: 'contact-detail.html' }) export class ContactDetail { contact:Contact; constructor(params:RouteParams, contactsService:ContactsService) { this.contact = contactsService.getContact(params.get('id')); } }
<div> <span>{{contact.firstname}} {{contact.lastname}}</span> <span>{{contact.street}}</span> <span>{{contact.zip}} {{contact.city}}</span> <span>{{contact.country}}</span> </div>
Demo App: github.com/thoughtram/ng2-contacts-demo