Activate Angular Material's autocomplete feature once the user has entered three characters

My goal is to implement an Angular Material Autocomplete feature that only triggers after the user has inputted at least three characters. Currently, I have it set up so that every click in the input field prompts an API call and fetches all the data, which is not ideal.

What I envision and aim to achieve is for the API call to be triggered only when the user has typed in a minimum of three characters, and for the autocomplete options to display only the matching results in the mat-option element.

If my explanation isn't clear enough, please let me know in the comments below.

Below you can find the code snippets:

dashboard.component.html:
<form [formGroup]="dasboardSearchForm" class="dashboardSearch">
  <div class="form-fields">
    <mat-form-field class="dashboard-search">
      <mat-label>Search users...</mat-label>
      <input type="text" matInput [formControl]="myControl" placeholder="search users..." [matAutocomplete]="auto" (change)="onSelected($event)">
      <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
        <mat-option *ngFor="let option of filteredOptions | async" [value]="option" routerLink="/customers/customers/{{option.id}}">
          {{option.name}}
        </mat-option>
      </mat-autocomplete>
    </mat-form-field>
  </div>
</form>

dashboard.component.ts:

  onSelected(event: any) {
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(''),
      switchMap(value => this._filter(value))
    );
    console.log('onSelected: ', this.filteredOptions);
  }

  displayFn(user: AutocompleteCustomers): string {
    return user && user.name ? user.name : '';
  }

  private _filter(value: string) {
    const filterValue = value.toLowerCase();
    return this.dashboardService.getCustomers().pipe(
      filter(data => !!data),
      map((data) => {
        debounceTime(3000); // Placeholder for potential error?
        console.log(data);
        return data.filter(option => option.name.toLowerCase().includes(filterValue));
      })
    )
  }

dashboard.service.ts:

  getCustomers(): Observable<AutocompleteCustomers[]> {
    return this.httpClient.get<AutocompleteCustomers[]>(`${environment.apiUrl}data/customers`);
  }

And here is the model interface for Observables:

export interface AutocompleteCustomers {
  id: string;
  name: string;
}

Answer №1

If you're searching for the right function, look no further than the RxJS filter operator.

const filteredChanges = this.myControl.valueChanges.pipe(
  filter((value) => value.length >= 3)
);

While that may solve one issue, there are multiple other concerns in your code.

  1. The autocomplete won't trigger because the changes observer is only set up after a user selects an option – a catch-22 situation. To remedy this, move the valueChanges observer to the class constructor or assign it directly to a class property.

  2. You need to use debounceTime in a pipe. Your current setup merely creates an observable without utilizing it effectively. Also, debouncing HTTP responses seems odd. Instead, debounce input changes to reduce backend requests and handle responses properly.

Here's a basic outline of how it should be implemented:

class DashboardComponent {

  // ...

  public filteredOptions = this.myControl.valueChanges.pipe(
    filter((value) => value?.length > 3),
    debounceTime(500),
    switchMap(value => this._filter(value))
  );

  private _filter(value: string) {
    const filterValue = value.toLowerCase();
    return this.dashboardService.getCustomers().pipe(
      map((data) => {
        if (!data) {
          return [];
        }
        return data.filter(option => option.name.toLowerCase().includes(filterValue));
      })
    )
  }
}

It might be more efficient to send the query to the server and retrieve matching customers only. If client-side search is necessary, load customers once and reuse the list during searches.

class DashboardComponent {

  // ...

  private customers = [];

  public filteredOptions = this.myControl.valueChanges.pipe(
    filter((value) => value?.length > 3),
    map((value) => {
      const query = value.toLowerCase();
      return this.customers.filter(customer => customer.name.toLowerCase().includes(query))
    }),
  );

  constructor(private dashboardService: DashboardService) {
    this.dashboardService.getCustomers().subscribe((data) => {
      this.customers = data || [];
    });
  }
}

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

What is the best way to create a promise in a basic redux action creator?

My function add does not return any promises to the caller. Here's an example: let add = (foo) => {this.props.save(foo)}; In another part of my application, I want to wait for add() to finish before moving on to something else. However, I know t ...

Encountering: Unable to break down the property 'DynamicServerError' of 'serverHooks' as it does not have a defined value

An error has arisen in a Nextjs app with TypeScript, specifically in the line of my react component which can be found here. This is my inaugural package creation and after several trials, I managed to test it successfully in a similar vite and TypeScript ...

What are the steps to update content by referencing an id in the hash URL?

Currently, I am utilizing the following code snippet to extract an ID from the hash URL: var integer = window.location.hash.match(/\d+/) | 0; alert(integer); However, I have encountered an issue where upon using the back button after modifying the U ...

The specified type `Observable<Pet>&Observable<HttpResponse<Pet>>&Observable<HttpEvent<Pet>>` is not compatible with `Observable<HttpResponse<Pet>>`

I'm currently attempting to integrate the Angular code generated by openapi-generator with the JHipster CRUD views. While working on customizing them for the Pet entity, I encountered the following error: "Argument of type 'Observable & ...

Leveraging both onmouseover and onmouseout for container expansion

My goal is to utilize JavaScript along with the HTML events "onmouseover" and "onmouseout" to create a dynamic container. Essentially, I want the container to act as simply a heading when the mouse is not hovering over it, but expand to display additional ...

Prevent textArea from reducing empty spaces

I am facing an issue with my TextEdit application set to Plain Text mode. When I copy and paste text from TextEdit into a textarea within an HTML form, the multiple spaces get shrunk. How can I prevent the textarea from altering the spacing in the text? T ...

The readyState of Ajax is consistently anything but 4

I have encountered an issue with my JavaScript code. I am running these codes in order to display data based on user input. Despite there being no error connection and the connection happening properly, whenever I enter a name it always goes into the else ...

Creating a personalized event using typescript

I need help with properly defining the schema for an EventObject, specifically what should be included within the "extendedProps" key. Currently, my schema looks like this: interface ICustomExtendedProps { privateNote?: string; publicNote?: string; ...

Applying jQuery .animate() to elements without specifying position: absolute

My goal is to create a website where clicking on an image triggers a dropdown menu. The jQuery method I'm using is as follows: function main() { $('#arrow').click(function() { $('.hidden').animate({ top: &a ...

Monitor the collection for changes before adding an item to the collection

When using ui-select multiple, I am facing an issue where I need to check the collection before ng-model="collection" is updated in order to ensure that the new value is not already present in it. Simply watching the collection does not solve this problem ...

Error encountered while invoking web server method in C# through ajax resulting in a 500 Internal Server Error

Occasionally encountering a 500 internal server error when calling a server method from an AJAX request has left me perplexed. The inconsistency of the issue, sometimes working fine and sometimes not, is baffling. To add to the confusion, no changes were m ...

Dygraphs.js failing to display the second data point

My website features a graph for currency comparison using Dygraphs. Everything was working fine until I encountered this strange issue. https://i.stack.imgur.com/OGcCA.png The graph only displays the first and third values, consistently skipping the seco ...

SCRAM-SERVER-FIRST-MESSAGE: The client's password is required to be in string format

After researching documentation from various sources on a similar issue, I have not been successful in resolving this specific error within my code. throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string') ^ ...

How to prevent unnecessary new instances from being created by the Inject() function in Angular

Can someone please clarify if the inject() function provides different instances of a service? I suspect this might be why my code is not functioning as expected. Let's examine the code snippet below: { path: 'recipes', comp ...

Unable to enhance Request using Typscript in nodejs

In my middleware, I am attempting to enhance the Request by adding a property called user. However, I encountered this error: Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs>' I am trying to u ...

Having trouble retrieving the attribute of an appended element in jQuery?

I am facing an issue where I am unable to retrieve the ID-attribute of an element that has been appended into my HTML. Each time I try, the result is always 'undefined'. How can I resolve this problem? jQuery('form#formular').append(&a ...

Assign the Firebase token to the JavaScript cookie value

Can a cookie store a token value? In my setup with js-cookie, Firebase auth/firestore, and Next.js, I am setting my cookie within the handleUser function like this: const handleUser = async (rawUser) => { if (rawUser) { const user = await fo ...

How can you prevent videos from constantly reloading when the same source is used in multiple components or pages?

How can we enhance loading performance in Javascript, React, or Next JS when utilizing the same video resource across various components on different pages of the website to prevent unnecessary reloading? Is there a method to store loaded videos in memor ...

npm build issues stemming from browserlist configurations

I am encountering an issue with my create-react-app failing to build, showing the following error: ./src/index.css Module build failed: BrowserslistError: Unknown browser query `dead` at Array.forEach (<anonymous>) I have carefully reviewed my ...

Utilize an Angular HttpInterceptor to invoke a Promise

I have an angular HttpInterceptor and I am in need of invoking an encryption method that is defined as follows: private async encrypt(obj: any): Promise<string> { However, I am unsure of how to handle this within the HttpInterceptor: intercept(req ...