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!
Cynthia HillLead ConsultantAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Julian HansenCommented:
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

0
Cynthia HillLead ConsultantAuthor Commented:
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!
0
Julian HansenCommented:
I had to change the reference of
(blush) the file was originally called pipe.pipes.ts - I changed the reference in the EE post but not everywhere.

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?
Did you mean file or folder? I go with what CLI does - it creates the file in the root folder so I would leave it there.

How to make it case insensitive. I will answer this with a code snippet as the sample below addresses your other query which is how to filter on id.
  public transform(items: any[], filter: string) {
    if (!filter) return items;
    filter = filter.toLocaleLowerCase();
    return items.filter(item => JSON.stringify(item).toLowerCase().indexOf(filter) !== -1);
  }

Open in new window

We just convert the filter and the stringified object to lower case.

Onto your actual request - how to only show records when a valid ID is entered.

First we remove the following code
    if (!filter) return items;

Open in new window

The reason we do this is that this code is meant to prevent us seeing an empty table when there is nothing in the search box. As this is exactly what you do want to see we take this line out.

Next we only want to show records that match on ID so we do this
  public transform(items: any[], filter: string) {
    return items.filter(item => item.id == filter);
  }

Open in new window

Done, the transform iterates of the items array and checks to see if item.id == filter if it is add it to the rows to display.

Updated sample here
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Cynthia HillLead ConsultantAuthor Commented:
Awesome...its working!!

You are the best Julian :)

Greatly appreciate your expertise and your efforts in helping to further my learning!
0
Julian HansenCommented:
You are welcome.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Development

From novice to tech pro — start learning today.