go beast mode with realtime reactive interfaces in angular 2 and firebase

Post on 09-Jan-2017

1.270 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Go Beast Mode with Realtime Reactive Interfacesin Angular and Firebase

State management

Controlling flow

Code volume

Enter Observables

return this.http.get(this.URLS.FETCH) .map(res => res.json()) .toPromise();

Problem solved!

Observables give us a powerful way to encapsulate, transpor t and transform data from user interactions to create powerful and immersive experiences.

Encapsulate Transport Transform

Encapsulate Transport Transform

Encapsulate Transport Transform

Iterator Pattern Observer Pattern

State Communication

Communicate state over time

Observable stream

Values over time

SINGLE MULTIPLE

SYNCHRONOUS Function Enumerable

ASYNCHRONOUS Promise Observable

Value consumption

SINGLE MULTIPLE

PULL Function Enumerable

PUSH Promise Observable

But observables are hard!!!

The Observable Stream

input output

output input

The Basic Sequence

final input

initial output

magic

subscribe

event

operators

@ViewChild('btn') btn;message: string;ngOnInit() { Observable.fromEvent(this.getNativeElement(this.btn), 'click') .subscribe(result => this.message = 'Beast Mode Activated!');}getNativeElement(element) { return element._elementRef.nativeElement;}

@ViewChild('btn') btn;message: string; ngOnInit() { Observable.fromEvent(this.getNativeElement(this.btn), 'click') .subscribe(result => this.message = 'Beast Mode Activated!');}getNativeElement(element) { return element._elementRef.nativeElement;}

Initial output

@ViewChild('btn') btn;message: string; ngOnInit() { Observable.fromEvent(this.getNativeElement(this.btn), 'click') .subscribe(event => this.message = 'Beast Mode Activated!');}getNativeElement(element) { return element._elementRef.nativeElement;}

Final input

@ViewChild('btn') btn;message: string; ngOnInit() { Observable.fromEvent(this.getNativeElement(this.btn), 'click') .map(event => 'Beast Mode Activated!') .subscribe(result => this.message = result);}getNativeElement(element) { return element._elementRef.nativeElement;}

Everything in between

@ViewChild('btn') btn;message: string; ngOnInit() { Observable.fromEvent(this.getNativeElement(this.btn), 'click') .filter(event => event.shiftKey) .map(event => 'Beast Mode Activated!') .subscribe(result => this.message = result);}getNativeElement(element) { return element._elementRef.nativeElement;}

Everything in between

BASIC SEQUENCE

How do we preserve state in a stream?

<button #right>Right</button><div class="container"> <div #ball class="ball" [style.left]="position.x + 'px'" [style.top]="position.y + 'px'"> </div></div>

@ViewChild('right') right;position: any;ngOnInit() { Observable .fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

@ViewChild('right') right;position: any; ngOnInit() { Observable .fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

@ViewChild('right') right;position: any; ngOnInit() { Observable .fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

@ViewChild('right') right;position: any; ngOnInit() { Observable .fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

MAINTAINING STATE

What if we have more than one stream?

@ViewChild('left') left;@ViewChild('right') right;position: any;ngOnInit() { const left$ = Observable.fromEvent(this.getNativeElement(this.left), 'click') .map(event => -10); const right$ = Observable.fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10); Observable.merge(left$, right$) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

@ViewChild('left') left;@ViewChild('right') right;position: any;ngOnInit() { const left$ = Observable.fromEvent(this.getNativeElement(this.left), 'click') .map(event => -10); const right$ = Observable.fromEvent(this.getNativeElement(this.right), 'click') .map(event => 10); Observable.merge(left$, right$) .startWith({x: 100, y: 100}) .scan((acc, curr) => { return { y: acc.y, x: acc.x + curr}}) .subscribe(result => this.position = result);}

MERGING STREAMS

What can we put in a stream?

increment(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] + value})}decrement(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] - value})}ngOnInit() { const leftArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowLeft') .mapTo(position => this.decrement(position, 'x', 10)); const rightArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowRight') .mapTo(position => this.increment(position, 'x', 10)); Observable.merge(leftArrow$, rightArrow$) .startWith({x: 100, y: 100}) .scan((acc, curr) => curr(acc)) .subscribe(result => this.position = result); }

increment(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] + value})}decrement(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] - value})}ngOnInit() { const leftArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowLeft') .mapTo(position => this.decrement(position, 'x', 10)); const rightArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowRight') .mapTo(position => this.increment(position, 'x', 10)); Observable.merge(leftArrow$, rightArrow$) .startWith({x: 100, y: 100}) .scan((acc, curr) => curr(acc)) .subscribe(result => this.position = result); }

increment(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] + value})}decrement(obj, prop, value) { return Object.assign({}, obj, {[prop]: obj[prop] - value})}ngOnInit() { const leftArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowLeft') .mapTo(position => this.decrement(position, 'x', 10)); const rightArrow$ = Observable.fromEvent(document, 'keydown') .filter(event => event.key === 'ArrowRight') .mapTo(position => this.increment(position, 'x', 10)); Observable.merge(leftArrow$, rightArrow$) .startWith({x: 100, y: 100}) .scan((acc, curr) => curr(acc)) .subscribe(result => this.position = result); }

MAPPING TO FUNCTIONS

How can we sequence a stream?

@ViewChild('ball') ball; position: any;ngOnInit() { const OFFSET = 50; const move$ = Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX - OFFSET, y: event.pageY - OFFSET}; }); const down$ = Observable.fromEvent(this.ball.nativeElement, 'mousedown'); down$ .switchMap(event => move$) .startWith({ x: 100, y: 100}) .subscribe(result => this.position = result);}

@ViewChild('ball') ball;position: any;ngOnInit() { const OFFSET = 50; const move$ = Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX - OFFSET, y: event.pageY - OFFSET}; }); const down$ = Observable.fromEvent(this.ball.nativeElement, 'mousedown'); down$ .switchMap(event => move$) .startWith({ x: 100, y: 100}) .subscribe(result => this.position = result); }

@ViewChild('ball') ball;position: any;ngOnInit() { const OFFSET = 50; const move$ = Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX - OFFSET, y: event.pageY - OFFSET}; }); const down$ = Observable.fromEvent(this.ball.nativeElement, 'mousedown'); down$ .switchMap(event => move$) .startWith({ x: 100, y: 100}) .subscribe(result => this.position = result);}

@ViewChild('ball') ball;position: any;ngOnInit() { const OFFSET = 50; const move$ = Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX - OFFSET, y: event.pageY - OFFSET}; }); const down$ = Observable.fromEvent(this.ball.nativeElement, 'mousedown'); down$ .switchMap(event => move$) .startWith({ x: 100, y: 100}) .subscribe(result => this.position = result);}

@ViewChild('ball') ball;position: any;ngOnInit() { const OFFSET = 50; const move$ = Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX - OFFSET, y: event.pageY - OFFSET}; }); const down$ = Observable.fromEvent(this.ball.nativeElement, 'mousedown'); const up$ = Observable.fromEvent(document, 'mouseup'); down$ .switchMap(event => move$.takeUntil(up$)) .startWith({ x: 100, y: 100}) .subscribe(result => this.position = result); }

TRIGGERS

What effect does the origin of the stream have on the output?

<div class="container"> <app-line *ngFor="let line of lines" [line]="line"> </app-line></div>

<svg> <line [attr.x1]="line.x1" [attr.y1]="line.y1" [attr.x2]="line.x2" [attr.y2]="line.y2" style="stroke:rgb(255,0,0);stroke-width:2"/> </svg>

lines: any[] = []; ngOnInit() { Observable.fromEvent(document, 'click') .map(event => { return {x: event.pageX, y: event.pageY}; }) .pairwise(2) .map(positions => { const p1 = positions[0]; const p2 = positions[1]; return { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }; }) .subscribe(line => this.lines = [...this.lines, line]);}

STREAM ORIGINS

lines: any[] = []; ngOnInit() { Observable.fromEvent(document, 'mousemove') .map(event => { return {x: event.pageX, y: event.pageY}; }) .pairwise(2) .map(position => { const p1 = positions[0]; const p2 = positions[1]; return { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }; }) .subscribe(line => this.lines = [...this.lines, line]);}

STREAM ORIGINS

What are some fun things we can do with a stream?

@ViewChild('ball') ball;ngOnInit() { const OFFSET = 50; Observable.fromEvent(document, 'click') .map(event => { return {x: event.clientX - OFFSET, y: event.clientY - OFFSET} }) .subscribe(props => TweenMax.to(this.ball.nativeElement, 1, props))}

SIMPLE ANIMATION

What are some MOAR fun things we can do with a stream?

circles: any[] = [];ngOnInit() { const OFFSET = 25; Observable.fromEvent(document, 'mousemove') .map(event => { return { x: event.clientX - OFFSET, y: event.clientY - OFFSET} }) .subscribe(circle => this.circles = [ ...this.circles, circle])}

<div class="container"> <app-circle *ngFor="let circle of circles" [style.left]="circle.x + 'px'" [style.top]="circle.y + 'px'"> </app-circle></div>

export class CircleComponent implements OnInit { @ViewChild('circle') circle; ngOnInit() { TweenMax.to(this.circle.nativeElement, 2, {alpha: 0, width: 0, height: 0}); }}

ANIMATION

The Realtime Observable Stream

Start with a realtime database

You called?

import { AngularFireModule } from 'angularfire2'; export const firebaseConfig = { apiKey: 'PETERBACONDARWINISABEASTINSHEEPSCLOTHING', authDomain: 'rxjsbeastmode.firebaseapp.com', databaseURL: 'https://rxjsbeastmode.firebaseio.com', storageBucket: ''};@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, AngularFireModule.initializeApp(firebaseConfig), ], bootstrap: [AppComponent]})export class AppModule {}

import { AngularFireModule } from 'angularfire2'; export const firebaseConfig = { apiKey: 'PETERBACONDARWINISABEASTINSHEEPSCLOTHING', authDomain: 'rxjsbeastmode.firebaseapp.com', databaseURL: 'https://rxjsbeastmode.firebaseio.com', storageBucket: ''};@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, AngularFireModule.initializeApp(firebaseConfig), ], bootstrap: [AppComponent]})export class AppModule {}

Consume the realtime stream

const remote$ = this.af.database.object('clicker/');remote$ .subscribe(result => this.count = result.ticker);

Update the realtime stream

const remote$ = this.af.database.object('clicker/');Observable.fromEvent(this.getNativeElement(this.btn), 'click') .startWith({ticker: 0}) .scan((acc, curr) => { return { ticker: acc.ticker + 1 }; }) .subscribe(event => remote$.update(event));

const remote$ = this.af.database.object('clicker/');

// Outgoing Observable.fromEvent(this.getNativeElement(this.btn), 'click') .startWith({ticker: 0}) .scan((acc, curr) => { return { ticker: acc.ticker + 1 }; }) .subscribe(event => remote$.update(event));

// Incoming remote$ .subscribe(result => this.message = result.message);

const remote$ = this.af.database.object('clicker/');

// Outgoing ——> Observable.fromEvent(this.getNativeElement(this.btn), 'click') .startWith({ticker: 0}) .scan((acc, curr) => { return { ticker: acc.ticker + 1 }; }) .subscribe(event => remote$.update(event));

// <—— Incoming remote$ .subscribe(result => this.message = result.message);

BEAST MODE TIME!

REALTIME COUNTER

REALTIME SLIDESHOW

REALTIME LOCATION

REALTIME MAP

REALTIME ANNOTATIONS

REALTIME GAME

BUSINESS MODE TIME!

REALTIME SLIDER

But observables are hard!!!

I YOU!

@simpulton

https://egghead.io/courses/step-by-step-async-javascript-with-rxjs

https://egghead.io/courses/introduction-to-reactive-programming

Thanks!

top related