Posts on this Category



Creating an Angular2 Datatable from Scratch PART 2: Filtering the Data

Continuing from where we stopped at Part 1, today we’re going to add the filter functionality to the table, but before we start let’s do some styling. I’m going to use Bootstrap, but if you don’t want you don’t have to, feel free to style your component as you like. But if you decide to use Bootstrap as well you’ll have to add these scripts to your index.html:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" >
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" ></script>

Now I’m going to add the class table to the table element in the DatatableComponent, this class will make the grid look like this:

Table With Bootstrap Style
Table With Bootstrap Style

It looks so much better already! Now let’s get started with the filter, most of the modifications will be made on the datatable.component.ts file, here’s the component after the changes:

import {Http, Response} from '@angular/http';
import {Injectable, Component, Input} from '@angular/core';
import {ColumnComponent} from './column.component';
 
@Component({
  selector: 'datatable',
  template: `
      <input type="text" class="form-control" *ngIf=enableFilter [(ngModel)]=query 
         (keyup)=filter() placeholder="Filter" />
      <table class="table">
        <thead>
          <tr>
            <th *ngFor="let column of columns">{{column.header}}</th>
          </tr>
        </thead>
        <tbody *ngFor="let row of getData()">
	  <tr>
	    <td *ngFor="let column of columns">{{row[column.value]}}</td>
	  </tr>
        </tbody>
      </table>
  `
})
export class DatatableComponent { 
 
  @Input() dataset;
  @Input() enableFilter = false;
  columns: ColumnComponent[] = [];
  query = "";
  filteredList;
 
  addColumn(column){
    this.columns.push(column);
  }
 
  getData(){
    if(this.query !== ""){
      return this.filteredList;
    }else{
      return this.dataset;  
    }
  }
 
  filter(){
    this.filteredList = this.dataset.filter(function(el){
      var result="";
        for(var key in el){
          result+= el[key];
        }
        return result.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
    }.bind(this));
  }
}

Let’s start by talking about the new attributes:

enableFilter: This is an input that indicates weather the filter is going to be enabled or not, it’s false by default.
query: Variable that stores the filter typed by the user.
filteredList: This variable is going to store the new dataset after the filtering, it’s necessary to use a different array in order to preserve the original data.

Now the new functions:

filter(): Function responsible for filtering the data when the user types something on the input, it’ll filter the data from the dataset variable and assign the result to the filteredList.
getData(): Our table will work simultaneously with two arrays, the dataset and filteredList, this function is responsible for telling which one will be used at any given moment, if the table has filtered data to show it’ll use the filteredList otherwise it’ll use the dataset.

Besides the new attributes and functions I’ve also modified the template a little bit, in the tbody I’ve change the ngFor from let row of dataset to let row of getData(), and as you can see there is an input above the table where the user can type, it’ll call the filter() function on the keyup event and it’ll only be visible when the enableFilter is true (For this input to work we also have to import the FormsModule on the app.module.js).

Now your datatable can be used like this:

<datatable [dataset]=cities [enableFilter]=true >
      <column [value]="'id'" [header]="'Id'"></column>
      <column [value]="'city'" [header]="'City'"></column>
      <column [value]="'country'" [header]="'Country'"></column>
 </datatable>

The only thing that has changed is that now we have the option enableFilter that will render an input above the table when set to true.

Creating an Angular2 Datatable from Scratch

Some time ago I’ve posted a tutorial on how to create a datatable with ag-grid, it didn’t take long before people start complaining that ag-grid wasn’t working the way I described in the tutorial, that’s because angular2 is changing rapidly rendering the component and/or the tutorial obsolete.

Today I’m going to show you again how to create a datatable, but this time I’m going to do it without ag-grid or any other third-party component, this is how our component will look like after we’re done:

<datatable [dataset]=cities>
    <column [value]="'id'" [header]="'Id'"></column>
    <column [value]="'city'" [header]="'City'"></column>
    <column [value]="'country'" [header]="'Country'"></column>
</datatable>

That’s the simplest datatable structure I could come up with, as you can see we’re going to create two components: datatable and column.

Let’s take a look at the column component first:

import {Component, Input} from '@angular/core';
import {DatatableComponent} from './datatable.component';
 
@Component({
  selector: 'column',
  template: ``,
 
})
export class ColumnComponent {
	@Input() value;
	@Input() header;
 
	constructor(table: DatatableComponent) {
    	   table.addColumn(this)
  	}
}

This component has two attributes as inputs: value and header.

Value: This is the name of the attribute from your JSON dataset that will be displayed on this column (I’m going to talk about the dataset in a minute).
Header: As the name suggests, this is the column header text.

Besides these two attributes, there’s also the constructor, which very conveniently injects the DatatableComponent, allowing us to add this column to an array from the parent component. To fully understand why I’m doing this we have to take a look at the DatatableComponent:

import {Http, Response} from '@angular/http';
import {Injectable, Component, Input} from '@angular/core';
import {ColumnComponent} from './column.component';
 
@Component({
  selector: 'datatable',
  template: `<table>
               <thead>
                 <tr>
                   <th *ngFor="let column of columns">{{column.header}}</th>
                 </tr>
               </thead>
	       <tbody *ngFor="let row of dataset">
	  	 <tr>
	  	   <td *ngFor="let column of columns">{{row[column.value]}}</td>
	         </tr>
	       </tbody>
  	     </table>
  `
})
export class DatatableComponent { 
 
  @Input() dataset;
  columns: ColumnComponent[] = [];
 
  addColumn(column){
    this.columns.push(column);
  }
}

In this component we have the attribute dataset as an input (which will receive the JSON dataset) and the array of columns I just mentioned, each column will add itself to this array as we’ve seen on the ColumnComponent‘s constructor. In the template I’m iterating over both the dataset (rows) and the columns to create a regular HTML table.

After creating these two components we can use the datatable as I showed in the beginning, here is the app.component.ts:

import {Http, Response} from '@angular/http';
import {Injectable, Component } from '@angular/core';
 
@Component({
  selector: 'my-app',
  template: `<datatable [dataset]=cities>
                  <column [value]="'id'" [header]="'Id'"></column>
                  <column [value]="'city'" [header]="'City'"></column>
                  <column [value]="'country'" [header]="'Country'"></column>
             </datatable>
             `,
 
})
export class AppComponent { 
    cities;
 
    constructor(private http:Http) {
        this.http.get('data/cities.json')
                .subscribe(res => this.cities = res.json());
    }
}

Note that in the constructor I’m loading my dataset from the file cities.json, for more datails about that click here.

We’re almost done, the last thing we have to do is to declare our components in the app.module.ts:

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule }      from '@angular/http';
import { AppComponent }   from './app.component';
import {DatatableComponent} from './datatable.component';
import {ColumnComponent} from './column.component';
 
@NgModule({
  imports:      [ BrowserModule, HttpModule ],
  declarations: [ AppComponent,DatatableComponent, ColumnComponent],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

That’s it for now guys! Click here to see the part 2 of this tutorial where I’m showing how to add the filter functionality, if you need other specific features please let me know in the comments, any suggestions are welcome!

Tutorial: Angular 2 Datatable with Sorting, Filtering and Resizable Columns

In this tutorial I’m going to show you one of the datatable solutions available for Angular 2, it’s called ag-Grid and it’s available not only for angular2, but also for other frameworks like Angular 1 and React. It’s a really complete datatable, it has lots of features that I definitely want to explore in other tutorials, but in this one I’ll try to keep it as simple as possible, my goal is to just make it display the data and enable the sorting, filtering and the resizable columns features. Maybe in the future I’ll write a PART 2 of this tutorial explaining how to add some more complex functionality. If you don’t want to use ag-grid take a look at this post.

I’m assuming you already have your Angular 2 project ready, but if you don’t just follow the instructions on this link to set up your environment, it’s really easy and straightforward, all you need to do is to create three configurations files: tsconfig.json, typings.json and package.json, make sure you have all of them before you continue.

Now let’s add the ag-grid to the project, to do that you have to add these two lines to the dependencies list on your package.json:

{
  "dependencies": {
    "ag-grid": "3.3.x",
    "ag-grid-ng2": "3.3.x"
    ...
  },
}

Run npm install to download and install the dependencies, when it completes we’ll be ready to start writing some code!

Open your project folder and create another one inside called app, that’s where we’re going to put all our Angular2 related code. The first file we are going to create on this folder is the app.component.ts, it should contain the following code, just copy and paste it into the file:

import {Component} from 'angular2/core';
import {AgGridNg2} from 'ag-grid-ng2/main';
 
@Component({
    selector: 'my-datatable',
    directives: [AgGridNg2],
    template: `
         <ag-grid-ng2 #agGrid style="height:100%;width:845px" class="ag-fresh"
            [gridOptions]="gridOptions">
         </ag-grid-ng2 >
     `
})
 
export class AppComponent { 
    myRowData = [
        {"name":"Ronald Bowman","country":"China","city":"Lutou","email":"rbowman0@spotify.com"},
        {"name":"Pamela Hill","country":"Russia","city":"Krylovskaya","email":"phill1@symantec.com"},
        {"name":"Robin Andrews","country":"Ukraine","city":"Korop","email":"randrews2@photobucket.com"},
        {"name":"Peter Kim","country":"Mexico","city":"San Jose","email":"pkim3@theatlantic.com"},
        {"name":"Carol Foster","country":"Mexico","city":"El Aguacate","email":"cfoster8@intel.com"},
        {"name":"Jimmy Burke","country":"Indonesia","city":"Banjarsari","email":"jburke9@over-blog.com"},
        {"name":"Jonathan Crawford","country":"Peru","city":"Alca","email":"jcrawforda@deliciousdays.com"},
        {"name":"Donald Montgomery","country":"Poland","city":"Działoszyce","email":"dmontgomeryb@google.com.br"},
        {"name":"Donna Shaw","country":"Japan","city":"Akune","email":"dshawc@chronoengine.com"},
        {"name":"Helen King","country":"United States","city":"Hollywood","email":"hkingd@devhub.com"},
        {"name":"Walter Myers","country":"China","city":"a ndaowa n", "email":"wmyerse@state.tx.us"},
        {"name":" Alice Collins","country":"Papua Nw  Guine a", "city":"Mendi","email":"acollinsf@npr.org"},
        {"name":"Anne Richards","country":"China","city":"Koramlik","email":"arichardsu@vinaora.com"},
        {"name":"Randy Miller","country":"Indonesia","city":"Trenggulunan","email":"rmillerv@oakley.com"},
        {"name":"Phillip Adams","country":"Bahamas","city":"Duncan Town","email":"padamsw@lycos.com"},
        {"name":"Nicholas Allen","country":"Philippines","city":"Bautista","email":"nallenx@aboutads.info"},
        {"name":"Lisa Willis","country":"Thailand","city":"Lat Yao","email":"lwillisy@istockphoto.com"},
        {"name":"Jeffrey Castillo","country":"Indonesia","city":"Karangsari","email":"jcastilloz@washington.edu"},
        {"name":"Michael Carpenter","country":"Colombia","city":"Cali","email":"mcarpenter13@prlog.org"},
        {"name":"Roger Lee","country":"France","city":"Courtaboeuf","email":"rlee14@earthlink.net"},
        {"name":"Steve Wallace","country":"Russia","city":"Novobeysugskaya","email":"swallace15@cisco.com"},
        {"name":"Shirley Patterson","country":"Peru","city":"La Tinguiña","email":"spatterson16@woothemes.com"},
        {"name":"Nancy Ward","country":"Sweden","city":"Båstad","email":"nward17@mapquest.com"}
    ];
 
    columnDefs = [
        {headerName: 'Name', field: "name", width: 200 },
        {headerName: 'Country', field: "country" ,width:180},
        {headerName: 'City', field: "city" ,width:160},
        {headerName: 'e-mail', field: "email" ,width:300}
    ];
 
    gridOptions = [];
 
    constructor() {
        this.gridOptions = {
            rowData: this.myRowData,
            columnDefs: this.columnDefs,
            enableColResize: true,
            enableSorting: true,
            enableFilter: true
        }   
    }
}

There is quite a lot to explain about this code, I’m going to start with the class AppComponent, that’s where we’re going to put all the parameters and configurations that makes our datatable work, we have three variables on this class: myRowData, columnDefs and gridOptions.

myRowData: This variable will store the data to be displayed on the table, for this example I’m just going to use some JSON data I generated, but in a real case scenario you would get the data from your database or a web service.

columnDefs: This one will store an object containing information about every column of our datatable, I’m just using the attributes headerName to define the column title, field to tell which array attribute will be displayed on the column and width to define the size of each column. Although I’m using only three, there are tons of other attributes you could specify here.

gridOptions: This one just wraps everything up and make it available to the component. It’s being initialized empty, but in the constructor I’m setting its value, besides the rowData and columnDefs I’m also specifying the attributes enableColResize, enaleSorting and enableFilter. They will do exactly what their names suggests, enable the columns resize, sorting and filtering features respectively.

Now we can create our component, to be able to use the directive AgGridNg2 we have import it first with the line import {AgGridNg2} from 'ag-grid-ng2/main, after that all you need to do is to pass the gridOptions to the ag-grid-ng2 component on the template.

Before we write the index.html we’re going to need one more ts file to be able to launch our application, on your app folder create the file main.ts and paste the following code into it:

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
 
bootstrap(AppComponent);

Now you can create the index.html file on your project root folder, let’s take a look at the code:

<html>
  <head>
    <title>Datatable</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
    <link href="node_modules/ag-grid/dist/styles/ag-grid.css" rel="stylesheet" />
    <link href="node_modules/ag-grid/dist/styles/theme-fresh.css" rel="stylesheet" />
    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          },        
          lib: {
            format: 'register',
            defaultExtension: 'js'
          },
          'ag-grid-ng2': {
            defaultExtension: "js"
          },
          'ag-grid': {
            defaultExtension: "js"
          }
        },
        map: {
            'ag-grid-ng2': 'node_modules/ag-grid-ng2',
            'ag-grid': 'node_modules/ag-grid'
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>
  </head>
  <body>
    <my-datatable>Loading...</my-datatable>
  </body>
</html>

This file will basically be used just to load the necessary scripts and to configure SystemJS to load everything and launch the application. Finally you can use your component here calling it by the selector we specified before: my-datatable.

We’re done!! To run the project open your terminal, navigate to your project folder and run npm start, this should be the result: my-datatable.

Angular 2 Datatable with Sorting, Filtering and Resizable Columns
Angular 2 Datatable with Sorting, Filtering and Resizable Columns