Avatar of Cynthia Hill
Cynthia Hill
Flag for United States of America asked on

Search Box to limit Angular2 table

Hello - I've tried a handful of different code samples, but can not figure out how to add a search box to an Angular 2 table.

Here is my app.component.html code

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{title}}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<div>
  <input [(ngModel)]="query">
  <div *ngFor="let user of users | search:'id':query">{{user.id}}</div>


<table class="table">
<ng-container *ngFor="let user of users">
      <tr>
        <th>First Name</th><td>{{user.firstName}}</td>
        <th>Last Name</th><td>{{user.lastName}}</td>
      </tr>
      <tr>
        <th>Job</th><td>{{user.job}}</td>
        <th>ID</th><td>{{user.id}}</td>
      </tr>
    </ng-container>
</table>
</div>

Open in new window



and here is my app.component.ts code

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


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'hello';
  users = [
    {
        "firstName": "Clark",
        "lastName": "Kent",
        "job": "Reporter",
        "id": 20
    },
    {
        "firstName": "Bruce",
        "lastName": "Wayne",
        "job": "Business Owner",
        "id": 30
    },
    {
        "firstName": "Peter",
        "lastName": "Parker",
        "job": "Photographer",
        "id": 40
    },
    {
        "firstName": "Tony",
        "lastName": "Stark",
        "job": "Business Owner",
        "id": 25
    }
]
}

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {
  public transform(value, keys: string, term: string) {

    if (!term) return value;
    return (value || []).filter((user) => keys.split(',').some(key => user.hasOwnProperty(key) && new RegExp(term, 'gi').test(user[key])));

  }
}

Open in new window


Where am I going wring. Should the code for the pipe be in a separate file? It did not seem to fix things when I tried...but that's probably because the code is not right.

Any assistance would be greatly appreciated! Thanks!
Web DevelopmentHTMLJavaScript

Avatar of undefined
Last Comment
Julian Hansen

8/22/2022 - Mon
Julian Hansen

A few things going on here
1. Should Pipe be in a separate file
Answer: it does not have to but convention says to separate functionality into different files - whether that is a file per Pipe declaration or one file that exports all Pipes is a design decision you make.

2. Why is it not working. Let's look at how a pipe works. The transform takes at least 2 parameters. A list of items to filter and 1 or more filter args. In this case we are going to use 1.

How do we set it up.

First the view - we need an input to take the search and we need a model for the value. If we are going to be using ngModel then we need to add the Forms module to our app module and include it in the imports section
app.module.ts
...
import { FormsModule } from '@angular/forms';
...
@NgModule({
  declarations: [
   ...
  ],
  imports: [
    BrowserModule,
    ...,
    FormsModule
  ],

Open in new window


Next we add the input to our component HTML view
Search Text <input [(ngModel)]="searchText" />

Open in new window

This can go anywhere on your page - put it where it makes most sense.

Also in the view we have to filter our results based on this value
<table class="table">
<ng-container *ngFor="let user of users | search:searchText">

Open in new window


We are going to pipe our results to the (to be defined) search Pipe and pass it the searchText to filter on.

Finally we need to define our pipe
Let's do it the right way.
I created my pipes file like so
ng g pipe app

Open in new window

This created (and added to my project) the file app.pipes.ts
I then defined my pipe as follows
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {
  public transform(items: any[], filter: string) {
    // IF SEARCH TEXT IS EMPTY RETURN THE COMPLETE LIST
    if (!filter) return items;
    // OTHERWISE FILTER RESULTS BASED ON WHETHER filter IS IN firstName
    // lastName OR Job
    return items.filter(item => item.firstName.indexOf(filter) !== - 1 ||
    item.lastName.indexOf(filter) !== -1 ||
    item.job.indexOf(filter) !== -1)
  }
}

export const appPipes = [SearchPipe];

Open in new window

(refer note at the end)
Note the exported appPipes at the end - this is so I can declare any other pipes in this component add them to the exported appPipes array and in my module.ts I only need to import the appPipes array - like so
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
// IMPORT THE exported appPipes
import { appPipes } from './pipes.pipe';

@NgModule({
  declarations: [
    AppComponent,
    appPipes // INCLUDE IN OUR DECLARATIONS
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Open in new window

And we are good to go.
Working sample here

EDIT - Note Added
Note: when searching an object for text - we can check the relevant fields as we have done in this sample or if we want to search the whole object we can simply stringify the item and do an indexOf on the string
(item => JSON.stringify(item).indexOf(filter) !== -1)

Open in new window

Full source using above method
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {
  public transform(items: any[], filter: string) {
    // IF SEARCH TEXT IS EMPTY RETURN THE COMPLETE LIST
    if (!filter) return items;

    // RETURN ANY ITEM THAT HAS THE filter STRING ANYWHERE IN THE OBJECT
    return items.filter(item => JSON.stringify(item).indexOf(filter) !== -1);
  }
}

export const appPipes = [SearchPipe];

Open in new window

Cynthia Hill

ASKER
That is an awesome explanation! I greatly appreciate you "teaching me how to fish" with all the detail you provide!.

Alright, here is where I am...

Its working...which is awesome!

I had to change the reference of
import { appPipes } from './pipes.pipe';

Open in new window

to
import { appPipes } from './app.pipe';

Open in new window

because the pipe files were added in my main app folder. What is best practice? Should the app.pipe.ts file have been created in a separate "pipe" file?

Also, I notice the text box search is case sensitive. How do I make it work when a user enters in either upper or lower case text?

The intent for this table is to always only show one record (based on the ID a user enters...that is the only field I need it to filter off of). I can go back an repoint the search functionality to look at only the ID field later tonight when I have more time to play with it.

However, what I am not sure on is how to not have any records returned until the user enters in a search value.

In any event...I will play more with it tonight. Greatly appreciate your help so far!
ASKER CERTIFIED SOLUTION
Julian Hansen

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
Cynthia Hill

ASKER
Awesome...its working!!

You are the best Julian :)

Greatly appreciate your expertise and your efforts in helping to further my learning!
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
Julian Hansen

You are welcome.