Angular2 Grid Component - Auto Generated






4.87/5 (11 votes)
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
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
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
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
@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 columnsAllowDelete
: Hide/display a delete buttonColumns
: 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 rowPageIndexChanged$
: Once triggered, will hold the new page index (starting from 0)
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.
<!--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
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>
.
import {AutoGrid} from './AutoGrid';
//ViewChild Needed whenever you want to access a child component property or method
import {Component,ViewChild} from '@angular/core';
@Component({
selector:'MainComponent',
template: `
<AutoGrid
[Columns]="Columns2Display"
[AllowDelete]="true" [TotalRows]="100" [PageSize]="20">
</AutoGrid>`,
directives: [AutoGrid]
})
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