Angular: Using ReplaySubject to Implement Reactive Form Input Handling

Technology
August 7, 2023
5 Mins

Introduction

  • Replay-subjects are a specialised type of Subject in Angular that function as Observable. What is an observable?
  • They retain and deliver older values to the multiple Observers.
  • Moreover, they offer the flexibility to specify the number of values and the time duration for which they should retain those values
Blog hero Image

Example and explanation


import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(3, 500);
// 3: specify upto how much older values need to be stored.
// 500: amount of time upo which values should be stored

// Observer 1:
subject.subscribe(
    {
        next: (v) => console.log(`observerA: ${v}`),
    }
);
subject.next(1);
// 1 will be logged and stored for A

subject.next(2);
// 2 will be logged and stored for A

subject.next(3);
// 3 will be logged and stored for A.
// after this new values will be stored and
// older values will be deleted

subject.next(4);

// Observer 2:
subject.subscribe(
    {
        next: (v) => console.log(`observerB: ${v}`),
    }
);

// since now onwards B also subscribed subject
// hence previous buffered sized values [2,3,4] will also be
// logged for B and now onwards A,B both will subscribe the subject.

subject.next(5);

// Final Output:
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerB: 2
// observerB: 3
// observerB: 4
// observerA: 5
// observerB: 5
  

Advantages

  • History and Caching: It maintains a buffer of previously emitted items, allowing late subscribers to receive historical data.
  • Multi-casting: Events are multicast to multiple subscribers, ensuring all of them receive the same event history.
  • Error Recovery: If one subscriber encounters an error, other subscribers can still receive events from the buffer.
  • Testing: Useful for simulating and verifying event streams in unit tests.
  • Time Travel: Can implement features like "time travel" by controlling buffer size or time window.
  • Consistency: Provides a consistent event stream across subscribers.
  • Complex use case

    In this scenario, we aim to implement a dynamic drop-down with search functionality. The available options in the drop-down will depend on the user's search input. We'll explore two different methods to achieve this:

    • Method without ReplaySubject: In this approach, we will create a drop-down that includes a search input field. As the user types in the search input, the options displayed in the drop-down will be recalculated based on the entered search text. The filtering and updating of options will be handled directly using basic array manipulation and event handling.
    • Method with ReplaySubject: In contrast, the second approach utilises the power of ReplaySubject. We will still create a drop-down with a search input, but now the filtering process and option updates will be managed by a ReplaySubject. As the user interacts with the search input, the ReplaySubject will automatically handle the filtering and notify the drop-down to display the updated options. This method ensures better reactivity and a centralised data source for the filtered options.
    • Case 1:

    Index.html

    
    <mat-select [formControl]="searchControl" [multiple]="true" >
      <mat-option>
        <ngx-mat-select-search
          [(ngModel)]="ngxSearchModel"
          (ngModelChange)="updateOptionsList($event)" >
        <ngx-mat-select-search>
      </mat-option>
      <mat-option *ngFor="let option of filteredOptions">
        {{option}}
      </mat-option>
    </mat-select>
      

    Component.ts

    
    export Component{    
        public searchControl:FormControl = new FormControl();    
        ngxSearchModel="";    
        actualOptions=[    
            "one",    
            "two",    
            "three",    
            "four",    
            "five"    
      ]    
        filteredOptions=[]    
        ngOninit(){    
            this.filteredOptions = [...this.actualOptions];    
        }    
        updateOptionsList(searchItem){    
            if(!searchItem){    
                this.filteredOptions = this.actualOptions;    
            } else {    
                searchItem= searchItem.toLLowerCase();    
                this.filteredOptions = this.actualOptions.filter(option=>option.    
                    toLowerCase().indexOf(searchItem)>=0);    
            }    
      }    
    }
      

    • Case 2:

    HTML

    
    <mat-select [formControl]="searchControl" [multiple]="true" >
      <mat-option>
        <ngx-mat-select-search
        [(ngModel)]="ngxSearchModel"
        (ngModelChange)="updateOptionsList($event)" >
        <ngx-mat-select-search>
      </mat-option>
      <mat-option *ngFor="let option of filteredOptions|async">
        {{option}}
      </mat-option>
    </mat-select>
      

    TS

    
    export AppComponent{   
        public searchControl:FormControl = new FormControl();   
        ngxSearchModel="";   
        actualOptions=[   
            "one",   
            "two",   
            "three",   
            "four",   
            "five"   
      ]   
        filteredOptions:ReplaySubject<string[]> = new ReplaySubject<string[]>(1);   
        ngOninit(){}   
        updateOptionsList(searchItem){   
            if(!searchItem){   
                this.filteredOptions.next(this.actualOptions);   
            } else {   
                searchItem= searchItem.toLLowerCase();   
                this.filteredOptions.next(this.actualOptions.filter(option=>option.   
                    toLowerCase().indexOf(searchItem)>=0));   
            }   
      }   
    }
      

    Now what is the difference and why second approach is better?

    • Reactivity: Automatically notifies subscribers of changes to the filtered options, ensuring data stays up-to-date without manual handling.
    • Centralised data source: Acts as a single source of truth for filtered options, simplifying data access across multiple components.
    • Code organisation: Separates filtering logic from display concerns, leading to cleaner and more maintainable code.
    • Code scalability: Enables easy expansion of the application with coordinated data updates for new subscribers.
    • Efficiency: Avoids unnecessary re-filtration by replaying the last emitted value to new subscribers.
    • Consistency: Ensures all components receive the same filtered options, avoiding data inconsistencies.

    Useful Resources

    Documentation : https://rxjs-dev.firebaseapp.com/api/index/class/ReplaySubject

    This blog post was originally published here.

    FAQ

    Build Apps That Fit Your Business Operations
    Blog CTA ImageGet Started - It's free