Exploring the differences between leveraging RxJS fromEvent and @ViewChild versus using the keyup event and RxJS Subject to detect changes in input

I have a simple component that allows users to search using the values typed into a search box. I've come across two different ways to implement this functionality and both seem to work well.

Method 1 utilizes fromEvent from RxJS to create an observable that captures changes in the input field value by referencing the input element with @ViewChild.

On the other hand, Method 2 captures input field value changes using the keyup event and then passes the new value to an RxJS Subject.

Most of the examples I found on Stackoverflow and other websites favor the use of @ViewChild in scenarios like this. Are there specific reasons to choose one method over the other, or is it simply a matter of personal preference?

METHOD 1

app.component.html

<input type="text" #search placeholder="Search" />
<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

app.component.ts

export class AppComponent implements OnInit {
  @ViewChild('search', { static: true }) search: ElementRef;
  states$: Observable<any[]>;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.states$ = fromEvent(this.search.nativeElement, 'input').pipe(
      map((event: any) => event.target.value),
      debounceTime(500),
      distinctUntilChanged(),
      switchMap((searchText: string) => this.dataService.getData(searchText))
    );
  }
}

METHOD 2

app.component.html

<input
  type="text"
  (keyup)="searchStringChange(getValue($event))"
  placeholder="Search"
/>
<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

app.component.ts

export class AppComponent implements OnInit {
  states$: Observable<any[]>;
  private searchText$ = new Subject<string>();

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.states$ = this.searchText$.pipe(
      debounceTime(500),
      distinctUntilChanged(),
      tap({ next: (searchText: string) => console.log(searchText) }),
      switchMap((searchText: string) => this.dataService.getData(searchText))
    );
  }

  searchStringChange(userInput: string) {
    this.searchText$.next(userInput);
  }

  getValue(event: Event): string {
    return (event.target as HTMLInputElement).value;
  }
}

Answer №1

Where possible, I try to steer clear of using @ViewChild. The rationale behind this is that the code becomes simpler when the component doesn't have to handle template-related issues. Handling template concerns introduces complexities such as ensuring the targeted element is currently in the DOM (it might be hidden by ngIf) and accessing it within the appropriate Angular lifecycle hook.

By adopting the "subject approach", the component only needs to focus on the data while the template can effortlessly display the data based on relative events.

It's worth noting that in your "Method 2", you don't necessarily need to utilize ngOnInit; you can easily initialize your observable within the class itself:

export class AppComponent {

  private searchText$ = new Subject<string>();

  states$: Observable<any[]> = this.searchText$.pipe(
    debounceTime(500),
    distinctUntilChanged(),
    tap(searchText => console.log(searchText)),
    switchMap(searchText => this.dataService.getData(searchText))
  ); 

  constructor(private dataService: DataService) {}

  searchStringChange(userInput: string) {
    this.searchText$.next(userInput);
  }

  getValue(event: Event): string {
    return (event.target as HTMLInputElement).value;
  }
}

Furthermore, there's a third method for dealing with this scenario, especially since you're working with a form input. You can leverage Angular's Reactive Forms, where the FormControl object offers a handy valueChanges observable that triggers whenever the input value changes:

export class AppComponent {

  searchText = new FormControl('');

  states$ = this.searchText.valueChanges.pipe(
    debounceTime(500),
    distinctUntilChanged(),
    tap(searchText => console.log(searchText)),
    switchMap(searchText => this.dataService.getData(searchText))
  ); 

  constructor(private dataService: DataService) {}

}
<input type="text" [formControl]="searchText" placeholder="Search" />

<li *ngFor="let state of states$ | async">
  {{ state.text }}
</li>

Feel free to explore this StackBlitz demo for a hands-on experience.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a more effective method to return a response apart from using a redundant function?

function unnecessaryFunction(){ let details: SignInDetails = { user: user, account: account, company: company }; return details; } I am being told that the details value is unnecessary. Is there ...

Angular 2 validation issue not functioning as anticipated

Here is a validator function that I have: export const PasswordsEqualValidator = (): ValidatorFn => { return (group: FormGroup): Observable<{[key: string]: boolean}> => { const passwordCtrl: FormControl = <FormControl>group.contr ...

Creating objects utilizing the asynchronous map method in JavaScript for data iteration

Within an async Immediately Invoked Function Expression (IIFE) in this JavaScript code, the goal is to: 1) read a JSON file, 2) extract multiple RSS feed URLs from the data, 3) fetch and parse information from those feeds, and generate an object containing ...

Uncovering the JavaScript Linked to a Specific Element

Is there a way to link directly to the JavaScript controlling a particular HTML element, unlike the typical "inspect element" approach which is limited to HTML and CSS? ...

What is the best way to use "ngModel #something="ngModel"" on a dynamically generated form field within a template-driven form?

Encountering an error when submitting my form indicating it's not defined. After some research, I found out that I needed to include ngModel #something="ngModel in each input field for the form to recognize the inputs, similar to the example provided ...

Which is the best option: saving data-containing objects in a separate .js file or in the database?

As I work on developing an RPG game web app, I am looking to store predefined details regarding skills and traits. For example, an array of objects that define attributes like "Battle-hardened grants +2 strength" and "Ruthless past results in -1 social." ...

executing a series of jQuery ajax calls iteratively

In my Web Application, I have implemented "Spatial Selection" on a map that allows users to select multiple streets. Once selected, each street is assigned a unique Street Id through the use of a "Selection Handler". The next step in the process involves ...

A TypeScript/Cypress command that restricts input parameters to specific, predefined values

I have a Cypress command with a parameter that I want to restrict so it only accepts certain values. For example, I want the columnValue to be limited to 'xxx', 'yyy', or 'zzz'. How can I achieve this? Cypress.Commands.add( ...

Testing in NodeJS - revealing the application

Currently, I am in the process of testing my NodeJS application using supertest. To make my app accessible within test.js at the end of app.js, I have exposed it. /////////////////// // https options var options = { key: fs.readFileSync("./private/key ...

How can PHP be used to create Javascript?

Is there a specific library or tool tailor-made for PHP programmers to seamlessly transition their logic into Javascript? For example: $document = new Document($html); $myFoo = $document->getElementById("foo"); $myFoo->value = "Hello World"; Which ...

Assign a variable to the result of ajax that remains unchanged until it is specifically invoked

I am currently working on the final part of my radio script, specifically focusing on the "last song" section. My goal is to retrieve a URL from an external PHP script, play the song, and once the song ends, I want to set a global variable equal to the cur ...

Warning: Unhandled Promise Rejection - Alert: Unhandled Promise Rejection Detected - Attention: Deprecation Notice

Encountering the following error message: (node:18420) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'name' of undefined at C:\Users\ohrid\Desktop\backend2\routes\categories.js:27:24 at Layer.han ...

Having trouble establishing a connection between socket.io client and server

I am currently attempting to establish a connection between a client and server using express and node.js. Unfortunately, I am encountering difficulties in connecting to the server. The tutorial I followed (available at https://socket.io/get-started/chat) ...

Implementing yup validation to check if a form field begins with the same value as another input field

In my form, I have two fields - carriercode and billnum. I want to ensure that the value in the billnum field always starts with the carriercode as a prefix. For example, if carriercode=ABCD, then billnum should start with ABCD followed by any combination ...

Transform the dataUrl into a blob and send it via ajax request

I am currently utilizing the imgly image cropper plugin with some modifications for my application. It has the ability to convert an image into a dataUrl and produce the image as a base64 image which can then be saved as a jpeg. My objective is to adjust ...

Node.js Error: The requested URL cannot be found

I have encountered an issue in my Node project where I am getting a 'Cannot GET/' error when trying to open localhost on port 8081. I suspect that the problem lies in correctly reading the HTML file, but I'm not entirely sure. var express = ...

Implementing dynamic webpage updates based on dropdown list selections

In my webpage, I am displaying a list of movies fetched from an API. The issue I am facing is related to sorting the movies based on user selection. When I uncomment one of the sort methods in my code, it works as intended. What I want to achieve is that ...

Looking for a way to determine in JavaScript whether the last item in an array is a number or not? Keep in mind that the last element could be either a number or a string, depending

console.log("case 1") var event = "Year 2021"; console.log(typeof(parseInt(event.split(" ").pop())) === "number"); console.log("case 2") var event = "Year mukesh"; console.log(typeof(parseInt(event.split(" ").pop())) === "number"); console.log("case 3") va ...

Problem arises with table nth-child selector following the addition of grid-gap

Our current table looks like this: https://i.sstatic.net/eUyBB.png The first row has a background color and the second row is white - which is correct. Now I would like to align the attributes to the right. I added the following code to the table: .table ...

Passing this as a function parameter in Jquery

HTML <input type="button" id="1" class="add" value="+" title="Add" onclick="set(this);"/> JS function set(obj){ alert(obj.id); } The code snippet provided above seems to have an issue. My Requirement I am looking for a solution that allows ...