Skip to content

Instantly share code, notes, and snippets.

@jcham
Last active January 4, 2018 07:21
Show Gist options
  • Save jcham/007c025becb6a3013c80f377e96162b1 to your computer and use it in GitHub Desktop.
Save jcham/007c025becb6a3013c80f377e96162b1 to your computer and use it in GitHub Desktop.

A Gentle Introduction to Rx (via RxJS)


What we want:

+-------+       +--------+          +--------+
| Start |       | Cancel |          |Confirm?|
+---+---+       +---+----+          +---+----+
    |               |                   ^
    |               |                   |
    |               +---------------+   |
    |               v               v   |
    |          +----+-----+       +-+---+----+
    |          |          |       |          |
    +--------> | doBitmap +-----> | doDialog +------>
               |          |       |          |
               +----------+       +----------+

JSFiddle Setup

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
class Bitmap {
  constructor(i) {
    this.i = i;
  }
}

Generic Observable template

function doBitmap() {

  return Rx.Observable.create(observer => {

    // Do stuff, send stuff 

    return () => {
      console.log('doBitmap disposed/unsubscribed!');
    }
  });
  
}

Generic Observable template

function doBitmap() {

  return Rx.Observable.create(observer => {

    // Do stuff, send stuff 
    observer.next();          //*
    observer.complete();      //*

    return () => {
      console.log('doBitmap disposed/unsubscribed!');
    }
  });
  
}

Fill in asynchronous functionality

function doBitmap() {

  return Rx.Observable.create(observer => {
    console.log('doBitmap started...');
    const bitmap = new Bitmap(0);
    
    const timeout = setInterval(() => {
      bitmap.i += 1;
      console.log('doBitmap progress', bitmap.i);
      if (bitmap.i >= 4) {
        observer.next(bitmap);    //*
        observer.complete();      //*
        cleanup();
      }
    }, 1000);

    function cleanup() { clearTimeout(timeout); }

    return () => {
      console.log('doBitmap disposed!');
      cleanup();                 //*
    }
  });  
}

doBitmap();

doBitmap().subscribe(bitmap => { console.log('doBitmap ->', bitmap); });

Look, no Objects Ma!

No Objects/Classes == Functional Reactive Programming


Create the buttons

<button id="startButton">
  Start
</button>
<button  id="cancelButton">
  Cancel
</button>

2 ways of getting events: (1) Create an observable

const start$ = Rx.Observable.create(observer => {
  return () => {
  };
});

(1) Create an observable

const start$ = Rx.Observable.create(observer => {

  const button = document.getElementById('startButton');
  const eventListener = _ => {
    observer.next(1);
  }
  button.addEventListener('click', eventListener);

  return () => {
    button.removeEventListener('click', eventListener);
  };
});

start$.subscribe(() => console.log('START'));

OR... (2) Create a Subject

<button onClick="cancelButton();">
  Cancel
</button>
const cancel$ = new Rx.Subject();
window.cancelButton = () => {
  cancel$.next();
}
cancel$.subscribe(() => console.log('CANCEL'));

Hooking it all up together: Try #1

start$.map(_ => doBitmap())
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

flatMap

start$.flatMap(_ => doBitmap())
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

switchMap

start$.switchMap(_ => doBitmap())
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

How to Cancel

const sub = start$.switchMap(_ => doBitmap())
                  .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));
      
cancel$.subscribe(_ => sub.unsubscribe());

How to Cancel: Use takeUntil

start$.switchMap(_ => doBitmap())
      .takeUntil(cancel$)
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

Dialog as an Asynchronous Observable

function doDialog() {
  return Rx.Observable.create(observer => {

    return () => {
    };
  })
}

Dialog as an Asynchronous Observable

function doDialog() {
  return Rx.Observable.create(observer => {    
    console.log('doDialog starting...');
    
    let button = document.createElement('button');
    button.innerHTML = "Confirm?";
    button.onclick = _ => {
      console.log('Dialog click');
      observer.next(1);               //*
      observer.complete();            //*
      cleanup();
    };
    document.body.appendChild(button);

    function cleanup() { if (button) { button.remove() } }

    return () => {
      console.log('doDialog disposed!');
      cleanup();
    };
  })
}

doDialog();
doDialog().subscribe(x => console.log('CONFIRM', x));

Hooking it up (not quite)

start$.switchMap(_ => doBitmap())
      .map(bitmap => doDialog())        //*
      .takeUntil(cancel$)
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

Hooking it up (ok)

start$.switchMap(_ => doBitmap())
      .flatMap(bitmap => doDialog())    //*
      .takeUntil(cancel$)
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));

Sort of works

Cancel clears button, but:

  1. Cannot restart
  2. Cancel is wonky

Better: Composing Observables

start$.switchMap(_ =>                   //*
         doBitmap()
           .flatMap(bitmap => doDialog())
           .takeUntil(cancel$)
         )           
      .subscribe(bitmap => console.log('Final Bitmap: ', bitmap));
<!DOCTYPE html>
<html>
<head>
<title>Remark</title>
<meta charset="utf-8">
<style>
@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz);
@import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic);
@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic);
body { font-family: 'Droid Serif'; }
h1, h2, h3 {
font-family: 'Yanone Kaffeesatz';
font-weight: normal;
}
.remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; }
</style>
</head>
<body>
<textarea id="source">
</textarea>
<script src="https://github.com/cardinalblue/remark/releases/download/v0.15.0_beta1/remark.min.js">
</script>
<script>
var slideshow = remark.create({
sourceUrl: './_RxJS Lesson 1.md',
highlightLines: true,
highlightSpans: /(.*)(\/\/\*)/g,
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment