Tutorial de ReactiveX – Ampliando los observables

Ampliando los observables

En la entrada anterior del tutorial de ReactiveX realizamos una breve introducción a la librería y creamos algunos ejemplos sencillos.

A continuación mostraremos algunas de las múltiples funciones que ofrece ReactiveX para crear observables.

  • of():  Se crean observables a partir de strings, arrays, objetos, boolean, etc…, emitiendo cada argumento secuencialmente y de forma síncrona hasta que recorra todos los argumentos.
    • import { Observer, of } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          let res = null
          res = typeof response === 'object' || Array.isArray(response) ? JSON.stringify(response) : response;
          if (typeof response === 'function') {
            res();
          }
          console.log(`Response: ${res}`);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const obsNumber$ = of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
      const obsString$ = of('a', 'b', 'c', 'name', 'age');
      const obsArray$ = of([1, 2, true, 'a'], ['a', 'b'], [true, false]);
      const obsObject$ = of({ a: 1, b: 2 },{ a: { b: { c: 1} } });
      const obsFunc$ = of(() => alert('1'), () => alert('2'));
      
      obsNumber$.subscribe(observerCfg);
      obsString$.subscribe(observerCfg);
      obsArray$.subscribe(observerCfg);
      obsObject$.subscribe(observerCfg);
      obsFunc$.subscribe(observerCfg);
  • from(): Se comporta igual que la función of(), salvo que from() emite los elementos que están dentro del argumento.
    • // Importamos los observables de la librería ReactiveX
      import { from, Observer, of } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          let res = null
          res = typeof response === 'object' || Array.isArray(response) ? JSON.stringify(response) : response;
          if (typeof response === 'function') {
            res();
          }
          console.log(`Response: ${res}`);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const observerFrom$ = from('abcdefgh');
      const observerOf$ = of('abcdefgh');
      
      observerFrom$.subscribe(observerCfg); 
      // Response: a, Response: b, Response: c, Response: d, Response: e, Response: f, Response: g, Response: h
      // Emite cada elemento dentro del argumento
      
      observerOf$.subscribe(observerCfg); 
      // Response: abcdefgh
      // Emite el argumento
  • fromEvent(): Podemos crear observables que emiten cuando los eventos de Javascript son ejecutados, encargándose también de cancelar la escucha del evento cuando se cancele la suscripción del observable.
    • // Importamos los observables de la librería ReactiveX
      import { Observer, fromEvent } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          console.log('Response:');
          console.log(response?.key || response?.x || '');
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const mouseEvent$ = fromEvent<MouseEvent>(document, 'click');
      const keybEvent$ = fromEvent<KeyboardEvent>(document, 'keydown');
      
      mouseEvent$.subscribe(observerCfg); // Response: 300 -> Coordenada X del evento mouse
      keybEvent$.subscribe(observerCfg);  // Response: a -> key del evento keyboard
  • range(): Creamos un observable que emite un listado de números ascendentes comprendidos entre la posición inicial y un número de veces determinado.
    • // Importamos los observables de la librería ReactiveX
      import { asyncScheduler, Observer, range } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          console.log('Response:', response);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      // Síncrono
      console.log('IN RANGE A');
      const rangeA$ = range(1, 20);  // 1,2,3....,20
      const subscriptionA = rangeA$.subscribe(observerCfg);
      console.log('OUT RANGE A');
      
      // Síncrono
      console.log('IN RANGE B');
      const rangeB$ = range(20);  // 0,1,2....,19
      const subscriptionB = rangeB$.subscribe(observerCfg);
      console.log('OUT RANGE B');
      
      // Forzamos asíncrono
      console.log('IN RANGE C');
      const rangeC$ = range(-10, 20, asyncScheduler);  // -10,-9,-8....,9
      const subscriptionC = rangeC$.subscribe(observerCfg);
      console.log('OUT RANGE C');
  • interval(): Creamos un observable que emite un listado de números ascendentes con un intervalo entre cada emisión que podemos indicar.
    • // Importamos los observables de la librería ReactiveX
      import {  Observer, interval } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          console.log('Response:', response);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const interval$ = interval(2000); // Emitimos un valor cada 2 seg.
      
      interval$.subscribe(observerCfg);
  • timer(): Es similar a interval(), pero a timer() ademas nos permite retrasar el inicio de la emisión del listado los números.
    • // Importamos los observables de la librería ReactiveX
      import {  Observer, timer } from 'rxjs';
      
      const observerCfg: Observer<any> = {
        next: response => {
          console.log('Response:', response);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const date = new Date();
      const timer$ = timer(2000);  // Espera 2 segundos y emite un valor
      const timerInterval$ = timer(2000, 500);  // Espera 2 segundos y emite un valor cada 0.5 seg.
      const alarm$ = timer(date);  // Emite en una fecha determinada
      
      timer$.subscribe(observerCfg);
      timerInterval$.subscribe(observerCfg);
      alarm$.subscribe(observerCfg);
  • ajax(): Creamos un observable que realiza peticiones Ajax y nos devuelve la respuesta de la petición.
    • // Importamos los observables de la librería ReactiveX
      import { Observer } from 'rxjs';
      import { ajax } from 'rxjs/ajax';
      
      const observerCfg: Observer<any> = {
        next: response => {
          console.log('Response:', response);
        },
        error: err => {
          console.log(`Error: ${err}`);
        },
        complete: () => {
          console.log(`Finalizado`);
        }
      };
      
      const url = 'https://jsonplaceholder.typicode.com/users'; 
      const ajax$ = ajax(url);
      const subscription = ajax$.subscribe(observerCfg);

Con los ejemplos anteriores hemos ampliado el conocimiento en ReactiveX, pero sería interesante poder aplicar lo que hemos aprendido hasta ahora en un ejemplo mas útil.

Actualizando el DOM usando ReactiveX

El siguiente ejemplo es muy sencillo pero nos da una pista hasta donde podemos llegar con ReactiveX.

index.html

<body>
    <h1>Turorial ReactiveX</h1>
    <input type="text" placeholder="name" data-reactive="keyup" data-source="name" />
    <input type="number" placeholder="age" data-reactive="keyup" data-source="age" />
    <div>
      <div data-model="name"></div>
      <div data-model="name"></div>
      <div data-model="name"></div>
      <div>
        <input type="text" readonly data-model="name" />
      </div>
      <div data-model="age"></div>
      <div data-model="age"></div>
      <div>
        <input type="text" readonly data-model="age" />
      </div>
    </div>
    <script src="bundle.js"></script>
</body>

index.ts

import { fromEvent, Observer } from 'rxjs';

const keyboardCallback: Observer<any> = {
  next: response => {
    // Obtenemos el modelo que necesitamos actualizar
    const source = response.target.dataset.source;

    // Buscamos todos los modelos que necesitamos actualizar
    const updateModels = document.querySelectorAll(`[data-model="${source}"]`);

    // Empezamos a recorrer cada elemento y actualizamos su contenido
    updateModels.forEach(item => {
      const hasHTML = ['DIV'].includes(item.tagName);
      item[hasHTML ? 'innerHTML' : 'value'] = response.target.value;
    });
  },
  error: err => {},
  complete: () => {}
};
// Obtenemos todos los elementos que tenga el siguiente atributo
const keyboardEvent = document.querySelectorAll('[data-reactive="keyup"]');

// Creamos un observable sobre los elementos anteriores
const keyboardEvent$ = fromEvent<KeyboardEvent>(keyboardEvent, 'keyup');

// Nos suscribimos, y se ejecuta cada vez que presionamos y soltamos una tecla
keyboardEvent$.subscribe(keyboardCallback);

 

En la próxima entrada del tutorial continuaremos aprendiendo mas sobre esta fantástica librería, y recordad que desde aquí podéis acceder a todo el código del tutorial.