Implement Smart Search Using Fuzzy Search Logic with Syncfusion Autocomplete Control

Fuzzy search is a technique that finds approximate matches for the search string or characters; even when it does not exactly match the entered information.

For example, if a user searches for “Node,” they will get an unmatched result when they type “Nede.” But a fuzzy search returns results containing both “Node” and “Nede” words.

Fuzzy search logic is one of the basics of spell checker and was implemented using various algorithms like Levenshtein distance, Damerau-Levenshtein distance, Bitap algorithm, Smith-Waterman algorithm, and others. It can be used on any search engine algorithms and has already been implemented in several popular search engines (Google, Yahoo, Bing, etc.) and databases.

In this blog, you are going to learn how to achieve fuzzy searches using our Essential JS 2 autocomplete component and Fusejs.

How to integrate fuzzy search in Essential JS 2 autocomplete

A fuzzy search can be performed with the help of the search method of the Fusejs library and a filtering event of autocomplete component. The filtering event provides an option to filter the existing data source and rebind with the modified data source.

Installation

 

Configure the autocomplete component in Angular:

    • To set up a basic Angular application, use the following commands.
git clone https://github.com/angular/quickstart.git quickstart
cd quickstart
npm install
    • Install the dependencies required to render the autocomplete component in the Angular environment using the following command.
npm install @syncfusion/ej2-ng-dropdowns --save
    • Install the Fuse JS packages required to perform fuzzy searches in the Angular environment using the following command.
npm install fuse.js

Configure autocomplete component

    • Map the following drop-down packages in the systemjs.config.js file.
/**
 * System configuration for Angular samples.
*/
(function (global) {
System.config({
paths: {
      // paths serve as alias
      'npm:': 'node_modules/',
      'syncfusion:': 'node_modules/@syncfusion/', 
    },

    map: {

        // our app is in the app folder.
        app: 'app',

        // Angular bundles

        // Syncfusion bundles
      '@syncfusion/ej2-base': 'syncfusion:ej2-base/dist/ej2-base.umd.min.js',
      '@syncfusion/ej2-data': 'syncfusion:ej2-data/dist/ej2-data.umd.min.js',
      '@syncfusion/ej2-inputs': 'syncfusion:ej2-inputs/dist/ej2-inputs.umd.min.js',
      '@syncfusion/ej2-popups': 'syncfusion:ej2-popups/dist/ej2-popups.umd.min.js',
      '@syncfusion/ej2-lists': 'syncfusion:ej2-lists/dist/ej2-lists.umd.min.js',
      '@syncfusion/ej2-buttons': 'syncfusion:ej2-buttons/dist/ej2-buttons.umd.min.js',
      '@syncfusion/ej2-splitbuttons': 'syncfusion:ej2-splitbuttons/dist/ej2-splitbuttons.umd.min.js',
      '@syncfusion/ej2-dropdowns': 'syncfusion:ej2-dropdowns/dist/ej2-dropdowns.umd.min.js',
      '@syncfusion/ej2-ng-base': 'syncfusion:ej2-ng-base/dist/ej2-ng-base.umd.min.js',
      '@syncfusion/ej2-ng-dropdowns': 'syncfusion:ej2-ng-dropdowns/dist/ej2-ng-dropdowns.umd.min.js',
'fuse.js': 'npm:fuse.js/dist/fuse.min.js',
    }
  });
})(this);
    • Import autocomplete module into Angular application (app.module.ts) from the package @syncfusion/ej2-ng-dropdowns.
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AutoCompleteComponent } from '@syncfusion/ej2-ng-dropdowns';
import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
    //declaration of ej2-ng-dropdowns module in NgModule.
  declarations: [ AppComponent, AutoCompleteComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
    • Modify the template in theapp.component.ts file to render the ejs-autocomplete component. Also, reference the themes from the corresponding base package @syncfusion/ej2-dropdowns through styleUrls.
import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'my-app',
  // specifies the template string for the autocomplete component.
  template: ``,
  // include the material theme in autocomplete.
  styleUrls: ['../../node_modules/@syncfusion/ej2-dropdowns/styles/material.css'],
  encapsulation: ViewEncapsulation.None
})
export class AppComponent  { }

Binding data

After initializing, populate data using the dataSource property of autocomplete. Here, we have used the books data as the data source of autocomplete.

import { Component,ViewEncapsulation } from '@angular/core';
import * as Fuse from 'fuse.js';
import { EmitType } from '@syncfusion/ej2-base';
import { FilteringEventArgs } from '@syncfusion/ej2-dropdowns';

@Component({
  selector: 'my-app',
  template: `
<ejs-autocomplete id=”books” [dataSource]=”booksData” [fields]=”fields” (filtering)=”onFiltering($event)” [placeholder]=”watermark”glt;

`, // include the material theme in autocomplete. styleUrls: [‘../../node_modules/@syncfusion/ej2-dropdowns/styles/material.css’], encapsulation: ViewEncapsulation.None }) export class AppComponent { name = ‘Angular’; public booksData: { [key: string]: Object; }[] = [ { BookName: ‘Support Vector Machines Succinctly’, BookID: ‘BOOK1’ }, { BookName: ‘Scala Succinctly’, BookID: ‘BOOK2’ }, { BookName: ‘Application Security in .NET Succinctly’, BookID: ‘BOOK3’ },{ BookName: ‘Node.js Succinctly’, BookID: ‘BOOK36’ } ]; // maps the appropriate column to the fields property. public fields: Object = { value: ‘BookName’ }; // set placeholder for autocomplete input element. public watermark: string = ‘e.g. Node.js Succinctly’; }

Custom filter configuration

Define the filtering event to rebind the data source with the result of the Fuse JS search method. You can use your own libraries to filter the data and update it to autocomplete’s suggestion list through the updateData method.

public onFiltering: EmitType = (e: FilteringEventArgs) => {
// pass the filter data source to updateData method.
        e.updateData([], null);
}

Fuse JS configuration

Configure the Fuse JS library with autocomplete:

    • First, import the Fuse JS packages in app.component.ts file.
import * as Fuse from 'fuse.js';
    • Initialize the Fuse search plugin within the filtering event to perform the search action on every key press using the fuse.search method. The typed-in characters are passed as an argument to the search method and then it returns the result.
    • Fuse JS has more APIs to retrieve the search results in a customized style, but here we have used the keys, includeMatches, and findAllMatches options for including the matched characters indexes within the return results.
public onFiltering: EmitType = (e: FilteringEventArgs) => {
        let options: Object = {
            keys: ['BookName'],
            includeMatches: true,
            findAllMatches: true
        };
        // create object from Fuse constructor.
        let fuse: Fuse = new Fuse(this.booksData, options);
        // store the search result data based on typed characters.
        let result: any = fuse.search(e.text)
}
    • The result data source is passed to the updateData method as argument. The autocomplete component then generates its suggestion list based on the new data source and displays it over the pop-up.
  let data: { [key: string]: Object; }[] = [];
  for (let i: number = 0; i < result.length; i++) {
      data.push(result[i].item as any);
  }
  // pass the filter data source to updateData method.
  e.updateData(data, null);
  let popupElement: HTMLElement = document.getElementById('books_popup');
  let lists: Element[] = <nodelistof & Element[]>popupElement.querySelectorAll('.e-list-item');
  // For highlight the typed characters, pass the result data and list items to highlightSearch method.
  this.highlightSearch(lists, result);</nodelistof

Highlight matched text

    • After the suggestion result is displayed over the pop-up, the matched characters need to be highlighted in it, so as to help the user identify the matched characters on the suggestion list. It will allow the user to identify the differences in the pop-up list quickly.
    • The built-in highlight option is not supported for custom searches due to the dynamic result updated in the result. Therefore, here we have highlighted the text using matched indexes retrieved from the search result collection.
public highlightSearch(listItems: Element[], result: any): void {
  if (result.length > 0) {
      for (let i: number = 0; i < listItems.length; i++) {
          let innerHTML: string = listItems[i].innerHTML;
          for (let j: number = result[i].matches[0].indices.length - 1; j >= 0; j--) {
              let indexes: number[] = result[i].matches[0].indices[j];
              innerHTML = innerHTML.substring(0, indexes[0]) + '' +
                  innerHTML.substring(indexes[0], (indexes[1] + 1)) + '' + innerHTML.substring(indexes[1] + 1);
              listItems[i].innerHTML = innerHTML;
          }
      }
  }
}

Fuzzy Search with Syncfusion Autocomplete

Note: The Fuse JS library provides appropriate indexes with the matched characters on search.

Conclusion

You have learned how to achieve fuzzy searches using our Essential JS 2 Angular autocomplete component. You also learned how to integrate third-party libraries like FuseJS with the autocomplete component. For more, check out how fuzzy search works in our sample from GitHub, which is ready to run.

The autocomplete component is also available for the JavaScript, JavaScript (ES5), React, Vue, ASP.NET MVC, and ASP.NET platforms.

To try our autocomplete component’s advanced features, you can explore our documentation. You can download the free trial or play with our online demo. You can even check out the package on GitHub at https://github.com/syncfusion/ej2-ng-dropdowns. If you have any questions or feedback, please let us know in the comments below. You can also contact us through our support forum or Direct-Trac.

If you like this blog post, we think you’ll also like the following free e-books:

 

Tags:

Share this post:

Related Posts

Leave a comment