3

I have done a simple CRUD application, now I have to add a Search Bar that filters my table and show me the rows with the same letters that I digit.

I don't know what to do in my component, I saw different things with pipes and filter but I couldn't adapt them to my project.

I would like to know if there is a method that I can implement to my component.ts without creating a new one.

COMPONENT.HTML

<div class="container">
  <ul class="table-wrapper">
    <div class="table-title">
      <div class="row">
        <div class="col-sm-4">
          <button *ngIf="metadata && metadata.addMD" (click)="addFunc(metadata)">{{metadata.addMD.label}}</button>
          <div class="show-entries">
            <span>Show</span>
            <label>
              <select [(ngModel)]="pageSize">
                <option *ngFor="let maxPerPage of rowsOnPageSet"
                        (click)="maxElements(maxPerPage)">{{maxPerPage}}</option>
              </select>
            </label>
            <span>users</span>
          </div>
        </div>

        <div class="col-sm-4">
          <h2 class="text-center">Users <b>Details</b></h2>
        </div>

        <div class="col-sm-4">
          <div class="search-box">
            <div class="input-group">
              <span class="input-group-addon"><i class="material-icons">&#xE8B6;</i></span>
--> //HERE I HAVE TO ADD THE FUNCTION OR SOMETHING ELSE
             <input type="text" class="form-control" [(ngModel)]="searchVal"
                 (ngModelChange)='checkSearchVal()'  placeholder="Search&hellip;">
            </div>
          </div>
        </div>
      </div>
    </div>

    <table class="table table-bordered">
      <thead>
      <tr>
        <th *ngFor="let col of columns" (click)="sortTable(col)">{{col}}
          <i *ngIf="col === columnSorted && !direction" class="material-icons">keyboard_arrow_up</i>
          <i *ngIf="col === columnSorted && direction" class="material-icons">keyboard_arrow_down</i>
        </th>
        <th>Actions</th>
      </tr>
      </thead>
      <tbody>
      <tr *ngFor="let user of users | paginate: {itemsPerPage: pageSize,
                                               currentPage: page,
                                               totalItems: users.length}">
        <td *ngFor="let col of columns">{{user[col]}}</td>
        <td>
          <button [ngClass]="getClassCondition(act.actionType)" *ngFor="let act of actions"
                  (click)="actionFunc(act, user)">{{act.label}}</button>

        </td>

      </tr>
      </tbody>
    </table>

    <div align="center">
      <!--<div class="hint-text">Showing <b>{{totUsersPerPage}}</b> out of <b>{{users.length}}</b> entries</div>-->
      <ul align="center">
        <pagination-controls (pageChange)="pageChanged($event)"></pagination-controls>
      </ul>
    </div>

  </ul>
</div>

COMPONENT.TS, here I have to create the right method that works for my app.

    export class DynamicTableComponent implements OnInit {

  constructor(private userService: UserService,
              private resourcesService: ResourcesService,
              private router: Router) {
  }

  @Input()
  users = [];
  @Input()
  columns: string[];
  @Input()
  actions = [];
  @Input()
  metadata: any;
  @Input()
  class;

  direction = false;
  columnSorted = '';
  public rowsOnPageSet = ['5', '10', '15', '20', '25'];
  page = 1;
  private pageSize = 5;
  searchVal = '';

  /*totalPages = Math.trunc(this.users.length / this.pageSize);
  totUsersPerPage = this.pageSize;*/

  ngOnInit() {
  }

  actionFunc(action, element: any) {
    if (action.actionType === 'DELETE') {
        /*...*/
      }
    }
    if (action.actionType === 'GO_TO') {
        /*...*/
    }
  }

  addFunc(metadata) {
    if (metadata.addMD.actionType === 'ADD_TO') {
       /*...*/
    }
  }

  maxElements(maxPerPage) {
    this.rowsOnPageSet = maxPerPage;
  }

  sortTable(param) {
    /*...*/
  }

  getClassCondition(act) {
    return act === 'DELETE' ? this.class = 'btn btn-danger' : 'btn btn-primary';
  }

  pageChanged($event: number) {
     /*...*/
  }

checkSearchVal() {
    this.users.slice();
    const filteredUsers: User[] = [];
    if (this.searchVal && this.searchVal !== '') {
      for (const selectedUser of this.users) {
        if (selectedUser.firstName.toLowerCase().search(this.searchVal.toLowerCase()) !== -1 ||
          selectedUser.lastName.toLowerCase().search(this.searchVal.toLowerCase()) !== -1) {
          filteredUsers.push(selectedUser);
        }
      }
      this.users = filteredUsers.slice();
    }
  }

}

DATA STRUCTURE: in.memory-data.service.ts

import { InMemoryDbService } from 'angular-in-memory-web-api';
import { User } from './user';
import { Injectable } from '@angular/core';

export const COLUMNS = ['id', 'firstName', 'lastName', 'age'];

@Injectable({
  providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    const USERS = [
      {id: 1, firstName: 'NAME_1', lastName: 'SURNAME_1', age: 23},
      {id: 2, firstName: 'NAME_2', lastName: 'SURNAME_2', age: 23},
      {id: 3, firstName: 'NAME_3', lastName: 'SURNAME_3', age: 23},
      {id: 4, firstName: 'NAME_4', lastName: 'SURNAME_4', age: 24},
      {id: 5, firstName: 'NAME_5', lastName: 'SURNAME_5', age: 42},
      {id: 6, firstName: 'NAME_6', lastName: 'SURNAME_6', age: 41},
      {id: 7, firstName: 'NAME_7', lastName: 'SURNAME_7', age: 24},
      {id: 8, firstName: 'NAME_8', lastName: 'SURNAME_8', age: 25},
      {id: 9, firstName: 'NAME_9', lastName: 'SURNAME_9', age: 25},
      {id: 10, firstName: 'NAME_10', lastName: 'SURNAME_10', age: 25},
      {id: 11, firstName: 'NAME_11', lastName: 'SURNAME_11', age: 22},
      {id: 12, firstName: 'NAME_12', lastName: 'SURNAME_12', age: 22},
      {id: 13, firstName: 'NAME_13', lastName: 'SURNAME_13', age: 24},
    ];
    return {USERS};
  }
}

user.ts

export class User {
  id: number;
  firstName: string;
  lastName: string;
  age: number;
}
S.A.R.A.
  • 165
  • 1
  • 2
  • 15
  • 1
    in HTML file. Add an [(ngModel)] and ngModelChange='searchFunction()"' on your search input... in your TS file, add logic to this searchFunction() which will probably filter your data structure as per the contents of the search input field... – Akber Iqbal Mar 08 '19 at 09:55
  • yeah, the problem is the function in ts... – S.A.R.A. Mar 08 '19 at 10:01
  • Share the data structure and I can help with the filter code – Akber Iqbal Mar 08 '19 at 10:04
  • @AkberIqbal I edited the question. Tell if u need something else, thanks in advance! – S.A.R.A. Mar 08 '19 at 10:09

2 Answers2

5

Just create a PIPE.ts file to filter the table:

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

@Pipe({ 
    name: 'filterAll'
})
export class FilterPipe implements PipeTransform {
  transform(value: any, searchText: any): any {
    if(!searchText) {
      return value;
    }
    return value.filter((data) => this.matchValue(data,searchText)); 
  }

  matchValue(data, value) {
    return Object.keys(data).map((key) => {
       return new RegExp(value, 'gi').test(data[key]);
    }).some(result => result);
  }
 }

Add the FilterPipe to app.module.ts in declarations and add this to your component.html:

  <form id="searchForm">
    <div class="form-group">
        <div class="input-group" id="filterAll">
          <div class="input-group-addon"><i class="glyphicon glyphicon-search"></i></div>
          <input type="text" class="form-control" name="searchString" placeholder="Search..." [(ngModel)]="searchString">
        </div>
    </div>
</form>

then you add a pipe to the TR of your table like this:

 <tr *ngFor="let user of users | paginate: {itemsPerPage: pageSize,
                                           currentPage: page,
                                           totalItems: users.length} | filterAll: searchString">

I forgot about the component.ts file sorry: You need to put the searchString variable as:

public searchString: string;
Jon Fernandez
  • 117
  • 1
  • 8
  • This works perfectly, can you explain what does the pipe.ts do exactly? Also I would like to search for colums, so add a dropdown in the searchbar with my columns array, choose one and search for it. – S.A.R.A. Mar 08 '19 at 11:16
  • 1
    as you can see, the filter.ts gets 2 values, 'value' and 'searchText'. the value is the data of the table and the searchText your filter string. Basically compares the value of the searchText string and the data and returns it if there is a match – Jon Fernandez Mar 08 '19 at 11:37
  • with the search for colums, what do you want to display in the table? only that colum? – Jon Fernandez Mar 08 '19 at 11:41
  • thanks for the explenation ;) I would like to choose the columns from a drop down or check box, then the search bar will work just for the text inside that column and show me the same result, so the row table. Example: search for name, surname,... – S.A.R.A. Mar 08 '19 at 11:45
  • 1
    first add the dropdown with your colum array then you will have to tell the table somehow that the filter only affects the selected colum. The good thing is that you don´t have to change the pipe.ts file – Jon Fernandez Mar 08 '19 at 11:52
2

I saw that you got your answer... here is another approach for what it is worth... as discussed in the comments...

checkSearchVal() {
    this.USERS = masterUSERS.slice();
    let filteredUsers: User[] = [];
    if (this.searchVal && this.searchVal != '') {

    /* NORMAL FOR
      for(var i=0; i<this.USERS.length; i++ ){
        if(this.USERS[i].firstName.toLowerCase().search(this.searchVal.toLowerCase()) != -1 || this.USERS[i].lastName.toLowerCase().search(this.searchVal.toLowerCase()) != -1 ){
          filteredUsers.push(this.USERS[i])
        }
      }
    */
    /* FOR EACH
      this.USERS.forEach((selectedUser) => {
        if (selectedUser.firstName.toLowerCase().search(this.searchVal.toLowerCase()) != -1 ||
          selectedUser.lastName.toLowerCase().search(this.searchVal.toLowerCase()) != -1) {
          filteredUsers.push(selectedUser);
        }
      })
    */

    /*  FOR OF */
    for (let selectedUser of this.USERS) {
        if (selectedUser.firstName.toLowerCase().search(this.searchVal.toLowerCase()) != -1 ||
          selectedUser.lastName.toLowerCase().search(this.searchVal.toLowerCase()) != -1) {
          filteredUsers.push(selectedUser);
        }
    }

      this.USERS = filteredUsers.slice();
    }
  }

update: moved the this.USERS = filteredUsers.slice(); inside the IF

update:2: same code with forEach and For-Of (to get rid of the TSLint error)

Akber Iqbal
  • 14,487
  • 12
  • 48
  • 70
  • I would like to use your edit, at the moment I get an error on the for telling me "TSLint: Expected a 'for-of' loop instead of a 'for' loop with this simple iteration (prefer-for-of)". When I run it works, but if i delete the text from the search bar, the table returns empty, also in your example on stackBlitz. It should return all the datas. – S.A.R.A. Mar 08 '19 at 10:53
  • @S.A.R.A. moved `this.USERS = filteredUsers.slice();` inside the IF statement... don't see the TSLint issue on stackblitz... can you recreate it on stackblitz? – Akber Iqbal Mar 08 '19 at 10:58
  • Ok, this is perfect,it works! What about the error in "for", I'm checking [link](https://stackoverflow.com/questions/51897497/tslint-error-expected-a-for-of-loop-instead-of-a-for-loop-with-this-simple) but it looks strange... – S.A.R.A. Mar 08 '19 at 11:01
  • hi @S.A.R.A., Thanks for the vote and accepted answer; it is strange that it wants you to write For-Of... i once read an article which compared normal For vs. forEach ... and the winner was our normal for... – Akber Iqbal Mar 08 '19 at 11:15
  • I noticed that it works well on stackBlitz, I edited my question adding the row for the search bar in HTML and add the method in my component.ts: The problem for the empty table when delete text from search bar remains... why? – S.A.R.A. Mar 08 '19 at 11:33
  • did you get any error in the console?... ngModel and ngModelChange are both present in your code, right? – Akber Iqbal Mar 08 '19 at 11:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189673/discussion-between-s-a-r-a-and-akber-iqbal). – S.A.R.A. Mar 08 '19 at 11:39