Wednesday, March 15, 2017

The best talk I saw at MVPMIX was Lukas Ruebbelke on Observables.

A picture of an athlete doing the Fosbury Flop was advertised in the first slide to emphasize a point. This approach to high jumping was introduced to the world by a guy named Dick Fosbury in a 1960s Olympic Games. Mr. Fosbury would win the gold for his mad approach to high jumping which looked unlike anything anyone had seen before but yet was wildly effective. In this approach to jumping you run up and then twist to throw yourself over the bar with your back, bent in a U shape, to the bar and then flop down to the mat. Everyone else had dove over with their belly to the bar up until then. Crazy! Now the Fosbury Flop is the standard and the norm. What does this have to do with Observable? Well, Observable may seem scary and strange to you at first. You may think that it's just the default way to get asynchronous stuff in modern AngularJS, but this is really a simplification. There's more to it that you don't want to understand and you sense it, don't you. This will hurt a little, but only a little. Once you ramp up, you'll be glad that you did. You know that too, don't you? Don't be that guy laughing at Dick Fosbury while becoming a dinosaur. Lukas' suggested the seemly backwards thing about Observables, akin to the Fosbury Flop, was that instead of giving an input to get an output, as you might expect, you will, in this paradigm, get an output and then transform it into an input. There may be a bit of casting, transformations, and other hijinks along the way. Observables tend to take the shape of an event/subscribe sandwich, with an event drinking asynchronously from an endpoint and then, downstream in code (in what is the same line of code with some dot-dot-dot chaining going on), a subscribe handing off to code beyond the Observable implementation. Between these two points (on the same long line of code) one may have operators, the meat of our sandwich and that which makes Observable something more than just let's-call-an-API in nature. There is the opportunity for quite a bit of heavy lifting that may occur not after the plane has landed so much as while the individual passengers are parachuting in.

@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);
}

 
 

Above and below are examples.

@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;
}

 
 

Another analogy: At a soda fountain at a restaurant where you may do the free refill thing (awesome) the stuff that comes into your paper cup is a mixture of two streams, one of carbonated water and the other of syrup. Similarly, you may combine to streams in Observable.

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);
}

 
 

Tweening for animations...

@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))
}

 
 

This last example shows how to collect/organize the things that stream in into pairs. I'm not sure that the dot-dot-dot is supposed to be taken literally below. This example, and all of the others here come directly from the slides Lukas Ruebbelke had.

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]);
}

 
 

Observables are looped in with the RxJS ReactiveX library. They are a better alternative to the Promise of TypeScript. I had heard that they were better due to the fact that they could stream in a collection progressively (and have the individual items hydrate asynchronously) when talking to a web socket. That's clearly not the only reason.

No comments:

Post a Comment