RxJS

Featured image

RxJS y Observables

Buenasss como andan? Lo prometido es deuda y volvemos con más magia RxJS y Observables! Como siempre acerco la docu oficial acá

El post de hoy va dedicado a experiencias y algunos escenarios con diferentes combinaciones donde vamos a estar aplicando y profundizando estos conceptos.

Understanding RxJS

¿Qué es RxJS y por qué es tan importante?

RxJS, o Reactive Extensions for JavaScript, es una biblioteca que permite aplicar programación reactiva en JavaScript, basicamente para lidiar con operaciones asíncronas, como la gestión de eventos, las solicitudes HTTP y las actualizaciones en tiempo real. El enfoque que se le da a los Observables ofrece una manera estructurada y potente de manejar flujos de datos que cambian con el tiempo. Si no leiste los posts anteriores te sugiero una pasadita por acá

Experiencias y Escenarios

Ejercicio 1: Búsqueda Instantánea

Search - Material Design

Imaginemos una app con una barra de búsqueda instantánea (Google?). La idea es que a medida que los usuarios escriben en esta barra, lo que queremos es mostrar sugerencias basadas en lo que va escribiendo. Aquí es donde entra en juego nuestro maravilloso RxJS.

    import { Component } from '@angular/core';
    import { FormControl } from '@angular/forms';
    import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
    import { ProductService } from './product.service';
    
    @Component({
      selector: 'app-product-search',
      templateUrl: './product-search.component.html',
    })
    export class ProductSearchComponent {
      searchControl = new FormControl();
      suggestions: string[] = [];
    
      constructor(private productService: ProductService) {
        this.searchControl.valueChanges
          .pipe(
            debounceTime(300),
            distinctUntilChanged(),
            switchMap((query: string) => this.productService.getSuggestions(query))
          )
          .subscribe((suggestions: string[]) => {
            this.suggestions = suggestions;
          });
      }
    }` 

Explicación:

Cuando los usuarios escriben en la barra de búsqueda, queremos proporcionar sugerencias relevantes en tiempo real. Para esto, primero creamos un control de form llamado searchControl que representa el campo de búsqueda. Usamos una serie de operadores RxJS, como debounceTime (aguanta la mecha, nos da un tiempo de retraso entre entradas según el valor que especifiquemos para ello), distinctUntilChanged (con este operador evitamos duplicados consecutivos, es decir, ejecuciones consecutivas incluso cuando el valor no cambia) y switchMap (manejo de solicitudes asincrónicas, te lo explico en detalles en este post) en la secuencia de eventos del valueChanges del control. Con todo esto estamos en condiciones de emitir solicitudes al servidor para obtener sugerencias basadas en la entrada actual del usuario.

Ejercicio 2: Mejorando la Experiencia de Usuario

Comment ajouter un défilement infini (Infinite Scroll) à votre site  WordPress ? | Communauté WooCommerce - WordPress France Podemos referirnos a ciertas interacciones algo complejas, como por ejemplo, cargar contenido según la posición de desplazamiento, lo cual puede mejorar significativamente la experiencia del usuario. Nuevamente, podemos aprovechar la magia de los Observables que resultan ideales para detectar estos eventos de desplazamiento y cargar datos adicionales de manera eficiente.

    import { Component, HostListener } from '@angular/core';
    import { DataService } from './data.service';
    
    @Component({
      selector: 'app-infinite-scroll',
      templateUrl: './infinite-scroll.component.html',
    })
    export class InfiniteScrollComponent {
      items: any[] = [];
    
      constructor(private dataService: DataService) {}
    
      @HostListener('window:scroll', ['$event'])
      onScroll() {
        const scrolledToBottom = window.innerHeight + window.scrollY >= document.body.offsetHeight;
        if (scrolledToBottom) {
          this.dataService.loadMoreData().subscribe((newItems: any[]) => {
            this.items.push(...newItems);
          });
        }
      }
    }` 

Explicación:

En este ejercicio, cuando el usuario se desplaza al final de la página, queremos cargar más elementos automáticamente. Utilizamos un Observador en el evento de desplazamiento (@HostListener) para detectar cuándo el usuario ha llegado al final. Si el usuario ha llegado al final de la página, llamamos a un servicio DataService para cargar más datos. Los nuevos elementos se agregan a los existentes, lo que permite una experiencia de desplazamiento continuo y sin interrupciones.

Ejercicio 3: Actualizaciones en Tiempo Real con WebSockets

What Are WebSockets And How Do Hackers Exploit Them? Imaginemos una app de chat en tiempo real, en donde es crucial que las actualizaciones se reflejen instantáneamente. Los Observables se convierten en un recurso fundamental para manejar mensajes entrantes y mantener actualizada la interfaz de usuario.

    import { Component } from '@angular/core';
    import { WebSocketService } from './websocket.service';
    
    @Component({
      selector: 'app-chat',
      templateUrl: './chat.component.html',
    })
    export class ChatComponent {
      messages: string[] = [];
    
      constructor(private wsService: WebSocketService) {
        this.wsService.connect();
        this.wsService.messages.subscribe((message: string) => {
          this.messages.push(message);
        });
      }
    
      sendMessage(message: string) {
        this.wsService.sendMessage(message);
      }
    }` 

Explicación:

En este escenario, creamos un servicio WebSocketService para manejar la conexión con el servidor de WebSocket. La suscripción al Observador messages nos permite estar al tanto de los mensajes entrantes. Cuando un nuevo mensaje llega, se agrega a messages, lo que actualiza automáticamente la vista de chat. Btw, WebSocket amerita su propio post.

Ejercicio 4: Seguimiento y gestión de tareas

Top 20 To-Do List Apps You'll Want to Look Into in 2023 | Infinity Supongamos un caso donde los usuarios pueden agregar y completar tareas. Además, queremos llevar un registro de las mismas. Veamos el ejemplo

    import { from, Subject } from 'rxjs';
    import { tap, scan, filter } from 'rxjs/operators';
    
    // Flujo de tareas agregadas
    const addTaskSubject = new Subject();
    
    // Stream de tareas con estadísticas
    const task$ = addTaskSubject.pipe(
      scan((tasks, task) => [...tasks, { name: task, completed: false }], []),
      tap(tasks => console.log('Tareas:', tasks)),
      scan((stats, tasks) => {
        const completedTasks = tasks.filter(task => task.completed);
        return { total: tasks.length, completed: completedTasks.length };
      }, { total: 0, completed: 0 })
    );
    
    // Simulamos el agregado de "tareas"
    from(['Tomar birra', 'Tomar gin']).subscribe(task => {
      addTaskSubject.next(task);
    });
    
    // Simulación de completar una tarea
    setTimeout(() => {
      addTaskSubject.next('Tomar birra');
    }, 2000);
    
    // Suscripción para recibir actualizaciones de tareas y estadísticas
    task$.subscribe(stats => {
      console.log('Estadísticas:', stats);
    });

Explicación:

En este caso, creamos un Subject llamado addTaskSubject para representar el flujo de tareas agregadas. Tambien vamos a usar un operador llamado scan para acumular las tareas en un arreglo y luego un tap para mostrar las tareas en la consola cada vez que se agrega una tarea.
Volvemos a usar scan pero esta vez junto con filter para calcular las estadísticas de tareas completadas en función del arreglo de tareas. Las estadísticas se acumulan en un objeto que contiene el número total de tareas y el número de tareas completadas.
Simulamos el agregado de tareas usando from(['Tomar birra', 'Tomar gin']).subscribe(task => { ... }). Después de 2 segundos, simulamos la finalización de una tarea llamando a addTaskSubject.next('Tomar birra'). Finalmente, nos suscribimos a la secuencia task$ para recibir actualizaciones sobre las tareas y las estadísticas.

Conclusión

RxJS y los Observables pueden aplicarse en situaciones para crear interacciones dinámicas, manejar flujos de datos en tiempo real y mejorar la experiencia del usuario en aplicaciones. Cada caso muestra cómo RxJS permite abordar problemas complejos de manera estructurada y efectiva, lo que contribuye a crear apps más receptivas y atractivas.

Happy coding!

Beer!