Data Grid Overview and Configuration

The Ignite UI for Angular Data Grid is used to display and manipulate data with ease. Quickly bind your data with very little code or use a variety of events to customize different behaviors. This component provides a rich set of features like data selection, excel style filtering, sorting, paging, templating and column moving. Displaying of tabular data has never been easier and beautiful thanks to the Material Table based UI Grid.

Demo


Dependencies

To get started with the Data Grid, first you need to install Ignite UI for Angular by typing the following command:

ng add igniteui-angular

For a complete introduction to the Ignite UI for Angular, read the getting started topic.

The grid is exported as an NgModule, thus all you need to do in your application is to import the IgxGridModule inside your AppModule:

// app.module.ts

import { IgxGridModule } from 'igniteui-angular';
// Or
import { IgxGridModule } from 'igniteui-angular/grid';

@NgModule({
    imports: [
        ...
        IgxGridModule.forRoot(),
        ...
    ]
})
export class AppModule {}

Each of the components, directives and helper classes in the IgxGridModule can be imported either through the grid sub-package or through the main bundle in igniteui-angular. While you don't need to import all of them to instantiate and use the grid, you usually will import them (or your editor will auto-import them for you) when declaring types that are part of the grid API.

import { IgxGridComponent } from 'igniteui-angular/grid/';
// Or
import { IgxGridComponent } from 'igniteui-angular'
...

@ViewChild('myGrid', { read: IgxGridComponent })
public grid: IgxGridComponent;

Usage

Now that we have the grid module imported, let’s get started with a basic configuration of the igx-grid that binds to local data:

<igx-grid #grid1 id="grid1" [data]="localData" [autoGenerate]="true"></igx-grid>

The id property is a string value and is the unique identifier of the grid which will be autogenerated if not provided, while data binds the grid, in this case to local data.

The autoGenerate property tells the igx-grid to auto generate the grid's IgxColumnComponent based on the data source fields. It will also try to deduce the appropriate data type for the column if possible. Otherwise, the developer needs to explicitly define the columns and the mapping to the data source fields.

Styling Configuration

Note

The IgxGridComponent uses css grid layout, which is not supported in IE without prefixing, consequently it will not render properly.

In Angular most of the styles are prefixed implicitly thanks to the Autoprefixer plugin.

For prefixing grid layouts however, you need to enable the Autoprefixer grid property with the comment /* autoprefixer grid:on */.

To facilitate your work, apply the comment in the src/styles.scss file.

// src/styles.scss
   @import '~igniteui-angular/lib/core/styles/themes/index';
   @include igx-core();
   @include igx-theme($default-palette);

   /* autoprefixer grid:on */
...

Columns configuration

IgxColumnComponent is used to define the grid's columns collection and to enable features per column like sorting and paging. Cell, header, and footer templates are also available.

Defining columns

Let's turn the autoGenerate property off and define the columns collection in the markup:

<igx-grid #grid1 [data]="data | async" [autoGenerate]="false" [paging]="true" [perPage]="6" (onColumnInit)="initColumns($event)"
    (onSelection)="selectCell($event)" [allowFiltering]="true">
    <igx-column field="Name" [sortable]="true" header=" "></igx-column>
    <igx-column field="AthleteNumber" [sortable]="true" header="Athlete number" [filterable]="false"></igx-column>
    <igx-column field="TrackProgress" header="Track progress" [filterable]="false">
        <ng-template igxCell let-value>
            <igx-linear-bar [stripped]="false" [value]="value" [max]="100"></igx-linear-bar>
        </ng-template>
    </igx-column>
</igx-grid>

Each of the columns of the grid can be templated separately. The column expects ng-template tags decorated with one of the grid module directives.

Header template

igxHeader targets the column header providing as a context the column object itself.

...
<igx-column field="Name">
    <ng-template igxHeader let-column>
        {{ column.field | uppercase }}
    </ng-template>
</igx-column>
...

Cell template

igxCell applies the provided template to all cells in the column. The context object provided in the template consists of the cell value provided implicitly and the cell object itself. It can be used to define a template where the cells can grow according to their content, as in the below example.

...
<igx-column field="Name">
    <ng-template igxCell let-value>
        {{ value | titlecase }}
    </ng-template>
</igx-column>
...

In the snippet above we "take" a reference to the implicitly provided cell value. This is sufficient if you just want to present some data and maybe apply some custom styling or pipe transforms over the value of the cell. However even more useful is to take the IgxGridCellComponent object itself as shown below:

<igx-grid #grid [data]="data">
    <igx-column dataType="string" field="Name">
        <ng-template igxCell let-cell="cell">
            <!-- Implement row deleting inside the cell template itself -->
            <span tabindex="0" (keydown.delete)="grid.deleteRow(cell.rowIndex)">{{ cell.value | titlecase }}</span>
        </ng-template>
    </igx-column>
    <igx-column dataType="boolean" field="Subscribtion">
        <ng-template igxCell let-cell="cell">
            <!-- Bind the cell value through the ngModel directive and update the data source when the value is changed in the template -->
            <input type="checkbox" [ngModel]="cell.value" (ngModelChange)="cell.update($event)" />
        </ng-template>
    </igx-column>
<igx-grid>

When changing data through the cell template using ngModel, you need to call the appropriate API methods to make sure the value is correctly updated in the grid's underlying data collection. In the snippet above, the ngModelChange call passes through the grid's editing API and goes through the grid's editing pipeline, properly triggering transactions(if applicable) and handling of summaries, selection, etc. However, this ngModelChange will fire every time the value of the cell changes, not just when the user is done editing, resulting in a lot more API calls.

If the data in a cell is bound with [(ngModel)] and the value change is not handled, the new value will not be properly updated in the grid's underlying data source. When dealing with cell editing with a custom template, it is strongly advised to use the cell's cell editing template.

When properly implemented, the cell editing template also ensures that the cell's editValue will correctly pass through the grid editing event cycle.

Cell editing template

The column also accepts one last template that will be used when a cell is in edit mode. As with the other column templates, the provided context object is again the cell value and the cell object itself. Of course in order to make the edit-mode template accessible to end users, you need to set the editable property of the IgxColumnComponent to true.

<igx-column dataType="number" editable="true" field="Price">
    <ng-template igxCellEditor let-cell="cell">
        <label for="price">
            Enter the new price tag
        </label>
        <input name="price" type="number" [(ngModel)]="cell.editValue" />
    </ng-template>
</igx-column>

Make sure to check the API for the IgxGridCellComponent in order to get accustomed with the provided properties you can use in your templates.

Column template API

Each of the column templates can be changed programmatically at any point through the IgxColumnComponent object itself. For example in the code below, we have declared two templates for our user data. In our TypeScript code we'll get references to the templates themselves and then based on some condition we will render the appropriate template for the column in our application.

<igx-grid>
    <!-- Column declarations -->
</igx-grid>

<ng-template #normalView let-value>
    <div class="user-details">{{ val }}</div>
    <user-details-component></user-details-component>
</ng-template>

<ng-template #smallView let-value>
    <div class="user-details-small">{{ val }}</div>
</ng-template>
@ViewChild("normalView", { read: TemplateRef })
public normalView: TemplateRef<any>;

@ViewChild("smallView", { read: TemplateRef })
public smallView: TemplateRef<any>;

....

const column = this.grid.getColumnByName("User");
// Return the appropriate template based on some conditiion.
// For example saved user settings, viewport size, etc.
column.bodyTemplate = this.smallView;

Column properties can also be set in code in the initColumns event which is emitted when the columns are initialized in the grid.

public initColumns(column: IgxGridColumn) {
    const column: IgxColumnComponent = column;
    if (column.field === 'ProductName') {
        column.sortable = true;
        column.editable = true;
    }
}

The code above will make the ProductName column sortable and editable and will instantiate the corresponding features UI (like inputs for editing, etc.).

Data structure

The IgxGridComponent takes only flat data. The data structure specific for rendering is in the form:

const OBJECT_ARRAY = [{
        ObjectKey1: value1,
        ObjectKey2: value2,
        .
        .
        .
        ObjectKeyN: valueN
    },
    {
        ObjectKey1: value1,
        ObjectKey2: value2,
        .
        .
        .
        ObjectKeyN: valueN
    },
    .
    .
    .,
    {
        ObjectKey1: value1,
        ObjectKey2: value2,
        .
        .
        .
        ObjectKeyN: valueN 
    }];
Warning

The key values must not contain neither arrays or other objects.

If you use autoGenerate columns the data keys must be identical.

Data binding

Before going any further with the grid we want to change the grid to bind to remote data service, which is the common scenario in large-scale applications. A good practice is to separate all data fetching related logic in a separate data service, so we are going to create a service which will handle the fetching of data from the server.

Let's implement our service in a separate file

// northwind.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map } from 'rxjs/operators';

We're importing the Injectable decorator which is an essential ingredient in every Angular service definition. The HttpClient will provide us with the functionality to communicate with backend services. It returns an Observable of some result to which we will subscribe in our grid component.

Note: Before Angular 5 the HttpClient was located in @angular/http and was named Http.

Since we will receive a JSON response containing an array of records, we may as well help ourselves by specifing what kind of data we're expecting to be returned in the observable by defining an interface with the correct shape. Type checking is always recommended and can save you some headaches down the road.

// northwind.service.ts

export interface NorthwindRecord {
    ProductID: number;
    ProductName: string;
    SupplierID: number;
    CategoryID: number;
    QuantityPerUnit: string;
    UnitPrice: number;
    UnitsInStock: number;
    UnitsOnOrder: number;
    ReorderLevel: number;
    Discontinued: boolean;
    CategoryName: string;
}

The service itself is pretty simple consisting of one method: fetchData that will return an Observable<NorthwindRecord[]>. In cases when the request fails for any reason (server unavailable, network error, etc), the HttpClient will return an error. We'll leverage the catchError operator which intercepts an Observable that failed and passes the error to an error handler. Our error handler will log the error and return a safe value.

// northwind.service.ts

@Injectable()
export class NorthwindService {
    private url = 'http://services.odata.org/V4/Northwind/Northwind.svc/Alphabetical_list_of_products';

    constructor(private http: HttpClient) {}

    public fetchData(): Observable<NorthwindRecord[]> {
        return this.http
            .get(this.url)
            .pipe(
                map(response => response['value']),
                catchError(
                    this.errorHandler('Error loading northwind data', [])
                )
            );
    }

    private errorHandler<T>(message: string, result: T) {
        return (error: any): Observable<any> => {
            console.error(`${message}: ${error.message}`);
            return of(result as T);
        };
    }
}

Make sure to import both the HttpClientModule and our service in the application module and register the service as a provider.

// app.module.ts

import { HttpClientModule } from '@angular/common/http';
...
import { NorthwindService } from './northwind.service';

@NgModule({
    imports: [
        ...
        HttpClientModule
        ...
    ],
    providers: [
        NorthwindService
    ]
})
export class AppModule {}

After implementing the service we will inject it in our component's constructor and use it to retrieve the data. The ngOnInit lifecycle hook is a good place to dispatch the initial request.

Note: In the code below, you may wonder why are we setting the records property to an empty array before subscribing to the service. The Http request is asynchronous, and until it completes, the records property will be undefined which will result in an error when the grid tries to bind to it. You should either initialize it with a default value or use a BehaviorSubject.

// my.component.ts

@Component({
    ...
})
export class MyComponent implements OnInit {

    public records: NorthwindRecord[];

    constructor(private northwindService: NorthwindService) {}

    ngOnInit() {
        this.records = [];
        this.northwindService.fetchData().subscribe((records) => this.records = records);
    }
}

and in the template of the component:

    <igx-grid [data]="records">
        <igx-column field="ProductId"></igx-column>
        <!-- rest of the column definitions -->
        ...
    </igx-grid>

Note: The grid autoGenerate property is best to be avoided when binding to remote data for now. It assumes that the data is available in order to inspect it and generate the appropriate columns. This is usually not the case until the remote service responds, and the grid will throw an error. Making autoGenerate available, when binding to remote service, is on our roadmap for future versions.

Complex data binding

The IgxGridComponent main purpose is to handle flat data, although this does not mean that it is impossible to work with more complex data.

Currently, the Grid columns don't support composite keys, although you can still create a column out of several other columns. In this section we will cover, how to configure IgxGridComponent with nested data and flat data.

Nested data

In order to bind hierarchical data to IgxGrid you may use:

  • the value of the cell, that contains the nested data
  • a custom column template

Below is the data that we are going to use:

export const EMPLOYEE_DATA = [
    {
        Age: 55,
        Employees: [
            {
                Age: 43,
                HireDate: new Date(2011, 6, 3),
                ID: 3,
                Name: "Michael Burke",
                Title: "Senior Software Developer"
            },
            {
                Age: 29,
                HireDate: new Date(2009, 6, 19),
                ID: 2,
                Name: "Thomas Anderson",
                Title: "Senior Software Developer"
            },
            {
                Age: 31,
                HireDate: new Date(2014, 8, 18),
                ID: 11,
                Name: "Monica Reyes",
                Title: "Software Development Team Lead"
            },
            {
                Age: 35,
                HireDate: new Date(2015, 9, 17),
                ID: 6,
                Name: "Roland Mendel",
                Title: "Senior Software Developer"
            }],
        HireDate: new Date(2008, 3, 20),
        ID: 1,
        Name: "John Winchester",
        Title: "Development Manager"
    },
...

The custom template for the column, that will render the nested data:

...
 <igx-column field="Employees" header="Employees" [cellClasses]="{ expand: true }" width="40%">
        <ng-template #nestedDataTemp igxCell let-people let-cell="cell">
            <div class="employees-container">
                <igx-expansion-panel *ngFor="let person of people">
                    <igx-expansion-panel-header iconPosition="right">
                        <igx-expansion-panel-description>
                            {{ person.Name }}
                        </igx-expansion-panel-description>
                    </igx-expansion-panel-header>
                    <igx-expansion-panel-body>
                        <div class="description">
                            <igx-input-group (keydown)="stop($event)" displayDensity="compact">
                                <label igxLabel for="title">Title</label>
                                <input type="text" name="title" igxInput [(ngModel)]="person.Title" style="text-overflow: ellipsis;" />
                            </igx-input-group>
                            <igx-input-group (keydown)="stop($event)" displayDensity="compact" style="width: 15%;">
                                <label igxLabel for="age">Age</label>
                                <input type="number" name="age" igxInput [(ngModel)]="person.Age" />
                            </igx-input-group>
                        </div>
                    </igx-expansion-panel-body>
                </igx-expansion-panel>
            </div>
        </ng-template>
 </igx-column>
...

And the result from this configuration is:

Flat data

The flat data binding approach is similar to the one that we already described above, but instead of cell value we are going to use the rowData property of the IgxRowComponent.

Since the grid is a component for rendering, manipulating and preserving data records, having access to every data record gives you the opportunity to customize the approach of handling it. The rowData property provides you this opportunity.

Below is the data that we are going to use:

export const DATA: any[] = [
    {
        Address: "Obere Str. 57",
        City: "Berlin",
        CompanyName: "Alfreds Futterkiste",
        ContactName: "Maria Anders",
        ContactTitle: "Sales Representative",
        Country: "Germany",
        Fax: "030-0076545",
        ID: "ALFKI",
        Phone: "030-0074321",
        PostalCode: "12209",
        Region: null
    },
...

The custom template:

...
<igx-column field="Address" header="Address" width="25%" editable="true">
                <ng-template #compositeTemp igxCell let-cell="cell">
                    <div class="address-container">
                    // In the Address column combine the Country, City and PostCode values of the corresponding data record 
                        <span><strong>Country:</strong> {{cell.row.rowData.Country}}</span>
                        <br/>
                        <span><strong>City:</strong> {{cell.row.rowData.City}}</span>
                        <br/>
                        <span><strong>Postal Code:</strong> {{cell.row.rowData.PostalCode}}</span>
                    </div>
                </ng-template>
...

Keep in mind that with the above defined template you will not be able to make editing operations, so we need an editor template.

...
                 <ng-template  igxCellEditor let-cell="cell">
                        <div class="address-container">
                        <span>
                            <strong>Country:</strong> {{cell.row.rowData.Country}}
                            <igx-input-group width="100%">
                                    <input igxInput [(ngModel)]="cell.row.rowData.Country" />
                            </igx-input-group>
                        </span>
                            <br/>
                            <span><strong>City:</strong> {{cell.row.rowData.City}}</span>
                            <igx-input-group width="100%">
                                    <input igxInput [(ngModel)]="cell.row.rowData.City" />
                            </igx-input-group>
                            <br/>
                            <span><strong>Postal Code:</strong> {{cell.row.rowData.PostalCode}}</span>
                            <igx-input-group width="100%">
                                    <input igxInput [(ngModel)]="cell.row.rowData.PostalCode" />
                            </igx-input-group>
                            <br/>
                        </div>
                </ng-template>
</igx-column>
...

And the result is:

State persistence

Persisting the grid state across pages/sessions is a common scenario and is currently achievable on application level. To demonstrate the approach to take, let's implement state persistence across pages. The example is using the localStorage object to store the JSON string of the state, but depending on your needs you may decide to go with the sessionStorage object. All implementation details are extracted in the igxState directive:

// state.directive.ts

@Directive({
    selector: "[igxState]"
})
export class IgxGridStateDirective {

    public ngOnInit() {
        this.loadGridState();
        this.router.events.pipe(take(1)).subscribe((event: NavigationStart) => {
            this.saveGridState();
        });
    }

    public ngAfterViewInit() {
        this.restoreGridState();
    }

    public saveGridState() { ... }
    public loadGridState() { ... }
    public restoreGridState() { ... }
}

As seen in the example above, when a NavigationStart event occurs (each time a user navigates away from the page), saveGridState method is called, which contains the logic to read the grid state (sorting and filtering expressions, paging state, columns order, collection of selected rows) and save this data as json string in the localStorge. Later, when a user comes back to the grid, loadGridState and restoreGridState methods are called during the OnInit and AfterViewInit lifecycle hooks respectively. What loadGridState does is decode the JSON string from the localStorage into a gridState object, while restoreGridState uses the grid API to apply the corresponding sorting and filtering expressions to the grid, set paging, etc.

Last thing to do is apply the directive to the grid and restore the columns collection during the OnInit hook of the grid component:

// grid.component.ts

public ngOnInit() {
    const columnsFromState = this.state.getColumnsForGrid(this.gridId);
    this.columns = this.state.columns && columnsFromState ?
        columnsFromState : this.initialColumns;
}

Sizing

See the Grid Sizing topic.

Known Limitations

Limitation Description
Column widths set in percentage and px Currently we do not support mixing of column widths with % and px.
When trying to filter a column of type number If a value different than number is entered into the filtering input, NaN is returned due to an incorrect cast.
Grid width does not depend on the column widths The width of all columns does not determine the spanning of the grid itself. It is determined by the parent container dimensions or the defined grid's width.
Grid nested in parent container When grid's width is not set and it is placed in a parent container with defined dimensions, the grid spans to this container.
Grid OnPush ChangeDetectionStrategy The grid operates with ChangeDetectionStrategy.OnPush so whenever some customization appears make sure that the grid is notified about the changes that happens.
Columns have a minimum allowed column width. Depending on the displayDensity option, they are as follows:
"compact": 24px
"cosy": 32px
"comfortable ": 48px
If width less than the minimum allowed is set it will not affect the rendered elements. They will render with the minimum allowed width for the corresponding displayDensity. This may lead to an unexpected behavior with horizontal virtualization and is therefore not supported.
Row height is not affected by the height of cells that are not currently rendered in view. Because of virtualization a column with a custom template (that changes the cell height) that is not in the view will not affect the row height. The row height will be affected only while the related column is scrolled in the view.
Note

igxGrid uses igxForOf directive internally hence all igxForOf limitations are valid for igxGrid. For more details see igxForOf Known Issues section.

API References

Additional Resources

Our community is active and always welcoming to new ideas.