by Gion Kunz
Founder, UI Developer, UX Enthusiast at syncrea GmbH
M31 Andromeda Galaxy, ~2.5 million light-years from Earth and the nearest large galaxy to the Milky Way
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
let x: any = 10;
x = 'anything';
let y: unknown = 5;
y = 'unknown';
let z: number;
z = x;
// Not possible!
z = y;
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
let x: string | null;
x!.toLowerCase();
function die(message: string): never {
throw new Error(message);
}
(x || die('Ooops...')).toLowerCase();
let n: number;
// Not this -> let n: Number;
let b: boolean;
// Not this -> let b: Boolean;
let s: string;
// Not this -> let s: String;
const n = 100;
// Not this -> const n: number = 100;
const x = 'Hello World';
// Not this -> const x: string = 'Hello World';
type NameAgeTuple = [string, number];
const nameAndAge: NameAgeTuple = ['Peter', 52];
type AnswerToAllQuestions = 42;
// Error!
const n: AnswerToAllQuestions = 13;
type FibonacciNumber = 1 | 2 | 3 | 5 | 8 | 13;
// Error!
const n: FibonacciNumber = 4;
type OrderStatus = 'Ordered' | 'Paid' | 'Shipped' | 'Delivered';
interface Order {
id: number;
status: OrderStatus;
}
const order: Order = {
id: 1,
status: 'Ordered'
};
interface Cat {
name: string;
meow: () => void;
}
interface Dog {
name: string;
bark: () => void;
}
export type CatDog = Cat & Dog;
// a.ts
const magicNumber = 13;
export const modulePublic = 'This is public';
export function multiplyWithMagicNumber(n: number): number {
return magicNumber * n;
}
// Default export
export default [1, 2, 3, 5, 8, 13];
// index.ts
import {
modulePublic as moduleAText,
multiplyWithMagicNumber
} from './a';
import fibonacci from './a';
import * as A from './a';
console.log(moduleAText);
console.log(multiplyWithMagicNumber(7));
console.log(fibonacci);
console.log(A.default === fibonacci);
console.log(A.modulePublic === moduleAText);
function closure() {
var closureScoped = 1;
for(let i = 0; i < 10; i++) {
}
// Error
console.log(i);
}
const peter = {
name: 'Peter'
};
console.log(`My name is ${peter.name}!`);
const list = [1, 2, 3, 4, 5, 6, 7];
// Traditional but block scope counter
for(let i = 0; i < list.length; i++) {
console.log(i);
}
// Functional
list.forEach(console.log);
// for...of loop
for(let item of list) {
console.log(item);
}
const multiply = (a: number, b: number) => a * b;
// Array destructuring
const list = [1, 2, 3];
const [first, second, third] = list;
// Object destructuring
const peter = {
firstName: 'Peter',
lastName: 'Griffin'
};
const {firstName, lastName} = peter;
export interface ConfigurationOptions {
delay: number;
host: string;
limit: number;
}
function configure({delay = 1000,
host = 'localhost',
limit = 300}: Partial<ConfigurationOptions>) {
console.log(host);
console.log(delay);
console.log(limit);
}
// Array spreading
const list = [1, 2, 3, 4];
const appended = [...list, 5, 6, 7];
// Object property spreading
const peter = {
firstName: 'Peter',
lastName: 'Griffin'
};
const peterWithAge = {
...peter,
age: 52
};
// Traditional promise chain
function invalidateUsersLame() {
return fetch('/api/users')
.then(usersResponse => usersResponse.json())
.then((users: User[]) =>
Promise.all(users.map(user =>
fetch(`/api/users/${user.id}/invalidate`).then(() => user)
))
);
}
// Async await löve!
async function invalidateUsers() {
const usersResponse = await fetch('/api/users');
const users: User[] = await usersResponse.json();
for(let user of users) {
await fetch(`/api/users/${user.id}/invalidate`);
}
return users;
}
// Pure
function multiply(a: number, b: number): number {
return a * b;
}
// Impure
const name = 'Peter';
function getNameLength() {
return name.length;
}
interface Person {
readonly name: string;
readonly age: number;
}
const person: Person = {
name: 'Peter',
age: 52
};
// Error!
person.name = 'Pete';
interface Person {
name: string;
age: number;
}
const person: Readonly<Person> = {
name: 'Peter',
age: 52
};
// Error!
person.name = 'Pete';
const list: ReadonlyArray<number> = [1, 2, 3, 4];
// Error!
list[0] = 10;
// Error!
list.sort();
// Error!
list.fill(42);
export interface Person {
firstName: string;
lastName: string;
}
const person: Person = {
firstName: 'Peter',
lastName: 'Griffin'
};
export class DesigningDeveloper {
design(): void {
// Can design
}
develop(): void {
// Can develop
}
}
Angular
React
Vue.js
Component Rendering
Router
TypeScript Friendliness
Animation
Multi-Platform
Server Side Rendering
State Management
Core Support
Core Support
Core Support
Written in TypeScript
TypeScript JSX and good support
Good support
Core Support
react-router
vue-router
Core Support
react-move, react-motion and others
Core Support
vue-server-renderer
Core Support
Virtual DOM, react native
Render Adapter, Web Worker, Server, Native
Weex, supported but limited
Virual DOM, roll-your-own, Next.js
ngrx store 3rd party
Redux 3rd party
vuex
Complete local setup and create first project using Angular CLI.
# Install Angular CLI npm install -g @angular/cli # Create new project ng new my-first-app cd my-first-app # Create components with generator ng generate component my-component # Start dev server ng serve # Create production build ng build
import {Component} from '@angular/core';
@Component({
selector: 'my-component',
template: '<p>Hello World!</p>'
})
export class MyComponent {}
Module A
Module B
import
1
2
3
4
5
6
7
8
9
Components
Components
runtime
1
2
3
4
5
6
7
8
9
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@NgModule({
imports: [BrowserModule],
declarations: [Comp1, Comp2, Comp3, ...],
exports: [Comp1, Comp2, Comp3, ...],
bootstrap: [Component]
})
export class AppModule {}
import {platformBrowserDynamic}
from '@angular/platform-browser-dynamic';
import {AppModule} from './app-module';
platformBrowserDynamic().bootstrapModule(AppModule);
@Component({
selector: 'app'
template: '<div>{{message}}</div>'
})
class AppComponent {
message: string = 'Hello World!';
}
@Component({
selector: 'counter'
template: '<input type="text" [value]="num">'
})
class AppComponent {
num: number = 0;
constructor() {
setInterval(() => this.num++, 1000);
}
}
<p [title]="title"></p>
<p [attr.role]="role"></p>
<p [class.is-active]="isActive()"></p>
<p [style.display]="!isActive() ? 'none' : null"></p>
Regular DOM property
Attribute binding (setAttribute)
CSS class binding (classList)
CSS style binding
@Component({
selector: 'app'
template: `
<button (click)="onClick()">Click me</button>
`
})
class AppComponent {
onClick() {
alert('Cool!');
}
}
<input (input)="handleInput($event.target.value)">
@Component({
selector: 'app'
template: `
<input (keydown.enter)="submit($event.target.value)">
`
})
class AppComponent {
submit(value: string) {
console.log(value);
}
}
<ul>
<li *ngFor="let item of items">
{{item}}
</li>
</ul>
<p>Sheeps: {{sheepCount}}</p>
<p *ngIf="sheepCount > 1000">
You should be asleep now!
</p>
Create simple click counter component.
import {Input} from '@angular/core';
@Component({
selector: 'app-message',
template: '{{message}}'
})
export class MessageComponent {
@Input() message: string;
}
@Component({
selector: 'app-root',
template: '<app-message [message]="message"></app-message>'
})
export class AppComponent {
message: string = 'Hello World!';
}
import {Output, EventEmitter} from '@angular/core';
@Component({
selector: 'timer',
template: '<button (click)="startTimer()">Start</button>'
})
export class TimerComponent {
@Output() timeout = new EventEmitter();
startTimer() {
setTimeout(
() => this.timeout.emit('I timed out!'),
5000
);
}
}
@Component({
selector: 'app',
template: '<timer (timeout)="onTimeout($event)"></timer>'
})
export class AppComponent {
onTimeout(message) {
console.log(message);
}
}
A small component communication exercise.
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {MainComponent} from './main.component.ts';
import {ChildlComponent} from './child1/child1.component.ts';
import {Child2Component} from './child2/child2.component.ts';
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot([
{path: 'childl', component: ChildlComponent},
{path: 'child2', component: Child2Component}
])
],
declarations: [MainComponent, ChildlComponent, Child2Component],
bootstrap: [MainComponent]
})
export class MainModule {}
RouterModule.forRoot([{
path: 'child1',
component: Child1Component
},{
path: '',
pathMatch: 'full',
redirectTo: '/child1'
},{
path: '**',
component: PageNotFoundComponent
}];
<h1>Website</h1>
<main class="content">
<router-outlet></router-outlet>
</main>
<h1>Welcome!</h1>
<nav>
<a routerLink="/child1"
routerLinkActive="active">Child1</a>
<a routerLink="/child2"
routerLinkActive="active">Child2</a>
</nav>
<router-outlet></router-outlet>
RouterModule.forRoot([{
path: 'child/:id',
component: ChildComponent
}])
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'ngc-child',
template: 'Child:<p>Param: {{params.id}}</p>'
})
export class ChildComponent {
constructor(route: ActivatedRoute) {
this.params = route.snapshot.params;
}
}
@Component({
selector: 'ngc-child',
template: `
Child:
<p>Param: {{params.id}}</p>`
})
export class ChildComponent {
params: any;
constructor(route: ActivatedRoute) {
route.params.subscribe((params) => {
this.params = params;
});
}
}
Using the router to make an existing application routable.
@Component({
selector: 'my-component',
template: `
<ul>
<li *ngFor="let item of items | asnyc">{{item}}</li>
</ul>`
})
export class MyComponent {
items: Observable<Item[]>;
constructor(http: HttpClient) {
this.items = this.http
.get<Item[]>('/api/items');
}
}
class HttpClient {
delete<T>(url: string, options?: {...}): Observable<T>
get<T>(url: string, options?: {...}): Observable<T>
head<T>(url: string, options?: {...}): Observable<T>
jsonp<T>(url: string, callbackParam?: string): Observable<T>
options<T>(url: string, options?: {...}): Observable<any>
patch<T>(url: string, body: any | null, options?: {...}): Observable<T>
post<T>(url: string, body: any | null, options?: {...}): Observable<T>
put<T>(url: string, body: any | null, options?: {...}): Observable<T>
}
Use HttpClient service to load data asynchronously.
import {
TestBed,
ComponentFixture} from '@angular/core/testing';
import {MainComponent} from './main.component';
import {CounterService} from './counter.service';
describe('MainComponent', () => {
let fixture: ComponentFixture<MainComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MainComponent],
providers: [CounterService]
});
fixture = TestBed.createComponent(MainComponent);
});
});
Use TestBed to create dynamic test module
Include Components and dependencies within the test module
class ComponentFixture<T> {
debugElement: DebugElement
componentInstance: T
nativeElement: any
elementRef: ElementRef
changeDetectorRef: ChangeDetectorRef
componentRef: ComponentRef<T>
detectChanges(): void
isStable(): boolean
whenStable(): Promise<any>
destroy(): void
}
Wrapper around instantiated component
Access debug element, native host element and change detector
class DebugElement {
nativeElement: any
query(predicate: Predicate<DebugElement>): DebugElement
queryAll(predicate: Predicate<DebugElement>): DebugElement[]
children: DebugElement[]
triggerEventHandler(eventName: string, eventObj: any)
}
Wrapper around component host element
Allows to travers through DebugElement tree (DOM like)
Can be used to tigger events
import {DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
const countElement = fixture.debugElement
.query(By.css('.count'))
.nativeElement;
console.log(countElement.textContent);
import {DebugElement} from '@angular/core';
const buttonDebugElement: DebugElement =
fixture.debugElement.query(By.css('button'));
buttonDebugElement.triggerEventHandler('click', null);
import {CounterService} from './counter.service';
export class CounterServiceMock() {
increment() {}
}
TestBed.configureTestingModule({
declarations: [MainComponent],
providers: [{
provide: CounterService,
useClass: CounterServiceMock
}]
});
Testing our ToDo App.
# Create a nx workspace
npx create-nx-workspace
# Install Nx globally
npm install -g nx
# Start the application
cd my-workspace
nx serve my-app