RxJS

Featured image

RxJS and Observables

How are you doing? As promised, we are back with more RxJS and Observables magic! As always I’m bringing the official docu here

Today’s post is dedicated to experiences and some scenarios with different combinations where we will be applying and deepening these concepts.

Understanding RxJS](https://a.storyblok.com/f/42126/1a01e12825/understanding-rxjs.png)

What is RxJS and why is it so important?

RxJS, or Reactive Extensions for JavaScript, is a library that allows you to apply reactive programming in JavaScript, basically to deal with asynchronous operations, such as event handling, HTTP requests and real-time updates. The approach to Observables provides a structured and powerful way to handle data streams that change over time. If you didn’t read the previous posts, I suggest you take a look at here

Experiences and Scenarios

Search - Material Design](https://lh3.googleusercontent.com/JSzdxGsx_JZDhhXbuatxStSpjpQAjXHHDBoIDThnkVCxGn7PaTDkZrZEElfsbQ5yvemI1J_Nb2Vsw8bCRYxUyB5r3wXxiDCGlMZZug=w1064-v0)

Let’s imagine an app with an instant search bar (Google?). The idea is that as users type into this bar, what we want is to show suggestions based on what they are typing. This is where our wonderful RxJS comes into play.

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

Explanation:

When users type in the search bar, we want to provide relevant suggestions in real time. For this, we first create a form control called searchControl that represents the search field. We use a series of RxJS operators, such as debounceTime (hold the fuse, it gives us a delay time between entries depending on the value we specify for it), distinctUntilChanged (with this operator we avoid consecutive duplicates, that is, consecutive executions even when the value does not change) and switchMap (asynchronous request handling, I explain it in details in this post) in the valueChanges event sequence of the control. With all this we are now able to issue requests to the server for suggestions based on the current user input.

Exercise 2: Improving the User Experience !

Comment ajouter un défilement infini (Infinite Scroll) à votre site WordPress ? We can refer to certain complex interactions, such as loading content based on scroll position, which can significantly improve the user experience. Again, we can leverage the magic of Observables which are ideal for detecting these scroll events and loading additional data efficiently.

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

Explanation:.

In this exercise, when the user scrolls to the end of the page, we want to load more elements automatically. We use an Observer on the scroll event (@HostListener) to detect when the user has reached the end. If the user has reached the end of the page, we call a DataService to load more data. New elements are added to existing ones, allowing for a seamless and uninterrupted scrolling experience.

Exercise 3: Real-Time Updates with WebSockets

What Are WebSockets And How Do Hackers Exploit Them?](https://lh5.googleusercontent.com/uAOxVcieeqpGTtCP8A8QAOR9YOg3Nj-_HtJhckRr52qMWY1Inbzq27ekK3wy3hDXqd_51Q585dy2ups2vUiKU6ne0OjAQkJhvMtAxQZuCyW64QXNieyKgSDsWNUTWEL2LRtM2etp=s0) Let’s imagine a real-time chat app, where it is crucial that updates are reflected instantly. Observables become a fundamental resource for handling incoming messages and keeping the user interface up to date.

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

Explanation:

In this scenario, we create a WebSocketService service to handle the connection to the WebSocket server. The subscription to the messages Watcher allows us to be aware of incoming messages. When a new message arrives, it is added to messages, which automatically updates the chat view. Btw, WebSocket deserves its own post.

Exercise 4: Tracking and task management

Top 20 To-Do List Apps You'll Want to Look Into in 2023 | Infinity Let’s assume a case where users can add and complete tasks. In addition, we want to keep track of them. Let’s see the example

    import { from, Subject } from 'rxjs';
    import { tap, scan, filter } from 'rxjs/operators';
    
    const addTaskSubject = new Subject();
    
    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 })
    );
    
    from(['Drink beer', 'Drink gin']).subscribe(task => {
      addTaskSubject.next(task);
    });
    
    setTimeout(() => {
      addTaskSubject.next('Drink beer');
    }, 2000);
    
    task$.subscribe(stats => {
      console.log('Estadísticas:', stats);
    });

Explanation:

In this case, we create a Subject called addTaskSubject to represent the flow of aggregated tasks. We are also going to use an operator called scan to accumulate the tasks in an array and then a tap to display the tasks in the console each time a task is added.
We use scan again but this time together with filter to calculate the statistics of completed tasks based on the task array. The statistics are accumulated in an object containing the total number of tasks and the number of completed tasks.
We simulate the aggregation of tasks using from([['Drink birra', 'Drink gin']).subscribe(task => { ... }). After 2 seconds, we simulate the completion of a task by calling addTaskSubject.next('Drink beer'). Finally, we subscribe to the task$ sequence to receive updates on tasks and statistics.

Conclusion

RxJS and Observables can be applied in situations to create dynamic interactions, handle real-time data flows, and improve the user experience in applications. Each case shows how RxJS enables complex problems to be addressed in a structured and effective way, which contributes to creating more responsive and engaging apps.

Happy coding!

Beer!