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