Search Sort Pagination in Angular 4

August 1, 2017
by Rahil Shaikh
  • Search Sort Pagination in Angular 2

Search Sort Pagination, The three most common features in any list. Let’s see how we can implement it in our Angular application.

The goal of this article is to teach to add search, sort, and pagination to a list displayed using the ngFor directive, also keeping the complexity to a minimum.

Start a new App

We will use angular CLI to speed up our development.

ng new game-list

Throughout the article, the word Angular refers to Angular 2 and above unless mentioned specifically otherwise. This tutorial uses Angular 4.

This will create a starter app for us to work with.

Navigate inside the directory using your CLI tool. And install all the dependencies (sometimes the dependencies fail to install, so we need to do it manually).

npm install

Overview and setup

In this article, we will cover..

  • Search filter in Angular
  • Sorting in an Angular list
  • Pagination in Angular

Before we begin with any of the three, we must lay out the foundation work for our application. Below, I will be adding the bootstrap library to add a bit of style to our app and will also display a list using ngFor on which we can perform our above operations.

npm install bootstrap --save

To load the CSS file we need to add it into .angular-cli.json file.

.angular-cli.json
....,
..,
      "styles": [
        "styles.css",
        "../node_modules/bootstrap/dist/css/bootstrap.min.css"
      ],
...,
...

Let’s create a list to display in the root component.

/src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  games = [
    {
      "id":"1",
      "name": "DOTA 2",
      "genre": "Strategy"
    },
    {
      "id":"2",
      "name": "AOE 3",
      "genre": "Strategy"
    },
    {
      "id":"3",
      "name": "GTA 5",
      "genre": "RPG"
    },
    {
      "id":"4",
      "name": "Far Cry 3",
      "genre": "Action"
    },
    {
      "id":"5",
      "name": "GTA San Andreas",
      "genre": "RPG"
    },
    {
      "id":"6",
      "name": "Hitman",
      "genre": "Action"
    },
    {
      "id":"7",
      "name": "NFS MW",
      "genre": "Sport"
    },{
      "id":"8",
      "name": "Fifa 16",
      "genre": "Sport"
    },{
      "id":"9",
      "name": "NFS Sen 2",
      "genre": "Sport"
    },{
      "id":"10",
      "name": "Witcher Assasins on King",
      "genre": "Adventure"
    }
  ]
}

Now we will display this list using ngFor directive.

/src/app/app.component.html
<header>
  <nav class="navbar navbar-default">
    <a class="navbar-brand" href="#">Ma Games</a>
  </nav>
</header>

<div class="container">
  <div class="row">
    <nav class="navbar">
      <input class="form-control" type="text" name="search">      
    </nav>
    <table class="table">
      <thead>
        <tr>
          <th>#</th>
          <th>Name</th>
          <th>Genre</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let game of games; let i = index">
          <td>{{i}}</td>
          <td>{{game.name}}</td>
          <td>{{game.genre}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Run the app ng serve. You should see this.

//image

Search filter in Angular

If you have worked with Angular v1, you know how easy it was to add a searching facility in ng-repeat using the filter filter. In Angular 2+ we have pipes which are equivalent to filters of Angular 1. But, unfortunately, the Angular team has not included a pre-built search pipe. So either we need to use a third-party library or we can build our own.

Here we will use the ng2-search-filter library to achieve this. Let’s start by installing it.

npm i ng2-search-filter --save

Since we have used Angular CLI for development, we do not have to worry about configuring it in webpack, systemjs or any other module loader. Angular CLI takes of this for us internally.

Now we need to import this into our app.module.ts.

/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { Ng2SearchPipeModule } from 'ng2-search-filter'; //importing the module

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    Ng2SearchPipeModule //including into imports
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

In our root component’s template, we will add an [(ngModel)] to our search bar and add the filter pipe provided by our included library to ngFor. Only two lines will be modifed in the template as shown below.

/src/app/app.component.html
...
..
..
    <nav class="navbar">
      <input class="form-control" type="text" name="search" [(ngModel)]="filter">      
    </nav>
....
...
...
        <tr *ngFor="let game of games | filter:filter; let i = index">
          <td>{{i}}</td>
          <td>{{game.name}}</td>
          <td>{{game.genre}}</td>
        </tr>
...
..
..

Run the app and hurray our search should be working.

Implement Sort feature in Angular list

Next we will add sorting to our list.

Again, there isn’t a pre-built pipe to carry out sorting in Angular. So we will rely on ng2-order-pipe npm package to implement it.

Let’s start by installing the package.

npm install ng2-order-pipe --save

Once it’s installed we need to import it into our app module, just like we did for the search package above.

I won’t repeat the entire app.module.ts below, rather only the parts modified.

/src/app/app.module.ts
....
..
//other imports
...
import { Ng2OrderModule } from 'ng2-order-pipe'; //importing the module

@NgModule({
..
..
 ...
  imports: [
    ...,
   ......,
    Ng2OrderModule //add here
  ],
  ..
...
})
export class AppModule { }

The usage of orderBy the pipe is very similar to the orderBy filter provided in Angular v1. Let’s examine it.

{{ collection | orderBy: key : reverse }}

Here,
collection is an array or an object on which sorting will be performed.
orderBy is the pipe provided by our library.
key is the key on which sorting will be performed.
reverse is a boolean which maintains the order of the sorting (asc or desc)

Now, that we know how it works, let’s add it to our list.

/src/app/app.component.html
...
...
...
        <tr *ngFor="let game of games | orderBy: key : reverse | filter:filter; let i = index">
          <td>{{i}}</td>
          <td>{{game.name}}</td>
          <td>{{game.genre}}</td>
        </tr>
...
..
..

This won’t work yet. Because we haven’t yet defined the key and reverse in our class. We will also add a sort(key) method which will be called on the click of the table headers.

/src/app/app.component.ts
....
...
..
export class AppComponent {
..
..
..
  //sorting
  key: string = 'name'; //set default
  reverse: boolean = false;
  sort(key){
    this.key = key;
    this.reverse = !this.reverse;
  }
}

Now, in our template, we need to call the sort method with appt values on click of table headers.

/src/app/app.component.html
...
...
...
       <thead>
        <tr>
          <th>#</th>
          <th (click)="sort('name')">Name</th>
          <th (click)="sort('genre')">Genre</th>
        </tr>
      </thead
...
..
..

This should be enough to get our sorting working, but we need to improve a bit of UX. We need to add some indicators that will inform the user of the column and order of sorting.

Since we are using Bootstrap, we can use glyphicon.

<span class="glyphicon sort-icon" *ngIf="key =='name'" [ngClass]="{'glyphicon-chevron-up':reverse,'glyphicon-chevron-down':!reverse}"></span>

Adding this to both the columns, our table header should look like below.

/src/app/app.component.html
...
...
...
       <thead>
        <tr>
          <th>#</th>
          <th (click)="sort('name')">Name
            <span class="glyphicon sort-icon" *ngIf="key =='name'" [ngClass]="{'glyphicon-chevron-up':reverse,'glyphicon-chevron-down':!reverse}"></span>
          </th>
          <th (click)="sort('genre')">Genre
            <span class="glyphicon sort-icon" *ngIf="key =='genre'" [ngClass]="{'glyphicon-chevron-up':reverse,'glyphicon-chevron-down':!reverse}"></span>
          </th>
        </tr>
      </thead>
...
..
..

I have also gone ahead and added a bit of CSS to the component CSS file.

/src/app/app.component.css
thead>tr>th {
    cursor: pointer;
}

Save it, and run the application. The sorting should work now.

We have search and sort both implemented, let’s work towards adding pagination.

Implement Pagination in Angular

Pagination has always been a complicated subject for UI developers. Here I will try to make pagination implementation as simple as possible.

We will use the ngx-pagination module written by Michael Bromley.

Let’s install the npm package.

npm install ngx-pagination --save

As we did with our other modules, we need to import ngx-pagination in our app module.

/src/app/app.module.ts
....
..
//other imports
...
import {NgxPaginationModule} from 'ngx-pagination'; // <-- import the module

@NgModule({
..
..
 ...
  imports: [
    ...,
   ......,
    NgxPaginationModule//add here
  ],
  ..
...
})
export class AppModule { }

Ngx-Pagination provides us with a paginate pipe that is responsible for paginating the data and a pagination-controls component which will display the navigation links.

Next, add the paginate pipe and the pagination-controls component to our root component template as shown below.

/src/app/app.component.html
...
...
...
<tbody>
        <tr *ngFor="let game of games | orderBy: key : reverse | filter:filter | paginate: { itemsPerPage: 5, currentPage: p }; let i = index">
          <td>{{i}}</td>
          <td>{{game.name}}</td>
          <td>{{game.genre}}</td>
        </tr>
      </tbody>
      <pagination-controls (pageChange)="p = $event"></pagination-controls>
...
..
..

And in our class initialize the currrentPage which is p to 1.

/src/app/app.component.ts
....
...
..
export class AppComponent {
..
..
..
  //sorting
  key: string = 'name'; //set default
  reverse: boolean = false;
  sort(key){
    this.key = key;
    this.reverse = !this.reverse;
  }

  //initializing p to one
  p: number = 1;
}

That is it. Save and run the application, pagination should be working now along with search and sort.

ngx-pagination comes with a lot of options, which I haven’t covered here. For eg: It supports multiple instances on the same page using the pagination has support for server side paging using the totalItems option and so on. You should definitely check out the GitHub page for more info.

Final Files

In case you have missed out on anything, here are the final files.

package.json
{
  "name": "search-sort-pagination-angular2",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "^4.0.0",
    "@angular/compiler": "^4.0.0",
    "@angular/core": "^4.0.0",
    "@angular/forms": "^4.0.0",
    "@angular/http": "^4.0.0",
    "@angular/platform-browser": "^4.0.0",
    "@angular/platform-browser-dynamic": "^4.0.0",
    "@angular/router": "^4.0.0",
    "bootstrap": "^3.3.7",
    "core-js": "^2.4.1",
    "ng2-order-pipe": "^0.1.5",
    "ng2-search-filter": "^0.3.1",
    "ngx-pagination": "^3.0.1",
    "rxjs": "^5.1.0",
    "zone.js": "^0.8.4"
  },
  "devDependencies": {
    "@angular/cli": "1.0.3",
    "@angular/compiler-cli": "^4.0.0",
    "@types/jasmine": "2.5.38",
    "@types/node": "~6.0.60",
    "codelyzer": "~2.0.0",
    "jasmine-core": "~2.5.2",
    "jasmine-spec-reporter": "~3.2.0",
    "karma": "~1.4.1",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "karma-coverage-istanbul-reporter": "^0.2.0",
    "protractor": "~5.1.0",
    "ts-node": "~2.0.0",
    "tslint": "~4.5.0",
    "typescript": "~2.2.0"
  }
}

 

/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { Ng2SearchPipeModule } from 'ng2-search-filter'; //importing the module
import { Ng2OrderModule } from 'ng2-order-pipe'; //importing the module
import {NgxPaginationModule} from 'ngx-pagination'; // <-- import the module

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    Ng2SearchPipeModule, //including into imports
    Ng2OrderModule, // importing the sorting package here
    NgxPaginationModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

/src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  games = [
    {
      "id":"1",
      "name": "DOTA 2",
      "genre": "Strategy"
    },
    {
      "id":"2",
      "name": "AOE 3",
      "genre": "Strategy"
    },
    {
      "id":"3",
      "name": "GTA 5",
      "genre": "RPG"
    },
    {
      "id":"4",
      "name": "Far Cry 3",
      "genre": "Action"
    },
    {
      "id":"5",
      "name": "GTA San Andreas",
      "genre": "RPG"
    },
    {
      "id":"6",
      "name": "Hitman",
      "genre": "Action"
    },
    {
      "id":"7",
      "name": "NFS MW",
      "genre": "Sport"
    },{
      "id":"8",
      "name": "Fifa 16",
      "genre": "Sport"
    },{
      "id":"9",
      "name": "NFS Sen 2",
      "genre": "Sport"
    },{
      "id":"10",
      "name": "Witcher Assassins on King",
      "genre": "Adventure"
    }
  ];

  //sorting
  key: string = 'name';
  reverse: boolean = false;
  sort(key){
    this.key = key;
    this.reverse = !this.reverse;
  }
  p: number = 1;
}

 

/src/app/app.component.html
<header>
  <nav class="navbar navbar-default">
    <a class="navbar-brand" href="#">Ma Games</a>
  </nav>
</header>

<div class="container">
  <div class="row">
    <nav class="navbar">
      <input class="form-control" type="text" name="search" [(ngModel)]="filter">      
    </nav>
    <table class="table">
      <thead>
        <tr>
          <th>#</th>
          <th (click)="sort('name')">Name
            <span class="glyphicon sort-icon" *ngIf="key =='name'" [ngClass]="{'glyphicon-chevron-up':reverse,'glyphicon-chevron-down':!reverse}"></span>
          </th>
          <th (click)="sort('genre')">Genre
            <span class="glyphicon sort-icon" *ngIf="key =='genre'" [ngClass]="{'glyphicon-chevron-up':reverse,'glyphicon-chevron-down':!reverse}"></span>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let game of games | orderBy: key : reverse | filter:filter | paginate: { itemsPerPage: 5, currentPage: p }; let i = index">
          <td>{{i}}</td>
          <td>{{game.name}}</td>
          <td>{{game.genre}}</td>
        </tr>
      </tbody>
      <pagination-controls (pageChange)="p = $event"></pagination-controls>
    </table>
  </div>
</div>

Code

You can also browse the entire code on this GitHub Repo.

Conclusion

In this article we have learned arguably the easiest way to implement search sort pagination on a list displayed using ngFor in Angular 2. Hope this tutorial helps you pace up your project development.

 

About

Engineer. Blogger. Thinker. Loves programming and working with emerging tech. We can also talk on Football, Gaming, World Politics, Monetary Systems.

Free PDF

Subscribe and get AngularJS Tips PDF. We never spam!
First Name:
Email:

21 comments

  1. |

    Hi Rahil,

    I appreciate and use the code you publish. I have learned a lot, just using and modifying your code

    • |

      Thanks Don,
      It’s always great to hear from you. 🙂

  2. |

    Thanks a lot for this great explanation and code.

  3. |

    How can i display the pagination on list of rows which are selected from dropdown .
    Lets say if there are 100 records, if i select 15 from dropdown list, it should display in 7 pages,if i select 50 then it should display in 2 pages and if i select 100 it should display only one 1 page.

    • |

      You only need to set the value of itemsPerPage from the component class depending on the value selected in the dropdown.

  4. |

    can we sort the date ,i want the logic to sort the date.

  5. |

    hai rahil amzing tutorial dude, very useful for my team please post more angular 4 ionic 3 blogs we are waiting dude

  6. |

    Works perfectly fine, thanks mate 🙂

  7. |

    The Ordering and Pagination are working as expected but I have an issue using Search functinality,
    When the app is up and running and if I import the modules in app.module.ts and the add the ngClass and other required info to search in mycomponent.html then the search functionality is working fine. But if i have to restart my application using ng serve then it is throwing error mentioned below.
    ERROR in Error: Unexpected value ‘Ng2SearchPipeModule in…..etc

    Why is it behaving like that?
    Do i need to import the search module in mycomponent.ts file as well??

    Please suggest.

    Thanks,
    alreddy

  8. |

    As I am a beginner, tried many sites for pagination, this only worked. Excellent tutorial.
    Thanks

  9. |

    Hi Rahil,

    Thanks for providing such a great tutorial. I am fond of anuglar2/4+ and node based frameworks. Keep posting such helpful blogs.

    Btw, i used search, sort, pagination feature for one my MEAN stack app, i used it with angular 4.

    Everything works as expected except one issue.

    I have below things in my app.

    Search text : which is textbox and used to search on specific field from the data.
    Order By : It has couple of more fields so that user can have specific search.
    Sort Order : As usual “Ascending” and “Descending”

    List of data : I am using bootstrap 4 and having col-md-3 for layout so that my piece of list is divided in 4 different blocks with detailed information.

    Pagination : having pagination using same as your guide above.

    Everything works fine when it loads for first time. My issues is when i change “order by” to other field i.e. industry other than “name”, it starts flashing/flickering the list of data and you feel like data has “earthquake” !!

    It works fine in some pages but not some page. for example, if it works fine in page 1 and 2 but when i go the 3rd page, it starts flickering/flashes the data.

    when i type some data in “search text box”, that issue is no more !, i spend too much time on this issue but couldnt get the solution !

    Can anyone facing such weird issue ? or anyone have idea about how to resolve this issue ?

    I am using Mongo database.

    I appreciate your help. Thanks in advance.

    Best regards,
    Ashish Narola

    • |

      That’s is a really strange issue as you have described. Even I can’t make out what could be the problem. Can you create a fiddle or a plunker and share the link with me?

  10. |

    Its very helpful…

  11. |

    Wow it’s awesome! Very nice examples!
    Maybe you know how to add searching to each column?

  12. |

    Awesome! This help me a lot!! Thanks

  13. |

    This was very useful in my app, this date.
    Thanks a lot.

  14. |

    Unexpected module ‘Ng2SearchPipeModule’ declared by the module ‘AppModule’. Please add a @Pipe/@Directive/@Component annotation.

Leave a Comment