Click here to Skip to main content
15,850,103 members
Articles / Web Development / HTML

Client and Server-Side Data Filtering, Sorting, and Pagination with Angular NgExTable

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
20 Dec 2020CPOL18 min read 61.4K   1.8K   21   3
A custom and configurable Angular data grid tool and demo application presenting both client and server-side data filtering, sorting, and pagination (updated to Angular 11)


Many Angular grid tools are available from either commercial markets or open-sources. Most of these tools, however, are Javascript API type based, with which developers don't have much flexibility to code the own HTML templates and data binding options for the data grid. For my projects, especially migrating from AngularJS with ngTable to Angular, I need a grid tool that can be initiated from the top-level attribute directive of the table template, similar to the legacy ngTable's scenario and template layout, but entirely in the Angular world. Thus, I created my own data grid, the NgExTable, to display a list of data records with data searching, sorting, and pagination capabilities. As the Angular has been evolved to higher versions, the sample demo application has also been upgraded to using the latest Angular 11 CLI. If you need the source code files with the previous Angular versions, please see the details in the History section by end of the article. 

The NgExTable has these features:

  • Using basic HTML code, not the JavaScript API, for the table tag, thus easy to define any own table attributes, styles, and Angular pipes.
  • Simple and straightforward data binding to tr and td tags.
  • Setting column sorting by ColumnSortComponent in the th tag with changeable sorting icons.
  • Multiple-column sorting feature ready but this article and sample application only present the general data grid workflow with the single-column sorting demo (please see the details of multiple-column sorting implementation and descriptions in the article, Multiple Column Sorting: from Angular NgExTable to Source Data List Management).
  • Flexible and customizable pagination components and display.
  • More flexible coding and less code lines in the grid tool consumer-side (see the table-hosting components client-paging.component.ts, server-paging.component.ts, and companion HTML templates for details).
  • Configurable table parameters, especially for those related to searching, sorting, and page-sizing commands.
  • The base library code is within a single folder for easy copying and direct use by any project even on different platforms. Any parts of the code can easily be modified as needed.

Below are all files listed for the NgExTable library and demo project folders:

Set Up and Run Sample Application

The downloaded sources contain two different Visual Studio solution/project types. Please pick up one or both you would like and do the setup on your local machine. You also need the node.js (recommended version 14.x LTS or above) and Angular CLI (recommended version 11.x or above) installed globally on the local machine. Please check the node.js and Angular CLI documents for details.

You may check the available versions of the TypeScript for Visual Studio in the C:\Program Files (x86)\Microsoft SDKs\TypeScript folder. Both ASP.NET and Core types of the sample application set the version of TypeScript for Visual Studio to 4.0 in the TypeScriptToolsVersion node of SM.NgExTable.Web.csproj file. If you don't have the version 4.0 installed, download the installation package from the Microsoft site or install the Visual Studio 2019 version 16.8.x which includes the TypeScript 4.0.


  1. You need to use the Visual Studio 2019 (version 16.8.x) on the local machine. The .NET Core 5.0 SDK is included in the Visual Studio installation.

  2. Download and unzip the source code file to your local work space.

  3. Go to physical location of your local work space, double click the npm_install.bat and ng_build.bat (or ng_build_local.bat if not installing the Angular CLI globally) files sequentially under the SM.NgExTable.Web\AppDev folder.

    NOTE: The ng build command may need to be executed every time after making any change in the TypeScript/JavaScript code, whereas the execution of npm install is just needed whenever there is any update with the node module packages. I do not enable the CLI/Webpack hot module replacement since it could break source code mapping for the debugging in the Visual Studio.

  4. Open the solution with the Visual Studio 2019, and rebuild the solution with the Visual Studio.

  5. Click the IIS Express toolbar command (or press F5) to start the sample application.


  1. Download and unzip the source code file to your local work space.

  2. Go to physical location of your local work space, double click the npm_install.bat and ng_build.bat (or ng_build_local.bat if not installing the Angular CLI globally) files sequentially under the SM.NgExTable.Web\ClientApp folder (also see the same NOTE for setting up the NgTable_AspNetCore_Cli project).

  3. Open the solution with Visual Studio 2017 or 2019 and rebuild the solution with the Visual Studio.

  4. Click the IIS Express toolbar command (or press F5) to start the sample application.

The file contains only the client script source code folders and files. If you use the systems other than the Microsoft platform, you can copy the pure Angular source code files to your own website project, perform the necessary settings using the npm and Angular CLI commands, and then run the sample application on your systems.

The first browser screen of the sample application looks like this:

You don't have to set up the server data source from the data-providing service application for the Server-side Pagination demo. By default, the sample application uses the server-mock-data.service.ts to simulate the server-side data request and response patterns and results. This also benefits the audiences who add the NgExTable_Scripts_AnyPlatform code files into their website project and run for the NgExTable example on their non-Microsoft platforms.

If you run the sample application with Visual Studio and would like to call the real server data source for the Server-side Pagination demo, I recommend performing these steps:

  1. Go to my other article, ASP.NET Core: A Multi-Layer Data Service Application Migrated from ASP.NET Web API and set up the Core API data services following the instructions. Any version of the data service application is a compatible data source provider.

  2. Start the data service application with the Visual Studio and keep the running solution with the IIS Express on the background.

  3. In the ../app/NgExTableDemo/app.config.ts file, change the ServerPagingDataSource from mock to server.

    export const ServerPagingDataSource: 
        string = 'server'; //'mock' for simulating server data.
  4. Edit and enable the active service URL if it's not correct for your settings.

    export const WebApiRootUrl: string = "http://localhost:7200/api/";  //Core 5.0
  5. Rebuild the Angular CLI by executing the ng_build.bat or ng_build_local.bat. 

  6. Press F5 to run the NgExTable demo application, select the Server-side Pagination left menu item, and click the Go button on the Search Products panel.

Table Structures and Working Mechanisms

Most other Angular data grids or tables use the table-level and column-level components based on the Angular component tree structures. However, this implementation approach doesn’t seem flexible for the development of web applications. It limits many custom options that can directly be added into the HTML elements, such as attributes, styles, additional elements in the columns, and even Angular pipes for data binding td tags.

The NgExTable itself is not a top-level Angular component. Instead, it serves as a service that provides add-on services, directives, and unit-processing components. It also relies on events to respond to the user actions and send the parameter data back to the parent table-hosting components. In the sample application, either the ClientPagingComponent or ServerPagingComponent is a table-hosting component.

  1. The main coding structures related to the base table include the table-main-params attribute directive for the table tag, ColumnSort component (column-sort tag) for the th tag, and tableChanged event triggered from the TableMainDirective class. Both client-paging.component.html and server-paging.component.html files (table-hosting views) of the sample application show the example of using the directive, component, and events in the HTML template.

    <table class="table table-condensed table-striped bottom-border top-space"
            [table-main-params]="pagingParams" (tableChanged)="onChangeTable($event)">
                <th>Product Name<column-sort sortBy="ProductName"></column-sort></th>
                <th>Category ID<column-sort sortBy="CategoryId"></column-sort></th>
                <th>Category Name<column-sort sortBy="CategoryName"></column-sort></th>
                <th>Unit Price<column-sort sortBy="UnitPrice"></column-sort></th>      
                <th>Status<column-sort sortBy="StatusDescription"></column-sort></th>
                <th>Available Since<column-sort sortBy="AvailableSince"></column-sort></th>
    		<tr *ngFor="let item of rows; let last = last">
    			<td align="center">{{item.CategoryId}}</td>
                <td>{{item.UnitPrice | currency:"USD":true:"1.2-2"}}</td>
                <td>{{item.AvailableSince | date:"MM/dd/yyyy"}}</td>                
  2. The most important object to pass values back and forth for paging, sorting, and even related to data filtering is the pagingParams. The object definition is defined in the model-interface.ts:

    export interface PagingParams {
        pageSize: number;
        pageNumber: number;
        sortList: Array<SortItem>;
        changeType: any;

    The instance of the PagingParams is initiated in the ngOnInit method of the table-hosting component with seeding values. The object instance is then updated from multiple places, including the table-hosting component and its child structures TableMainDirective and PaginationComponent, in response to user request actions on the page.

    this.pagingParams = {
        pageSize: pageSize !== undefined ? pageSize : 10,
        pageNumber: 1,
        sortList: Array<SortItem>;
        changeType: TableChange.init

    The changeType values are defined in the TableChange enum and updated for corresponding process status.

    export const enum TableChange {
  3. The onChangeTable method in the table-hosting component receives the changes in the pagingParams, performs some tasks, and then calls to get the filtered, sorted, and paginated data items that will be loaded into the table.

    onChangeTable(params: PagingParams ):any {    
        this.pagingParams = params;
        //Perform any needed task.
        - - -
        //Call to get data and bind data items to table.
        - - -     
  4. The sortBy value for the colulmn-sort tag is passed to the ColumnSortComponent class in which the sorted column definition and CSS icon are set. The sortBy and sortDirection values in the sortItem element of pagingParams.sortList array object that has been updated in response to user request actions are also sent back to the table-hosting component relayed by the sortChanged method and tableChanged events in the TableMainDirective class sequentially (see the details in the column-sort.component.ts and table-main.directives.ts if you are interested in the deep levels of the code logic).

  5. For any column that should be non-sortable, the column-sort tag can simply be omitted in the th tag.

Pagination Component

The pager is a separate component with its view template consisting of detailed HTML elements. It uses the similar workflow as the main table, i.e., passing the pagingParams object instance as the @Input to the PaginationComponent class and sending changed object instance back via the pageChanged event. The code in the table-hosting view is simple and straightforward:

<pagination *ngIf="pagingEnabled"

The pageChanged event is routed to the same target method, onChangeTable, as the tableChanged event for the main table due to possible linked code logic among the page number, size, and sorting changes. You can use a different method, such as onChangePage, for the pager if you would like.

The page number is changed and number button highlighted from two triggering sources.

  1. Clicking on any page number. This will directly call the selectPage method in the PaginationComponent. The code workflow is all within the pager so that you usually don’t have to care for it in the client calling logic.

    In view template:

    (click)="selectPage(page.number, $event)"

    In PaginationComponent class:

    selectPage(pageNumber: number, event?: Event): void {
        if (event) {
            //Set change type.
            this.pagingParams.changeType = TableChange.pageNumber;        
        //Update value in base pagingParams.
        this.pagingParams.pageNumber = pageNumber;
        //Set pagers.
        this.pages = this.getPages(); 
        //Fire event for pageNumber changeType.
        if (params.changeType == TableChange.pageNumber || 
                          params.changeType == TableChange.init)     {            
  2. Other operations, such as data filtering, sorting, or page-size changes. The page number should passively be reset and correct number button highlighted. You need to add code into the table-hosting component and execute the code in the corresponding event methods, such as onTableChange, or in the processing logic after the data access. For example, the below code block in the table-hosting component calls the selectPage method in the PaginationComponent through a method of TableMainDirective for updating the pager (also see details in the later section, Reset Pager Routines):

    //Call to update pager. 
    this.tableMainDirective.updatePagerAfterData(this.pagingParams, this.totalLength);

The change in page size is another user action on the pager. When selecting the number from the page-size dropdown, the onSizeChange event is triggered and corresponding processes are followed.

In the select tag of the pagination.component.html template:


In PaginationComponent class:

onSizeChange(event: any) {
    this.pagingParams.pageSize = event.value;
    //Set change type.
    this.pagingParams.changeType = TableChange.pageSize;
    - - -
    //Emit event for refresh data and pager.

You also don’t have to touch the code within the PaginationComponent. In the pageChanged target method of the table-hosting component, however, you may need to call the setPagerForSizeChange method from the PaginationComponent in addition to the data access process using the changed page-size value (see the method in the pagination.component.ts file for details if interested).

if (this.pagingParams.changeType == TableChange.pageSize) {    
    //Reset pager.            

The built-in pager HTML template is included in the NgExTable folder. If you would like to update it with different layout and styles, you can directly edit the built-in template or replace it with your own one in the existing NgExTable folder.

Search Component

Strictly speaking, the search component is an application unit separate from the NgExTable. I did not directly add the filtering feature into the columns since there are limited options when using the in-grid-column data filtering. I'd rather implement a search engine with the Angular component code. The SearchComponent class and its HTML view template in the sample application demonstrate how the structures and functionalities link to the NgExTable and perform the data filtering for the paginated grid display.

  1. Versatile input types are shown including multiple dropdown selections and double date pickers for date range entries. The inputs will be constructed as a JSON string or object which in turn serves as the request parameter object for the data retrieval. The structures and code are actually migrated from the AngulaJS Search Penal to the Angular search component.

  2. In the table-hosting view, the search tag is defined like this:

    <search *ngIf="searchEnabled" 
            [searchType] = "searchType"
  3. The searchChanged event is raised with searchParams object passed to the target method.

    //Construct searchParams object with input data.
    - - -
    //Raise event
  4. In the event target method, onChangeSearch, of the table-hosting component, conduct the processing logic and make the AJAX call for the data result set.

    onChangeSearch(searchParams: any) {
        this.pagingParams.changeType =;
        this.searchParams = searchParams;
        //Convert searchParams to request JSON object.
        - - - 
        //AJAX call to get data result sets.
        - - -
  5. Unlike the PaginationComponent, you are responsible for coding all pieces of the SearchComponent, its view template, and related parts in the table-hosting component. You also need to take care of some differences of searching logic between the client-side and server-side paginations.

  6. As for changes in page number, size, and sorting, changes for the searching also needs to reset the pager before and after the data access. You can call the methods in the TableMainDirective as described in the later section, Reset Pager Routines.

Column-Sorting Component

The ColumnSortingComponent object instance is created one for each column on which the sorting is enabled by specifying the <column-sort> tag for the column. The component sets the sorting icons on the column header. Clicking the icon will send the changing sorting command for the ascending, descending, and no-sorting operations. The toggleSort method in the component class handles these operations.

toggleSort() {
    let pThis: any = this;                
    switch (this.sortableItem.sortDirection) {
        case "asc":
            this.sortableItem.sortDirection = "desc";
        case "desc":
            this.sortableItem.sortDirection = this.toggleWithOriginalDataOrder ? "" : "asc";
            //Existing sortDirection is ''.
            this.sortableItem.sortDirection = "asc";
    - - -

The styles of sorting icons are also changed with the user actions by calling the refreshSortingIcon method in the TableMainDirective class.

refreshSortingIcon() {
    //For both single and multiple column sorting.
    if (this.sortableItem.sequence == -1) {
        this.sortableItem.sortDirection = '';

    if (this.sortableItem.sortDirection == '') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
    else if (this.sortableItem.sortDirection == 'asc') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
    else if (this.sortableItem.sortDirection == 'desc') {
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingBaseIcon);
        this.renderer.removeClass(this.sortIcon.nativeElement, this.config.sortingAscIcon);
        this.renderer.addClass(this.sortIcon.nativeElement, this.config.sortingDescIcon);

The NgExTable since Angular version 8 supports the multiple-column sorting feature. The NgExTable library and demo project in Angular version 8 or beyond contain all components and options for processing the multiple-column sorting logic and operations. This article here, however, still focuses on the general workflow and processing logic of the base data grid tool. The companion sample application to this article also sets for the single-column sorting type demo only. Detailed discussions and feature demonstrations on the multiple-column sorting is in another dedicated article, Multiple Column Sorting: from Angular NgExTable to Source Data List Management.

Client-Side Pagination Workflow

The client-side pagination pattern loads all data records to the client cache and displays one page at a time based on the page-size setting. In the sample application, the client-paging.component.ts and associated files and structures show how this pattern works to obtain and display the filtered, sorted, and paginated data.

  1. The AJAX call occurs at the very first time for all needed data list items without using the search component. The local data source in the local-data-products.json file is used for the demo (see the getData method for details).

  2. Call the clientPaginationService.processData method with passing the pagingParams object instance to get the data rows from the cached data sources for the active page, and then set the paged data rows that are loaded to the table.

    let rtn = this.clientPaginationService.processData
                   (this.pagingParams, this.currentDataList);
    this.rows = rtn.dataList;
  3. When the user performs a filter operation, the filter criteria is passed to the searchChanged event target method in which the logic basically repeats the step #2 described above. The paged data rows are refreshed based on the filter criteria.

  4. The pager buttons and labels will then be refreshed accordingly with the data row changes.

  5. When the user clicks another page number button, changes column sorting, or selects a different page size, the updated information in the pagingParams object instance is passed to the tableChanged event target method. The logic in the method then repeats similar processes to those in the steps #2 - 4 above. The table rows and pager are then refreshed again for the new request.

Server-Side Pagination Workflow

For applications, especially enterprise business applications that use databases in large or dynamically increased size, the server-side pagination pattern is the practical and optimal solution due to its chunked data processing and transfer per request. The server-paging.component.ts and associated files and structures in the sample application show how this pattern works to obtain and display the filtered, sorted, and paginated data.

  1. The pagingParams and/or searchParams with default or updated values, are always set and used for each AJAX call to obtain the paged data rows.

  2. The request object, input, is constructed before making any data access call (see the getProductListRequest method for details).

    let input = this.getProductListRequest();
  3. The AJAX call is executed at the beginning and whenever the user performs the search action, changes the page number, applies the column sorting, or selects a different page-size. The returned response object, data, includes the data list only for the active page and the total count of the data rows in the database based on the search criteria. The sample application can use the mock server data by calling the serverMockDataService.getPagedDataList() method which retrieves the data from the local data source with the same server-side pagination pattern. Here is the conditional code logic:

    let pThis = this;
    if (ServerPagingDataSource == 'server') {
        let input = this.getProductListRequest();, input) //Real server data
        .subscribe(data => {
            pThis.processDataReturn(pThis, data);
        - - -     
    else if (ServerPagingDataSource == 'mock') { 
        //Mock server data
        this.serverMockDataService.getPagedDataList(this.searchParams, this.pagingParams)  
        .subscribe(data => {
            pThis.processDataReturn(pThis, data);
        - - - 
  4. The pager buttons and labels will then be refreshed accordingly with the data row changes.

Reset Pager Routines

Resetting paging parameters and pager for the change in page number can directly be done within the PaginationComponent. However, for other operations, such as searching, sorting, and even for page size changes, the code logic in the table-hosting component side needs to cooperate with those child components, which could make the code bulky and repeated in the client callers. To resolve the issue, and rather than creating an additional Angular service, the centralized methods are added into the TableMainDirective class for communications between the table-hosting components and different child components.

There are two methods that need to be called before and after the data access, respectively.

  1. The setPagingParamsBeforeData() method for re-setting pagingParams based on some conditions and configuration values.

    //Method called from table-hosting component before data retrieval.
    setPagingParamsBeforeData(pagingParams?: PagingParams) {
        - - -
        if (pagingParams.changeType == {
            //Set pageNumber based on config.
            if (this.config.pageNumberWhenSearchChange != -1) {
                pagingParams.pageNumber = this.config.pageNumberWhenSearchChange;
            //Set to init sortList if not using current.
            if (this.config.sortingWhenSearchChange != "current") {
            - - -
        else if (pagingParams.changeType == TableChange.sorting) {
            ////For sorting change, set pageNumber based on config.
            if (this.config.pageNumberWhenSortingChange != -1) {
                pagingParams.pageNumber = this.config.pageNumberWhenSortingChange;
        else if (pagingParams.changeType == TableChange.pageSize) {
            //Set to init sortList if not using current.
            if (this.config.sortingWhenPageSizeChange != "current") {
            - - -
  2. The updatePagerAfterData method for updating the pageNumber based on obtained the data returns.

    //Method called from table-hosting component after obtaining data.
    updatePagerAfterData(pagingParams: PagingParams, totalLength: number) {
        if (pagingParams.changeType == {
            //Data items can only fit page 1.
            if (totalLength && 
             (totalLength <= pagingParams.pageSize && pagingParams.pageNumber != 1)) {
                pagingParams.pageNumber = 1;
            //Call PaginationComponent to set changeType and run selectPage method.
            this.updatePagerForChangeType(pagingParams.changeType, pagingParams.pageNumber);
        else {
            //For sorting or pageSize change, set pageNumber based on config.
            if (pagingParams.changeType == TableChange.sorting ||
                pagingParams.changeType == TableChange.pageSize) {
                     (pagingParams.changeType, pagingParams.pageNumber);
    //Generic method.
    private updatePagerForChangeType(changeType: TableChange, pageNumber: number) {
        //Call PaginationComponent to set changeType and run selectPage method.
        let paramesToUpdatePager: ParamsToUpdatePager = {
            changeType: changeType,
            pageNumber: pageNumber
               ('tableMain_paginationComponent', paramesToUpdatePager);

    Note that the rxjs Subject and Observable based messageService (message-transfer.service.ts) is used to access any other components, such as PaginationComponent, from the TableMainDirective.

As a general rule, you need to call these two methods only at two places in a table-hosting component:

  1. onChangeSearch event handler method called before data access:

    onChangeSearch(searchParams: any) {
        - - -
        //Call for data here.

    Note that you don't have to call this method in the onChangeTable event handler method. The setPagingParamsBeforeData() is internally called during the table-changed processes in the TableMainDirective class itself.

  2. The place after having obtained data and known the total record length of the dataset:

    //Call library method to update pager. Also need to pass current data length.
    this.tableMainDirective.updatePagerAfterData(this.pagingParams, this.totalLength); 


The sample application shows two-levels of configurations for using the NgExTable.

  • Library-level: NgExTable/ngex-table.config.ts
  • Consumer-level: NgExTableDemo/app.config.ts

The configuration items are merged in starting AppComponent class by assigning the consumer-level TableConfig object to the library-level NgExTableConfig.base object. If there are the same key names, the consumer-level settings always take precedence.

The code in the app.component.ts:

this.ngExTableConfig.appConfig = TableConfig;

The code in the ngex-table.config.ts:

private _appConfig: any = {};
get appConfig(): any {
    return this._appConfig;
set appConfig(v: any) {
    this._appConfig = v;
    this.main = Object.keys(this._appConfig).length ? 
    Object.assign(this.base, this._appConfig) : this.base;

Below is the entire TableConfig object for the configurations in the consumer-level. The comment lines illustrate all details of configuration items. Usually, you just need to make any value change in this file if you would like. You don’t have to modify any default setting in the library-level NgExTableConfig class.

//Remove or comment out item for using default setting.
export const TableConfig: any = {    
    pageSize: 10, /* number: number of rows per page */

    toggleWithOriginalDataOrder: true, /*boolean: true: no order, ascending, 
                                         and descending; false: ascending and descending */

    previousText: "&laquo;", /*string: page previous button label. Could be "PREV" */

    nextText: "&raquo;", /*string: page next button label. Could be "NEXT" */

    paginationMaxBlocks: 5, /* number: maximum number of page number buttons if "..." 
                               shown on both sides */

    paginationMinBlocks: 2, /* number: minimum number of page number buttons if "..." 
                               shown on both sides */

    pageNumberWhenPageSizeChange: -1, /*number: 1: reset to first page when changing page size; 
                                       -1: use current pageNumber */

    pageNumberWhenSortingChange: 1, /*number: 1: reset to first page 
                                      when changing column sorting; 
                                      -1: use current pageNumber */

    sortingWhenPageSizeChange: "current", /*string: "": reset to no order 
                                            when changing page size; 
                                            "current": use current sorting */
    //Related to data search (no default setting in library-level, NgExTableConfig base).
    pageNumberWhenSearchChange: 1, /*number: 1: reset to first page when 
                                     search or filtering change; 
                                     -1: use current pageNumber */

    sortingWhenSearchChange: "", /*string: "": reset to no order 
                                   when search or filtering change; 
                                   "current": use current sorting */

    sortingIconCssLib: "fa", /*string, or custom value */  

    sortingAscIcon: "fa-chevron-up", /*string, or custom value */

    sortingDescIcon: "fa-chevron-down", /*string, or custom value */ 

    sortingBaseIcon: "fa-sort", /*string, or custom value */ 

    sortingIconColor: "#c5c5c5" /*string: "#c5c5c5" (default), "#999999", or custom value */    

Guidelines for Styles

Since the updates in June 2019, the NgExTable uses the bootstrap.css version 4.3.1 as the base styles for both the grid and pager. It also uses the application-level custom site.css to overwrite some default styles from the bootstrap.css, or add some new styles when needed (see details in the site.css file). In the sample application, those css files are imported to the style.css file defined by the Angular CLI. The style class code with Scalable Vector Graphics (SVG) icon libraries are bundled to the dist directory after the build.

/* You can add global styles to this file, and also import other style files */
@import "~bootstrap/dist/css/bootstrap.css";
@import "~font-awesome/css/font-awesome.css";
@import "assets/site.css";

You can either modify the site.css or add your own global or local (component-level) css files for your needs. The typical example is to set the background color for alternative rows in the table. The bootstrap.css sets the default class and values like this:

.table-striped > tbody > tr:nth-of-type(odd) {
    background-color: #f9f9f9;

You can copy this class code to the site.css or local CSS file, and replace the “odd” with the “even” to apply the alternative color to the even numbers of rows. You may change to different color for the alternative rows by modifying the background-color value in the global or local CSS file.


The NgExTable is the directive-initiated and HTML template-based Angular grid tool. It's practical and optimal for business web applications, especially those using AngularJS ngTable that would be migrated to the Angular versions. The discussions in this article and the demo of the sample application can help developers better understand the structures and workflow of the NgExTable with the client-side and server-side pagination patterns so that developers can effectively incorporate the NgExTable into their own projects. Developers can even add new, and/or modify existing structures to meet their needs. For a user case with advanced practices of the NgExTable, please see this post Angular Data CRUD with Advanced Practices of Reactive Forms.


  • 7th February, 2018
    • Original post with Angular 5 
  • 5th December, 2018
    • Updated source code
    • Upgraded to Angular 6 CLI setup
    • Added section Reset Pager Service, and
    • edited some other sections
  • 19th June, 2019
    • Updated source code in Angular 7 and Bootstrap 4.3 (Download
    • Changed ResetPageService to calling built-in methods in the TableMainDirective class
    • Added ColumnSortingComponent to handle sorting processes
    • Edited or re-wrote text in many sections
  • 28th September, 2019
    • Updated source code in Angular 8
    • Edited text in some sections
  • 20th December, 2020

    • Updated source code with the Angular 11 CLI
    • Updated website project with the ASP.NET Core 5.0
    • Edited article text in some sections for the updates
    • If you need to run the sample application with previous Angular version 8, 9, or 10, you can download the package.json file for the application,, replace the package.json file in the existing application with the version you would like, than do the same based on the instructions in the Set Up and Run Sample Application section. The Angular 11 source code of the sample application is fully compatible with the Angular version 8, 9, and 10 without major breaking changes.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
United States United States
Shenwei is a software developer and architect, and has been working on business applications using Microsoft and Oracle technologies since 1996. He obtained Microsoft Certified Systems Engineer (MCSE) in 1998 and Microsoft Certified Solution Developer (MCSD) in 1999. He has experience in ASP.NET, C#, Visual Basic, Windows and Web Services, Silverlight, WPF, JavaScript/AJAX, HTML, SQL Server, and Oracle.

Comments and Discussions

QuestionThanks anything on Grouping? Pin
gudimanojreddy24-May-18 11:09
gudimanojreddy24-May-18 11:09 
AnswerRe: Thanks anything on Grouping? Pin
Shenwei Liu5-Dec-18 18:55
Shenwei Liu5-Dec-18 18:55 
PraiseClient and Server-Side Data Filtering, Sorting, and Pagination with Angular NgExTable Pin
Member 1368468318-Feb-18 21:01
Member 1368468318-Feb-18 21:01 
Well explained and informative article. Thanks a lot for sharing this valuable post.Latest servers for rentals in bangalore

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.