On Github thoughtram / angular2-master-class-jump-start-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();
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();
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();
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();
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 { ... } 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 { ... }
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 { ... }
ES2015 brings a module system to the table that enables us to write modular code.
// App.js import {Car, Convertible} from 'Car'; let bmw = new Car(); let cabrio = new Convertible();
ES2015 brings a module system to the table that enables us to write modular code.
// App.js import {Car, Convertible} from 'Car'; let bmw = new Car(); let cabrio = new Convertible();
ES2015 brings a module system to the table that enables us to write modular code.
// 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; }
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; }
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; }
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; }
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; }
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; }
export class ContactsAppComponent {}
import { Component } from '@angular/core'; @Component({ selector: 'trm-contacts-app', template: 'Hello World!', styleUrls: ['contacts.component.css'] }) export class ContactsAppComponent {}
Every component is part of an NgModule
import { NgModule } from '@angular/core'; @NgModule({ }) export class ContactsModule {}
import { NgModule } from '@angular/core'; import { ContactsAppComponent } from './contacts.component'; @NgModule({ declarations: [ContactsAppComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
import { NgModule } from '@angular/core'; import { ContactsAppComponent } from './contacts.component'; @NgModule({ declarations: [ContactsAppComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ContactsAppComponent } from './contacts.component'; @NgModule({ imports: [BrowserModule], declarations: [ContactsAppComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ContactsAppComponent } from './contacts.component'; @NgModule({ imports: [BrowserModule], declarations: [ContactsAppComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
How to bootstrap this module?
import { platformBrowserDynamic } from '@angular/core'; import { ContactsModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(ContactsModule);
import { platformBrowserDynamic } from '@angular/core'; import { ContactsModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(ContactsModule);
<html lang="en"> <head> <meta charset="utf-8"> <title>Contacts</title> </head> <body> <script src="..."></script> </body> </html>
<html lang="en"> <head> <meta charset="utf-8"> <title>Contacts</title> </head> <body> <trm-contacts-app>Loading...</trm-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 detectionDirectives/Components are added to an NgModule's declarations property.
@NgModule({ imports: [BrowserModule], declarations: [ContactsAppComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
Directives/Components are added to an NgModule's declarations property.
import { ContactsHeaderComponent } from './contacts-header'; @NgModule({ imports: [BrowserModule], declarations: [ContactsAppComponent, ContactsHeaderComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
Directives/Components are added to an NgModule's declarations property.
import { ContactsHeaderComponent } from './contacts-header'; @NgModule({ imports: [BrowserModule], declarations: [ContactsAppComponent, ContactsHeaderComponent], bootstrap: [ContactsAppComponent] }) export class ContactsModule {}
Directives/Components can then be used in components throughout that module.
@Component({ selector: 'trm-contacts-app', template: 'Hello World!', styleUrls: ['contacts.component.css'] }) export class ContactsAppComponent {}
Directives/Components can then be used in components throughout that module.
@Component({ selector: 'trm-contacts-app', template: '<trm-contacts-header></trm-contacts-header>', styleUrls: ['contacts.component.css'] }) export class ContactsAppComponent {}
Tip: Don't forget to git commit your solution
We can bind data to elements in HTML templates and Angular automatically updates the UI as data changes.
@Component({ selector: 'trm-contacts-app', template: 'Hello {{name}}' }) export class ContactsAppComponent { name = 'Angular 2'; }
@Component({ selector: 'trm-contacts-app', template: 'Hello {{name}}' }) export class ContactsAppComponent { name = 'Angular 2'; }
To enable type safety during compilation, we can define interfaces using the interface key word
interface Contact { id: Number; name: string; email: string; phone: string; ... }
import { Contact } from './models/contact'; @Component(...) export class ContactsAppComponent { }
import { Contact } from './models/contact'; @Component(...) export class ContactsAppComponent { contact: Contact = { id: 1, name: 'Christoph', email: 'chris@burdorf.io', image: 'path/to/image', ... } }
@Component({ selector: 'trm-contacts-app', template: `
@Component({ selector: 'trm-contacts-app', template: ` <div> <img [src]="contact.image"> <span> {{contact.name}} </span> </div> ` }) export class ContactsAppComponent { ... }
Tip: Don't forget to git commit your solution
Attributes are always strings and only set up the initial value
<input value="Christoph"> <img src="path/to/img.png">
Properties can be any value and changed imperatively at run-time
var input = document.querySelector('input'); input.value; // 'Christoph' var img = document.querySelector('img'); img.src; // 'path/to/img.png'
Properties and attributes values aren't always reflected automatically
var input = document.querySelector('input'); input.value; // 'Christoph' input.value = 'Pascal'; input.getAttribute('value'); // 'Christoph'
Properties and attributes values aren't always reflected automatically
var input = document.querySelector('input'); input.value; // 'Christoph' input.value = 'Pascal'; input.getAttribute('value'); // 'Christoph'
Angular 2 binds to element properties instead of attributes in order to work with any element
[] - syntax is the declarative way of writing to a property
export const CONTACTS: Contact[] = [ { id: 1, firstname: 'Christoph', ...}, { id: 2, firstname: 'Pascal', ...}, { id: 3, firstname: 'Julie', ...}, { id: 4, firstname: 'Igor', ...}, ... ];
import { CONTACTS } from './data/contact-data'; @Component({ selector: 'trm-contacts-app', template: `
import { CONTACTS } from './data/contact-data'; @Component({ selector: 'trm-contacts-app', template: ` <ul> <li> <!-- each contact goes here --> </li> </ul> ` }) export class ContactsAppComponent { contacts: Contact[] = CONTACTS; }
The NgFor directive instantiates a template once per item from an iterable.
<li *ngFor="let item of items">...</li>
@Component({ selector: 'trm-contacts-app', template: ` <ul> <li> <!-- each contact goes here --> </li> </ul> ` }) export class ContactsAppComponent { ... }
@Component({ selector: 'trm-contacts-app', template: ` <ul> <li *ngFor="let contact of contacts"> <img [src]="contact.image"> <span> {{contact.name}} </span> </li> </ul> ` }) export class ContactsAppComponent { ... }
Tip: Don't forget to git commit your solution
We can create services in Angular using ES2015 classes.
import { Injectable } from '@angular/core'; @Injectable() export class ContactsService { private contacts: Contact[] = CONTACTS; getContacts() { return this.contacts; } }
An NgModule's providers accepts a list of providers to configure the injector.
@NgModule({ ... providers: [ { provide: ContactsService, useClass: ContactsService } ] }) export class ContactsModule {}
An NgModule's providers accepts a list of providers to configure the injector.
@NgModule({ ... providers: [ { provide: ContactsService, useClass: ContactsService } ] }) export class ContactsModule {}
An NgModule's providers accepts a list of providers to configure the injector.
@NgModule({ ... providers: [ ContactsService ] }) export class ContactsModule {}
Dependencies can be injected using TypeScript type annotations.
@Component(...) export class ContactsAppComponent { contacts: Contact[]; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
Dependencies can be injected using TypeScript type annotations.
@Component(...) export class ContactsAppComponent { contacts: Contact[]; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
Directives and Components have lifecycle hooks. Some of them are:
We use ngOnInit to do initialization work in the component.
import { OnInit } from '@angular/core'; ... export class ContactsAppComponent implements OnInit { contacts: Contact[]; constructor(private contactsService: ContactsService) {} ngOnInit() { this.contacts = this.contactsService.getContacts(); } }
We use ngOnInit to do initialization work in the component.
import { OnInit } from '@angular/core'; ... export class ContactsAppComponent implements OnInit { contacts: Contact[]; constructor(private contactsService: ContactsService) {} ngOnInit() { this.contacts = this.contactsService.getContacts(); } }
Tip: Don't forget to git commit your solution
Our application consists of three components:
We define routes as a collection of Route objects that have a path and a component
import { ContactsListComponent } from './contacts-list'; export const ContactsAppRoutes = [ { path: '', component: ContactsListComponent } ]
We define routes as a collection of Route objects that have a path and a component
import { ContactsListComponent } from './contacts-list'; export const ContactsAppRoutes = [ { path: '', component: ContactsListComponent } ]
RouterModule.forRoot() configures routes for the root module of our app.
import { RouterModule } from '@angular/router'; import { ContactsAppRoutes } from './app.routes'; @NgModule({ imports: [ ... RouterModule.forRoot(ContactsAppRoutes) ] }) export class ContactsModule {}
RouterModule.forRoot() configures routes for the root module of our app.
import { RouterModule } from '@angular/router'; import { ContactsAppRoutes } from './app.routes'; @NgModule({ imports: [ ... RouterModule.forRoot(ContactsAppRoutes) ] }) export class ContactsModule {}
<router-outlet> directive specifies a viewport where components should be loaded into.
@Component({ selector: 'trm-contacts-app', template: '...', styleUrls: ['contacts.component.css'] }) class ContactsAppComponent {...}
<router-outlet> directive specifies a viewport where components should be loaded into.
@Component({ selector: 'trm-contacts-app', template: '<router-outlet></router-outlet>', styleUrls: ['contacts.component.css'] }) class ContactsAppComponent {...}
Tip: Don't forget to git commit your solution
RouterLink lets link to a specific part of our app.
<li *ngFor="let contact of contacts"> <img src="{{contact.image}}"> <span> {{contact.name}} </span> </li>
RouterLink lets link to a specific part of our app.
<li *ngFor="let contact of contacts"> <a [routerLink]="['/contact', contact.id]"> <img src="{{contact.image}}"> <span> {{contact.name}} </span> </a> </li>
RouterLink lets link to a specific part of our app.
<li *ngFor="let contact of contacts"> <a [routerLink]="['/contact', contact.id]"> <img src="{{contact.image}}"> <span> {{contact.name}} </span> </a> </li>
import { ContactsListComponent } from './contacts-list'; export const ContactsAppRoutes = [ { path: '', component: ContactsListComponent } ]
import { ContactsListComponent } from './contacts-list'; import { ContactsDetailComponent } from './contacts-detail'; export const ContactsAppRoutes = [ { path: '', component: ContactsListComponent }, { path: 'contact/:id', component: ContactsDetailComponent } ]
import { ContactsListComponent } from './contacts-list'; import { ContactsDetailComponent } from './contacts-detail'; export const ContactsAppRoutes = [ { path: '', component: ContactsListComponent }, { path: 'contact/:id', component: ContactsDetailComponent } ]
We can access a snapshot of a route state via ActivatedRoute.snapshot
import { ActivatedRoute } from '@angular/router'; @Component(...) class ContactsDetailComponent { constructor(route: ActivatedRoute) { let id = this.route.snapshot.params['id']; this.contact = this.contactsService .getContact(id); } }
We can access a snapshot of a route state via ActivatedRoute.snapshot
import { ActivatedRoute } from '@angular/router'; @Component(...) class ContactsDetailComponent { constructor(route: ActivatedRoute) { let id = this.route.snapshot.params['id']; this.contact = this.contactsService .getContact(id) } }
Tip: Don't forget to git commit your solution
Angular 2 introduces a completely redesigned Http layer based on Observables.
HttpModule configures the injector for http dependencies.
import { HttpModule } from '@angular/http'; import 'rxjs/add/operator/map'; @NgModule({ imports: [ ... HttpModule ] }) export class ContactsModule {...}
HttpModule configures the injector for http dependencies.
import { HttpModule } from '@angular/http'; import 'rxjs/add/operator/map'; @NgModule({ imports: [ ... HttpModule ] }) export class ContactsModule {...}
HttpModule configures the injector for http dependencies.
import { HttpModule } from '@angular/http'; import 'rxjs/add/operator/map'; @NgModule({ imports: [ ... HttpModule ] }) export class ContactsModule {...}
Http is an injectable class with methods to perform http requests.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; @Injectable() class ContactsService { constructor(private http: Http) { } }
Http is an injectable class with methods to perform http requests.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; @Injectable() class ContactsService { constructor(private http: Http) { } }
Http is an injectable class with methods to perform http requests.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; @Injectable() class ContactsService { constructor(private http: Http) { } }
Http returns an Observable which will emit a single response it's received.
class ContactsService { ... getContacts() { return this.http.get('http://myapi.com/contacts') .map((res) => { return res.json(); }) .map((data) => { return data.items; }); } }
Http returns an Observable which will emit a single response it's received.
class ContactsService { ... getContacts() { return this.http.get('http://myapi.com/contacts') .map((res) => { return res.json(); }) .map((data) => { return data.items; }); } }
Http returns an Observable which will emit a single response it's received.
class ContactsService { ... getContacts() { return this.http.get('http://myapi.com/contacts') .map((res) => { return res.json(); }) .map((data) => { return data.items; }); } }
Http returns an Observable which will emit a single response it's received.
class ContactsService { ... getContacts() { return this.http.get('http://myapi.com/contacts') .map(res => res.json()) .map(data => data.items); } }
We can subscribe to Observables using .subscribe()
@Component(...) class ContactsListComponent { constructor(contactsService: ContactsService) { contactsService.getContacts() .subscribe(contacts => this.contacts = contacts); } }
We can subscribe to Observables using .subscribe()
@Component(...) class ContactsListComponent { constructor(contactsService: ContactsService) { contactsService.getContacts() .subscribe(contacts => this.contacts = contacts); } }
Angular throws a null reference error when null objects are accessed:
The null contact's name is {{nullContact.name}}
Results in:
TypeError: Cannot read property 'name' of null in [null]
The "safe navigation" operator (?.) is a convenient way to guard against nulls in property paths
The null contact's name is {{nullContact?.name}}
The "safe navigation" operator (?.) is a convenient way to guard against nulls in property paths
The null contact's name is {{nullContact?.name}}
Tip: Don't forget to git commit your solution
NgModel implements two-way data binding by providing and combining property and event bindings.
<input [(ngModel)]="name">
Hello, {{name}}!
Or, the canonical version:
<input bindon-ngModel="name">
Hello, {{name}}!
Two-way Binding can be implemented like this:
<input [value]="name" (input)="name=$event.target.value">
ngModel hides repetitive expressions:
<input [ngModel]="name" (ngModelChange)="name=$event">
Shorthand syntax:
<input [(ngModel)]="name">
Two-way Binding can be implemented like this:
<input [value]="name" (input)="name=$event.target.value">
ngModel hides repetitive expressions:
<input [ngModel]="name" (ngModelChange)="name=$event">
Shorthand syntax:
<input [(ngModel)]="name">
To use NgModel we need to add FormsModule to our application.
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ ... FormsModule ] }) export class ContactsModule {}
Using ngModel we can already create simple forms.
<form> <label>Firstname:</label> <input [(ngModel)]="contact.firstname"> <label>Lastname:</label> <input [(ngModel)]="contact.lastname"> ... </form>
Http.put() performs a request with the put method
class ContactsService { ... updateContact(contact: Contact) { let url = `myapi.com/contacts/${contact.id}`; return this.http.put(url, contact); } }
Http.put() performs a request with the put method
class ContactsService { ... updateContact(contact: Contact) { let url = `myapi.com/contacts/${contact.id}`; return this.http.put(url, contact); } }
Http.put() performs a request with the put method
class ContactsService { ... updateContact(contact: Contact) { let url = `myapi.com/contacts/${contact.id}`; return this.http.put(url, contact); } }
Http.put() performs a request with the put method
class ContactsService { ... updateContact(contact: Contact) { let url = `myapi.com/contacts/${contact.id}`; return this.http.put(url, contact); } }
Http.put() performs a request with the put method
class ContactsService { ... updateContact(contact: Contact) { let url = `myapi.com/contacts/${contact.id}`; return this.http.put(url, contact); } }
Tip: Don't forget to git commit your solution
A pipe takes in data as input and transforms it to a desired output.
@Component({ selector: 'trm-contacts-detail', template: ` ... Birthday: {{contact?.dateOfBirth | date}} ` }) class ContactsDetailComponent { }
A pipe takes in data as input and transforms it to a desired output.
@Component({ selector: 'trm-contacts-detail', template: ` ... <span>Birthday:</span> <span>{{contact?.dateOfBirth | date}}</span> ` }) class ContactsDetailComponent { }
The Async pipe can receive a Promise or Observable as input and subscribe to the input automatically.
import { Observable } from 'rxjs/Observable'; @Component(...) class ContactsListComponent { contacts: Observable<Array<Contact>>; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
The Async pipe can receive a Promise or Observable as input and subscribe to the input automatically.
import { Observable } from 'rxjs/Observable'; @Component(...) class ContactsListComponent { contacts: Observable<Array<Contact>>; constructor(contactsService: ContactsService) { this.contacts = contactsService.getContacts(); } }
The Async pipe can receive a Promise or Observable as input and subscribe to the input automatically.
@Component({ template: ` <ul> <li *ngFor="let contact of contacts | async"> ... </li> </ul> ` }) class ContactsListComponent {...}
The Async pipe can receive a Promise or Observable as input and subscribe to the input automatically.
@Component({ template: ` <ul> <li *ngFor="let contact of contacts | async"> ... </li> </ul> ` }) class ContactsListComponent {...}
Tip: Don't forget to git commit your solution