Angular 11 ElasticSearch example – simple Full Text Search

Angular 11 ElasticSearch example – simple Full Text Search

In the previous posts, we had know how to get All Documents in Index and show them with pagination. This tutorial show you way to implement a simple Full Text Search in an Angular 11 Application.

Related Post:
Angular 11 ElasticSearch – Quick Start – How to add Elasticsearch.js
Angular 11 ElasticSearch example – How to create an Index
Angular 11 ElasticSearch example – Add Document to Index
Angular 11 ElasticSearch example – Get All Documents in Index
Angular 11 ElasticSearch example – Documents Pagination with Scroll

Elasticsearch Tutorials:
Elasticsearch Overview
ElasticSearch Filter vs Query
ElasticSearch Full Text Queries – Basic

I. How to

1. Add ElasticSearch to Angular 11 Project

Please visit Angular 11 ElasticSearch – Quick Start – How to add Elasticsearch.js for details.

With the post, you will know how to:
– Install ElasticSearch
– Add ElasticSearch to Angular 11 Project
– Use it in the project

2. Create Index & Check Connection

Please visit Angular 11 ElasticSearch example – How to create an Index for details.

3. Simple Full Text Search

Using Client.search() function with match_phrase_prefix, we create a simple full text search function:


fullTextSearch(_index, _type, _field, _queryText): any {
  return this.client.search({
    index: _index,
    type: _type,
    filterPath: ['hits.hits._source', 'hits.total', '_scroll_id'],
    body: {
      'query': {
        'match_phrase_prefix': {
          [_field]: _queryText,
        }
      }
    },
    // response for each document with only 'fullname' and 'address' fields
    '_source': ['fullname', 'address']
  });
}

In the component to implement searching, we create html page that catch any key-up event in the text box:

<input type="text" (keyup)="search($event)" placeholder="enter address" class="input">

<div *ngFor="let customerSource of customerSources">
  <customer-details [customer]='customerSource._source'></customer-details>
</div>

<div *ngIf="customerSources?.length < 1">
  <p>Not found!</p>
</div>

And search() function:


search($event) {
    this.queryText = $event.target.value;

    this.es.fullTextSearch(INDEX,TYPE,'field', this.queryText).then(
      response => {
        this.customerSources = response.hits.hits;
        console.log(response);
      }, error => {
        console.error(error);
      }).then(() => {
        console.log('Search Completed!');
      });
  }
}

To prevent hit the database after every keypress (it will make multiple queries to ElasticSearch), we implement a timeout by keeping track of the last keypress time, then update the subjects only if the last keypress was more than 100ms ago:


lastKeypress = 0;

search($event) {
  if ($event.timeStamp - this.lastKeypress > 100) {
    this.queryText = $event.target.value;
    this.es.fullTextSearch(...);
  }

  this.lastKeypress = $event.timeStamp;
}

So, with query:


-> POST http://localhost:9200/jsa_customer_idx/customer/_search?filter_path=hits.hits._source%2Chits.total%2C_scroll_id&_source=fullname%2Caddress
 {
    "query": {
      "match_phrase_prefix": {
        "address": "ull"
      }
    }
  }

The result will be like:


  <- 200
  {
    "hits": {
      "total": 2,
      "hits": [
        {
          "_source": {
            "address": "Ap #443-336 Ullamcorper. Street\nVisalia VA 54886",
            "fullname": "Jamie Konan"
          }
        },
        {
          "_source": {
            "address": "P.O. Box 902 3472 Ullamcorper Street\nLynchburg DC 29738",
            "fullname": "Jack Smith"
          }
        }
      ]
    }
  }

II. Practice

0. Overview

Goal:
angular-4-elasticsearch-full-text-search-simple-goal

Project Structure:
angular-4-elasticsearch-full-text-search-simple-structure

1. App Module

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { ElasticsearchService } from './elasticsearch.service';
import { AddCustomerComponent } from './customer/add-customer/add-customer.component';
import { ShowCustomersComponent } from './customer/show-customers/show-customers.component';
import { CustomerDetailsComponent } from './customer/customer-details/customer-details.component';
import { SearchCustomersComponent } from './customer/search-customers/search-customers.component';

@NgModule({
  declarations: [
    AppComponent,
    AddCustomerComponent,
    ShowCustomersComponent,
    CustomerDetailsComponent,
    SearchCustomersComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    AppRoutingModule
  ],
  providers: [ElasticsearchService],
  bootstrap: [AppComponent]
})

export class AppModule { }

2. ElasticSearch Service


import { Injectable } from '@angular/core';

import { Client } from 'elasticsearch';

@Injectable()
export class ElasticsearchService {

  private client: Client;

  constructor() {
    if (!this.client) {
      this.connect();
    }
  }

  private connect() {
    this.client = new Client({
      host: 'http://localhost:9200',
      log: 'trace'
    });
  }

  createIndex(name): any {
    return this.client.indices.create(name);
  }

  isAvailable(): any {
    return this.client.ping({
      requestTimeout: Infinity,
      body: 'hello JavaSampleApproach!'
    });
  }

  addToIndex(value): any {
    return this.client.create(value);
  }

  getAllDocumentsWithScroll(_index, _type, _size): any {
    return this.client.search({
      index: _index,
      type: _type,
      scroll: '1m',
      filterPath: ['hits.hits._source', 'hits.total', '_scroll_id'],
      body: {
        'size': _size,
        'query': {
          'match_all': {}
        },
        'sort': [
          { '_uid': { 'order': 'asc' } }
        ]
      }
    });
  }

  getNextPage(scroll_id): any {
    return this.client.scroll({
      scrollId: scroll_id,
      scroll: '1m',
      filterPath: ['hits.hits._source', 'hits.total', '_scroll_id']
    });
  }

  fullTextSearch(_index, _type, _field, _queryText): any {
    return this.client.search({
      index: _index,
      type: _type,
      filterPath: ['hits.hits._source', 'hits.total', '_scroll_id'],
      body: {
        'query': {
          'match_phrase_prefix': {
            [_field]: _queryText,
          }
        }
      },
      '_source': ['fullname', 'address']
    });
  }
}

3. Components

3.1 Add Document Component

Please visit: 3_Add_Document_Component for details.

3.2 Details Component

You can find it at: 3.2_Details_Component (in the previous post).

In this tutorial, we just modify code to hide age and published:

<div *ngIf="customer">
  <div>
    <label>Full Name: </label> {{customer.fullname}}
  </div>
  <div *ngIf="customer.age > 0">
    <label>Age: </label> {{customer.age}}
  </div>
  <div>
    <label>Address: </label> {{customer.address}}
  </div>
  <div *ngIf="customer.published?.length > 0">
    <label>Published: </label> {{customer.published}}
  </div>
  <hr/>
</div>

3.3 Show Documents Component

Please visit:
- Show_Documents_Component
- or Show_Documents_Component with Pagination

3.4 Search Documents Component

search-customers.component.ts


import { Component, OnInit } from '@angular/core';

import { CustomerSource } from '../customer.interface';
import { ElasticsearchService } from '../../elasticsearch.service';

@Component({
  selector: 'search-customers',
  templateUrl: './search-customers.component.html',
  styleUrls: ['./search-customers.component.css']
})
export class SearchCustomersComponent implements OnInit {

  private static readonly INDEX = 'jsa_customer_idx';
  private static readonly TYPE = 'customer';

  customerSources: CustomerSource[];
  private queryText = '';

  private lastKeypress = 0;

  constructor(private es: ElasticsearchService) {
    this.queryText = '';
  }

  ngOnInit() {

  }

  search($event) {
    if ($event.timeStamp - this.lastKeypress > 100) {
      this.queryText = $event.target.value;

      this.es.fullTextSearch(
        SearchCustomersComponent.INDEX,
        SearchCustomersComponent.TYPE,
        'address', this.queryText).then(
        response => {
          this.customerSources = response.hits.hits;
          console.log(response);
        }, error => {
          console.error(error);
        }).then(() => {
          console.log('Search Completed!');
        });
    }

    this.lastKeypress = $event.timeStamp;
  }
}

search-customers.component.html

<div style="margin-top:20px">
  <input type="text" (keyup)="search($event)" placeholder="enter address" class="input">

  <div *ngFor="let customerSource of customerSources" style="margin-top:20px">
    <customer-details [customer]='customerSource._source'></customer-details>
  </div>

  <div *ngIf="customerSources?.length < 1" style="margin-top:20px">
    <p>Not found!</p>
  </div>
</div>

4. App Routing Module


import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AddCustomerComponent } from './customer/add-customer/add-customer.component';
import { ShowCustomersComponent } from './customer/show-customers/show-customers.component';
import { SearchCustomersComponent } from './customer/search-customers/search-customers.component';

const routes: Routes = [
    { path: '', redirectTo: 'add', pathMatch: 'full' },
    { path: 'add', component: AddCustomerComponent },
    { path: 'customers', component: ShowCustomersComponent },
    { path: 'search', component: SearchCustomersComponent }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})

export class AppRoutingModule { }

5. App Component

app.component.ts


import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'JavaSampleApproach';
  description = 'Angular4-SpringBoot Demo';
}

app.component.html

<div class="container" style="width: 600px">
	<div style="color: blue; margin-bottom: 20px">
		<h2>{{title}}</h2>
		<h4>{{description}}</h4>
	</div>

	<nav>
		<a routerLink="add" class="btn btn-primary active" role="button" routerLinkActive="active">Add</a>
		<a routerLink="customers" class="btn btn-primary active" role="button" routerLinkActive="active">Customers</a>
		<a routerLink="search" class="btn btn-primary active" role="button" routerLinkActive="active">Search by Address</a>
	</nav>
	<router-outlet></router-outlet>
</div>

6. Check Result

Run Angular 11 App, go to http://localhost:4200/, add Customer Data, then choose Search by Address tab:
angular-4-elasticsearch-full-text-search-simple-goal

III. Sourcecode

Angular4ElasticSearch-fulltext-search



By grokonez | March 28, 2021.


Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*