AutoComplete Textbox in Angular 6 with Dynamic Data using Web API in MVC 4

← PrevNext →

I am sharing an example here explaining how to implement AutoComplete textbox feature in Angular 6 with dynamic data using Web API in MVC 4.

AutoComplete Textbox Example in Angular 6 using Web API

If you have already upgraded to Angular 6, then you can straightaway go to next part of the post (creating the Web API and the rest). However, if you are new to Angular 6 and haven’t upgraded yet, then follow these steps.

First, update Angular CLI globally

Open the command prompt and run the following …

npm install -g @angular/cli

ng update @angular/cli

Update the Angular Core Package

Update Angular packages to v6 and get the latest RxJs 6 and TypeScript.

ng update @angular/core

Update Angular Material

You will have to update Angular Material to latest version as well, since I am using the AutoComplete feature that comes with Angular Material.

ng update @angular/material

Update Node.js to Latest version

You need to install the latest version of Node.js in your computer. I guess, now Angular needs Node 8 or above.

It might take some time to update CLI, the packages etc. to the latest versions. However, its not very complicated and it is done only once.

Finally, in the root directory check the versions.

ng --version (or simply ng --v)

Well, we are now ready to create our Apps.

Since, I’ll be extracting data from an SQL Server database, I’ll create a table and add few rows of data to it.

Create a Table in SQL Server

Create a table in SQL Server and add few data to it.

CREATE TABLE [dbo].[Books](
    [BookID] [int] IDENTITY(1,1) NOT NULL,
    [BookName] [varchar](50) NULL,
    PRIMARY KEY CLUSTERED ( [BookID] ASC )
) ON [PRIMARY]
Web API (using MVC 4)

Next, create a Web API in Asp.Net. I have MVC 4 installed in my computer. You can use any other version.

Model Books.cs (C#)
using System;
namespace BooksApp.Models
{
    public class Books
    {
        public string BookName { get; set; }
    }
}
Model Books.vb (Vb)
Imports System.Web

Namespace BooksApp.Models
    Public Class Books
        Public Property BookName() As String
            Get
                Return m_BookName
            End Get
            Set(value As String)
                m_BookName = value
            End Set
        End Property
        Private m_BookName As String
    End Class
End Namespace
Web API Controller

The Controller in this project has a single public method called Get(), which will handle the http request and return the list of books to the calling application.

BooksController.cs (C#)
using System;
using System.Collections.Generic;

using System.Net;
using System.Net.http;
using System.Web.http;

using BooksApp.Models;
using System.Data.SqlClient;

namespace BooksApp.Controllers
{
    public class BooksController : ApiController
    {
        // LIST OBJECT WILL HOLD AND RETURN A LIST OF BOOKS.
        List<Books> MyBooks = new List<Books>();

        // RETURN A LIST OF BOOKS MATCHING WITH THE REQUESTED ALPHANUMERIC VALUE(S).
        public IEnumerable<Books> Get(string sLookUpString)
        {
            GetTheBooks(sLookUpString);
            return MyBooks;
        }

        public void GetTheBooks(string sFind)
        {

            string sConnString = "Data Source=DNA;Persist Security Info=False;" +
                "Initial Catalog=DNA_Classified;User Id=sa;Password=demo;Connect Timeout=30;";

            SqlConnection myConn = new SqlConnection(sConnString);

            // SEARCH DATABASE TABLE MATCHING BOOKS WITH THE "LOOKUP" STRING.
            SqlCommand objComm = new SqlCommand("SELECT *FROM dbo.Books " +
                "WHERE BookName LIKE '%'+@LookUP+'%' ORDER BY BookName", myConn);
            myConn.Open();

            objComm.Parameters.AddWithValue("@LookUP", sFind);
            SqlDataReader reader = objComm.ExecuteReader();

            // ADD EACH BOOKNAME ALONG WITH ITS ID IN THE LIST.
            while (reader.Read())
            {
                MyBooks.Add(new Books 
                    { 
                        BookName = reader["BookName"].ToString() 
                    });
            }

            myConn.Close();
        }
    }
}
BooksController.vb (Visual Basic)
Option Explicit On

Imports System.Net.http
Imports System.Web.http

Imports System.Data.SqlClient
Imports BooksApp.BooksApp.Models

Namespace BooksApp
    Public Class BooksController
        Inherits ApiController

        ' LIST OBJECT WILL HOLD AND RETURN A LIST OF BOOKS.
        Dim MyBooks As New List(Of Books)()


        ' RETURN A LIST OF BOOKS MATCHING WITH THE REQUESTED ALPHANUMERIC VALUE(S).
        Public Function [Get](sLookUpString As String) As IEnumerable(Of Books)
            GetTheBooks(sLookUpString)
            Return MyBooks
        End Function

        Public Sub GetTheBooks(sFind As String)

            Dim sConnString As String = "Data Source=DNA;Persist Security Info=False;" & _
                "Initial Catalog=DNA_Classified;User Id=sa;Password=demo;Connect Timeout=30;"

            Dim myConn As New SqlConnection(sConnString)

            ' SEARCH DATABASE TABLE MATCHING BOOKS WITH THE "LOOKUP" STRING.
            Dim objComm As New SqlCommand("SELECT *FROM dbo.Books " & _
                "WHERE BookName LIKE '%'+@LookUP+'%' ORDER BY BookName", myConn)

            myConn.Open()

            objComm.Parameters.AddWithValue("@LookUP", sFind)
            Dim reader As SqlDataReader = objComm.ExecuteReader()

            ' ADD EACH BOOKNAME ALONG WITH ITS ID IN THE LIST.
            While reader.Read()
                MyBooks.Add(New Books() With { _
                    .BookName = reader("BookName").ToString()
                 })
            End While

            myConn.Close()
        End Sub

    End Class
End Namespace

Now, run the API and keep it running. Or, you can run it later, once you have created your Angular 6 app.

Create Angular 6 Application

Now, create a project using Angular CLI (Command Line Interface). Open the command prompt and go to the folder where you want to create the project and enter,

ng new AutoCompleteInAngular

Go to the folder …

cd AutoCompleteInAngular

Launch the server …

ng serve --o

It will open the browser and will show the introduction page. We now need to add components, create a service and design our application template.

Add Angular Material Dependencies

Install Angular Material if you have not installed it before. This is important as it provides the AutoComplete component and its features.

npm install --save @angular/material @angular/cdk

Now open app.module.ts file inside src/app/ folder. We need to import few services to our project, like the MatAutoCompleteModule and MatInputModule from the @angular/material class.

To create a reactive form, we’ll import FormsModule and ReactiveFormModule from the @angular/forms class.

We’ll also include BrowserAnimationsModule, because the AutoComplete feature in Angular 6 has some basic animations.

To consume Asp.Net Web API services, I am using the new HttpClient Service in my application. Therefore, I’ll also import the HttpClientModule in the app.module.ts file.

The app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule, MatInputModule } from '@angular/material';

import { HttpClientModule } from '@angular/common/https';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    ReactiveFormsModule,
    MatAutocompleteModule,
    MatInputModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Create the Books Service

I’ll create an Angular service named booksService, which will make the API calls.

Add a folder named services inside src/app/ folder and create books.service.ts file.

Make sure the Web API is still running.

import { Injectable } from '@angular/core';  
import { HttpClient } from '@angular/common/https';
import { map } from 'rxjs/operators';
import { debounceTime } from 'rxjs/internal/operators/debounceTime';

@Injectable()  
export class booksService {  

    constructor (private httpService: HttpClient) { }  

    search(term) {
        var listOfBooks = this.httpService.get('http://localhost:25252/api/books/' + term)
        .pipe(
            debounceTime(500),  // WAIT FOR 500 MILISECONDS ATER EACH KEY STROKE.
            map(
                (data: any) => {
                    return (
                        data.length != 0 ? data as any[] : [{"BookName": "No Record Found"} as any]
                    );
                }
        ));

        return listOfBooks;  
    }  
}

The service above, has a method named search() that takes a parameter named term. The term is the value that the user will enter in the textbox.

After you have saved the data, it will automatically refresh the browser, where your app is running. Remember, you have launched the server.

Note: Check your browser console for any errors. A common error that occurs while working with Web API’s running on a different port is Error: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

Please check this post to resolve the above-mentioned error. You’ll have to configure the Global.asax file in your Asp.Net Web API and run API application again.

I have injected HttpClient into a constructor class. I am also using the get() method to make a request for data to the Web API.

The get() method has a URL (a different port) with the parameter term, pointing to the API. See the Web API controller, which we have created in the beginning of this article.

It will populate listOfBooks variable with values that the API returned, based on the term or keyword and will return the result to our component.

debounceTime(500): It creates a delay of 500 milliseconds before making a call. This is an important feature here, since the user will constantly enter values in the textbox, to get the desired result. However, the application needs some time to finish the first request, before sending the second or third request. You won’t see the difference when you are working on your local machine using localhost, however when you are using the remote server, this is very useful.

Ref: Check this post to learn more about debounceTime() method.

Create the Application Component

Well, now need to create our component. Open app.component.ts file inside src/app/ folder. Here we'll import the HttpClient class, which will help initiate http request and response.

In-addition, I have imported the booksService class.

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

import { FormControl } from '@angular/forms';
import { booksService } from './services/books.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [booksService]
})
export class AppComponent {
  title = 'AutoComplete Example in Angular 6';

  searchTerm : FormControl = new FormControl();
  myBooks = <any>[];

  constructor (private service: booksService) { }
  
  ngOnInit () {
    this.searchTerm.valueChanges.subscribe(
      term => {
        if (term != '') {
          this.service.search(term).subscribe(
            data => {
              this.myBooks = data as any[];
              //console.log(data[0].BookName);
          })
        }
    })
  }
}

The valueChanges method (a reactive form instance in FormControl) listens to any changes on a form control, like a textbox. It would check if the user has entered any value in the textbox and call our service (booksService) method search() with the entered value.

After the successful API call, it will receive a list of values (books in our case) and store the value in an array named myBooks. We now need to bind this array to the AutoComplete component in our applications template.

The Application Template

Open app.component.html file and add the below code to it.

<div style="text-align:left;">
  <h1>
    {{ title }}
  </h1>

  <form>
      <mat-form-field class="container">

          <!-- ADD AN INPUT BOX OF TYPE TEXT AND LINK IT TO THE AUTO COMPLETE PANEL. -->
          <input type="text" placeholder="Search books ..."
              matInput 
              [formControl]="searchTerm" 
              [matAutocomplete]="auto">  

          <!-- CREATE AUTO COMPLETE PANEL WITH OPTIONS. -->
          <mat-autocomplete #auto="matAutocomplete">
            <mat-option *ngFor="let books of myBooks" [value]="books.BookName">
              {{ books.BookName }}
            </mat-option>
          </mat-autocomplete>

      </mat-form-field>
  </form>
 
</div>

I have an input box of type text and I have added formControl directive and have created the auto complete panel with options. I am using *ngFor directive to populate the list of books or the values that the API service will return. The options are refreshed with new values for every keystroke.

Update Style.css file

Finally, add a little style to the forms control. In the root folder or src folder, you will find styles.css file. Open it and add the below style to it.

/* You can add global styles to this file, and also import other style files */

@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

.container {
    width: 300px;
    font: 17px Calibri;
}

That’s it. If everything goes right, the AutoComplete feature will work, as you wanted it. Happy Coding. 🙂

← PreviousNext →