Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Angular2 Grid Component - Auto Generated

4.87/5 (11 votes)
9 Aug 2016CPOL3 min read 31.6K   791  
Angular2 grid component

Introduction

As I started working on my first Angular2 project, coming from a .NET web development background, I instantly started missing the .NET controls, which used to provide me with a lot of awesome fast and quick components, that you can use and save a lot of time.

The first component that I really missed was the GridView which I used whenever I simply needed to display some data.... so I decided to create my own GridView using Angular2.

I want this component to do the following:

  • Fast and simple data display, no HTML writing at all
  • Provide paging and row deleting without no extra hassle
  • Can be reused, so it has to be customizable according to the different situation

Needed configuration:

  • Excel like column sorting on client side, with the option to switch it on/off
  • Control which columns you want to sort
  • Control allow row delete on/off
  • Pagination ability to add client side / server side searching

Image 1

Background

Beginner level in Angular2, JavaScript and ES6. So if you can create a basic simple Angular2 application and know how to use a component, you are good to go.

Using the Code

Image 2

In order to use this component, you simply need to include inside your application, the component is using a sorting pipe and the data is passed to the component from the main component.

Parts of the code are as follows:

  • AutoGrid.ts
  • AutoGrid.html (optional - HTML can be inside template attribute instead)
  • AutoGridPipe.ts
  • MainComponent.ts (where the component is used and how to call it)

Component Code (AutoGrid.ts)

Needed Packages
JavaScript
import {Component,Input} from '@angular/core';
//Needed for triggering events
import {Subject}    from 'rxjs/Subject';
import {Observable}     from 'rxjs/Observable';
//Needed for sorting
import {AutoGridPipe} from './AutoGridPipe';
Component Block
JavaScript
@Component({
  selector : 'AutoGrid',//tag name in when calling the component
  templateUrl : 'AutoGrid.html',//path of the HTML can be replaced with template:...
  pipes : [AutoGridPipe]//link the Pipe
})
Class

Using @Input() variables, we can configure the behaviour of the component:

  • AllowSorting: Main sorting control, switch on/off sorting option at columns
  • AllowDelete: Hide/display a delete button
  • Columns: Set which columns to display, and which of them are sortable or not

Using Subject variable to trigger events in the main component:

  • RowDeleted$: Once triggered, will hold the whole deleted row
  • PageIndexChanged$: Once triggered, will hold the new page index (starting from 0)
JavaScript
export class AutoGrid
{
  SortBy : string = "";//This param is used to determine which column to use for sorting
  Direction: number = 1;//1 is ascending -1 is descending

  Pages : any = [];//Dummy array to load the pagination
  Data : any = [];//Main data container
  Width:string;
  @Input() AllowDelete : boolean= true;//Can a row be deleted
  @Input() Columns : any = [];//Name of the columns to display / order
  @Input() AllowSorting : boolean= true;//Allow client side sorting
  @Input() TotalRows : number = 0;//Total number of rows for paging
  @Input() PageSize : number = 0;
  @Input() PageIndex : number = 0;//To control the start page index

  public RowDeleted$ = new Subject<any>();//Subscribe to this to handle delete event
  public PageIndexChanged$ = new Subject<any>();//Subscribe to this to handle "page index change" event

  public LoadData(_data : any)
  {//Main method to load the data
    this.Data = _data;
  }
  OnDeleteRow(Row2Delete:any)
  {//private method to raise RowDeleted
    this.RowDeleted$.next(Row2Delete);
    //client side delete for data can be done here
  }
  OnPageIndexChange(index:number)
  {//private method to raise RowDeleted
    console.log(index);
    this.PageIndex = index-1;
    this.Data = [];
    this.PageIndexChanged$.next(index-1);
  }
  ngOnInit(){
    //used for pagination style
    let totalPages : any = (this.TotalRows / this.PageSize);
    this.Width = ((totalPages * 38) + totalPages * 2) + "px";
  }
  ngAfterViewInit() {
    //fill the dummy array
    for(let i=0;i<this.TotalRows / this.PageSize;i++)
    this.Pages.push(i+1);
  }
   Sort(key:string,dir:number){
     //Change the sorting criteria
       this.SortBy = key;
       this.Direction = dir;
   }
}

Component HTML (AutoGrid.html)

As mentioned before, the whole HTML part can be moved into AutoGrid.ts inside template parameter.

HTML
<!--Just to give the pagination a better look-->
<style>
.nav{border:solid 1px #777;float:left;padding:8px; margin:1px;  width:38px; text-align: center;}
</style>
<!--End of style-->
<table class="table">
  <tr>
    <!--Generate column names ... loop on columns array-->
    <th *ngFor="let col of Columns">{{col.colName}}
      <!--If sorting is allow and its enabled for this column, we switch between ascending/descending-->
    <a [hidden]="Direction==-1 || !AllowSorting || 
    !col.sortable" (click)="Sort(col.colName,-1)">?</a>
    <a [hidden]="Direction==1 || !AllowSorting || 
    !col.sortable" (click)="Sort(col.colName,1)">?</a>
    </th>
    <!--For the delete link-->
    <th [hidden]="!AllowDelete"></th>
  </tr>
  <!--Loop on the data-->
  <tr *ngFor="let c of Data | AutoGridPipe : [SortBy,Direction]; let i = index;">
    <td *ngFor="let col of Columns">{{c[col.colName]}}</td>
      <!--show delete if enabled, and pass the whole row to OnDeleteRow-->
      <td [hidden]="!AllowDelete"><a (click)="OnDeleteRow(c)">X</a></td>
  </tr>
</table>
<!--Pagination-->
<table class="table">
  <tr>
    <td>
      <div style="margin:0px auto;" [style.width]="Width">
      <!--Width is calculated based on number of pages, to make it center-->
      <div *ngFor="let pageIndex of Pages;let i = index;" class="nav">
        <!--If this is the current page, page number displayed as a text only-->
        <span [hidden]="PageIndex!=i">{{pageIndex}}</span>
        <!--Page number displayed as a link, 
            which call OnPageIndexChange and pass the new PageIndex + 1 -->
        <a [hidden]="PageIndex==i" 
        (click)="OnPageIndexChange(pageIndex)">{{pageIndex}}</a>
      </div></div>
    </td>
  </tr>
</table>

Sorting Pipe

As Angular2 doesn't provide you with default sorting and filtering, we need to implement a pipe in order to do the sorting, the pipe is created in a separate file.

The sorting is done based on 2 variables, which column to sort with and which direction (represented by 1 / -1).
In order to make this component reusable regardless of the data, we have to access the property as an array, e.g., a["param"] or b["param"].

AutoGridPipe.ts

JavaScript
import {Pipe ,PipeTransform} from '@angular/core';
@Pipe({
  name:'AutoGridPipe',//name will be used in the page
  pure: false
})
export class AutoGridPipe implements PipeTransform
{
  //Sort,Dir ... will be passed through the component
  transform(array: any[],[SortBy,Dir] : string)
  {
    array.sort((a: any, b: any) =>
    {
      if (a[SortBy] > b[SortBy]) {
        return 1 * Dir;//we switch ascending and descending by multiply x -1
      }
      if (a[SortBy] < b[SortBy]) {
        return -1 * Dir;//we switch ascending and descending by multiply x -1
      }
      return 0;
    });
    return array;
  }
} 

Main Component

Using the grid component is pretty simple, we start by simply importing it, assuming that all the files are in the same folder, and then use the selector that we chose earlier in our definition <AutoGrid>.

JavaScript
import {AutoGrid} from './AutoGrid';
//ViewChild Needed whenever you want to access a child component property or method
import {Component,ViewChild} from '@angular/core';
JavaScript
@Component({
  selector:'MainComponent',
  template: `
           <AutoGrid
           [Columns]="Columns2Display"
           [AllowDelete]="true" [TotalRows]="100" [PageSize]="20">
           </AutoGrid>`,
  directives: [AutoGrid]
})
JavaScript
export class MainComponent
{
//Dummy data to be loaded
Items2Load : any = [
                 {Key:'1', paramName:'Dummy',
                 readType:'Post' ,regEx:'d+',mapValue:'31',priority:2},
                 {Key:'2', paramName:'Something',
                 readType:'GET' ,regEx:'&^',mapValue:'44',priority:1},
                 {Key:'3', paramName:'Hello',
                 readType:'JSON' ,regEx:'w+',mapValue:'333',priority:4},
                 {Key:'4', paramName:'Goo',
                 readType:'XML' ,regEx:'OSOSOS',mapValue:'555',priority:6}];

//Columns to display, enable / disable sort
//Basically any column base configuration needed, can be added here
//Such as Display name, column Icon ....
Columns2Display : any =[
                {colName: "Key", sortable:true},
                {colName: "paramName", sortable:true},
                {colName: "readType", sortable:false},
                {colName: "regEx", sortable:true},
                {colName: "mapValue", sortable:true},
                {colName: "priority", sortable:true}];

//Through this we can access the child component through _AutoGrid
@ViewChild(AutoGrid)  private _AutoGrid:AutoGrid;

//Load the data into the child component
//Cannot be done inside constructor as you don't have access to ViewChild in constructor

ngOnInit(){

      //Pass the data to child component, through LoadData method
      this._AutoGrid.LoadData(this.Items2Load);

      //Can be loaded using ajax call or service
      //this._dummy service.LoadItems((res:any)=>{
      //  this._AutoGrid.LoadData(res);
      //});
  }

//Handle the events (Delete / PageIndexChanged)
ngAfterViewInit() {
      this._AutoGrid.RowDeleted$.subscribe(c => console.log("I saw you, deleted " + c.Key));
      this._AutoGrid.PageIndexChanged$.subscribe(c=> console.log("New page id " + c));
  }
} 

Points of Interest

This control can be modified and customized to add more features (editing, client side filtering, server side filtering, server side sorting and more), in case we want to implement a new server side event (a subject variable needs to be used and can be handled by subscribing to it in the main component). Otherwise, your code will be inside the grid component.

As I consider myself a beginner in Angular2, my code or the methods that I am using are far from optimum and not to be considered as a best practice so any comments are welcome here.

History

  • 9th August, 2016: First version

License

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