Column Pinning (Frozen) in Angular Grid Component

17 Sep 202524 minutes to read

The Syncfusion Angular Grid component enables pinning (freezing) columns to lock them in place on the left, right, or a fixed position, ensuring they remain visible during horizontal scrolling. This feature ensures that essential data, such as identifiers or action buttons, remains accessible while navigating large datasets.

Frozen columns require careful configuration to avoid rendering issues. Ensure they are within the grid’s viewport and test rendering across different screen sizes for responsiveness.

Freeze multiple columns

The Syncfusion Angular Grid allows freezing multiple columns using the frozenColumns property, which specifies the number of columns to freeze on the left side. This ensures that the specified columns remain fixed while the remaining columns can be scrolled horizontally.

The following example sets the frozenColumns property to 2, freezing the first two columns on the left side of the grid.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, FreezeService, SelectionService, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { NumericTextBoxAllModule, RatingAllModule } from '@syncfusion/ej2-angular-inputs'
import {  ButtonModule } from '@syncfusion/ej2-angular-buttons'


import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent, } from '@syncfusion/ej2-angular-grids';
import { NumericTextBoxComponent } from '@syncfusion/ej2-angular-inputs';
import { data } from './datasource';

@Component({
imports: [
        
        GridModule,
        NumericTextBoxAllModule,
        RatingAllModule,
        ButtonModule
    ],

providers: [FreezeService, SelectionService, EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    template: `<div style="display: flex">
    <label style="padding: 10px 10px 26px 0">
      Change the frozen columns:
    </label>
    <ejs-numerictextbox
      id="frozenColumns"
      #frozenColumns
      min="0"
      max="3"
      [validateDecimalOnType]="true"
      decimals="0"
      format="n"
      value="2"
      width="100px"
    ></ejs-numerictextbox>
    <div> 
      <button style="margin-left:5px" ejs-button (click)="frozenColumnFn()">
        Update
      </button>
    </div>
  </div>
  <ejs-grid #grid style="padding: 5px 5px" [dataSource]='data' height=315 [frozenColumns]='2' [allowSelection]='false'  [enableHover]='false'>
    <e-columns>
      <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=90></e-column>
      <e-column field='CustomerID' headerText='Customer ID' width=100></e-column>
      <e-column field='OrderDate' headerText='Order Date' width=100 format='yMd' textAlign='Right'></e-column>
      <e-column field='EmployeeID' headerText='Employee ID' textAlign='Right' width=80></e-column>
      <e-column field='ShipName' headerText='Ship Name' width=130></e-column>
      <e-column field='ShipAddress' headerText='Ship Address' width=140></e-column>
      <e-column field='ShipCity' headerText='Ship City' width=100></e-column>
      <e-column field='ShipCountry' headerText='Ship Country' width=100></e-column>
      <e-column field='ShipRegion' headerText='Ship Region' width=80></e-column>
      <e-column field='ShipPostalCode' headerText='Ship Postal Code' width=110></e-column>
      <e-column field='Freight' headerText='Freight' width=80></e-column>
    </e-columns>
  </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    @ViewChild('grid')
    public grid?: GridComponent;
    @ViewChild('frozenColumns')
    public frozenColumns?: NumericTextBoxComponent;
  
    ngOnInit(): void {
      this.data = data;
    }
  
    frozenColumnFn() {
      (this.grid as GridComponent).frozenColumns = (this.frozenColumns as NumericTextBoxComponent).value;
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

  • Frozen columns must not be set outside the grid’s viewport.
  • The Grid supports column virtualization with frozen columns, improving performance for large datasets.
  • Frozen columns are supported only for columns visible in the current view.
  • Both frozenColumns and frozenRows properties can be used together.
  • When column virtualization and frozen columns are enabled, horizontal scrolling via touchpad gestures (e.g., two-finger swipe) is not supported. Use the horizontal scrollbar instead.

Freeze particular columns

The Syncfusion Angular Grid allows freezing specific columns using the isFrozen property, enabling selected columns to remain fixed on the left side while others scroll horizontally. Unlike the frozenColumns property, which freezes columns in their initial order, isFrozen provides flexibility to freeze columns at any index.

To freeze a particular column in the grid, you can utilize the isFrozen property of the grid component as true.

The following example demonstrates freezing a specific column using the isFrozen property, controlled via the change event of the DropDownList component. The getColumnByField method retrieves the column, and the refreshColumns method updates the grid. For large datasets, minimize frequent calls to refreshColumns to optimize performance.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, FreezeService, SelectionService, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { DropDownListAllModule } from '@syncfusion/ej2-angular-dropdowns'
import { ButtonModule, CheckBoxModule, RadioButtonModule, SwitchModule } from '@syncfusion/ej2-angular-buttons'


import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { ChangeEventArgs } from '@syncfusion/ej2-angular-dropdowns';
import { data } from './datasource';

@Component({
imports: [
        
        GridModule,
        DropDownListAllModule,
        ButtonModule, CheckBoxModule, RadioButtonModule, SwitchModule
    ],

providers: [FreezeService, SelectionService, EditService, ToolbarService],
standalone: true,
  selector: 'app-root',
  template: `
    <div style="display:flex;">
    <label style="padding: 10px 10px 26px 0">Change the frozen column: </label>
    <ejs-dropdownlist
      style="margin-top:5px"
      id="dropdown"
      #dropdown
      index="0"
      width="120"
      [fields]="field"
      [dataSource]="ddlData"
      (change)="columnChange($event)"
      width="100px"
    ></ejs-dropdownlist>
  </div>  
  <ejs-grid #grid id="grid" style="padding: 5px 5px" [dataSource]='data' height=315 [allowSelection]='false'  [enableHover]='false'>
    <e-columns>
      <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=90></e-column>
      <e-column field='CustomerID' headerText='Customer ID' width=100 [isFrozen]="true"></e-column>
      <e-column field='OrderDate' headerText='Order Date' width=100 format='yMd' textAlign='Right'></e-column>
      <e-column field='EmployeeID' headerText='Employee ID' textAlign='Right' width=80></e-column>
      <e-column field='ShipName' headerText='Ship Name' width=130></e-column>
      <e-column field='ShipCity' headerText='Ship City' width=100></e-column>
      <e-column field='ShipCountry' headerText='Ship Country' width=100></e-column>
      <e-column field='ShipRegion' headerText='Ship Region' width=80></e-column>
      <e-column field='ShipPostalCode' headerText='Ship Postal Code' width=110></e-column>
      <e-column field='Freight' headerText='Freight' width=80></e-column>
    </e-columns>
  </ejs-grid>`
})
export class AppComponent implements OnInit {

  public data?: object[];
  @ViewChild('grid')
  public grid?: GridComponent;
  public field?: object;
  public ddlData?: object[] ;

  ngOnInit(): void {  
    this.data = data;
    this.field= { text: 'text', value: 'value' };
    this.ddlData= [
      { text: 'OrderID', value: 'OrderID' },
      { text: 'CustomerID', value: 'CustomerID' },
      { text: 'OrderDate', value: 'OrderDate' },
      { text: 'EmployeeID', value: 'EmployeeID' },
      { text: 'ShipName', value: 'ShipName' },
      { text: 'ShipCity', value: 'ShipCity' },
      { text: 'ShipCountry', value: 'ShipCountry' },
      { text: 'ShipRegion', value: 'ShipRegion' },
      { text: 'ShipPostalCode', value: 'ShipPostalCode' },
      { text: 'Freight', value: 'Freight' },
    ]
  }

  columnChange(args: ChangeEventArgs) {
    
    const selectedColumn = (this.grid as GridComponent).getColumnByField(args.value as string);

    // Iterate through all columns and unfreeze any previously frozen columns
    ((this.grid as GridComponent).columns as { isFrozen: boolean; }[]).forEach((column) => {
      if (column.isFrozen) {
          column.isFrozen = false;
      }
  });

    // Freeze the newly selected column, if it exists
    if (selectedColumn) {
      selectedColumn.isFrozen = true;
    }

    // Refresh the columns
    (this.grid as GridComponent).refreshColumns();
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Freeze direction

In the Syncfusion Angular Grid, the “freeze direction” feature serves to reposition frozen columns either to the left, right, or in a fixed position, while still allowing the remaining columns to be horizontally movable. This feature is designed to optimize user experience by ensuring that critical information remains visible even during horizontal scrolling. By default, when you set the frozenColumns property of the grid or the isFrozen property of individual columns, it results in freezing those columns on the left side of the grid. This helps in keeping important data readily accessible as you navigate through your dataset.

To achieve this, you can utilize the column.freeze property. This property is used to specify the freeze direction for individual columns. The grid will adjust the column positions based on the column.freeze value.

The types of the column.freeze directions:

  • Left: When you set the column.freeze property to Left, specific columns will be frozen on the left side of the grid. The remaining columns will be movable.

  • Right: When you set the column.freeze property to Right, certain columns will be frozen on the right side of the grid, while the rest of the columns remain movable.

  • Fixed: The Fixed direction locks a column at a fixed position within the grid. This ensures that the column is always visible during horizontal scroll.

In the following example, the ShipCountry column is frozen on the left side, the CustomerID column is frozen on the right side and the Freight column is frozen on the fixed of the content table. Additionally, you can modify the column.freeze property to Left, Right and Fixed based on the selected column by utilizing the change event of the DropDownList component.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, FreezeService, SelectionService, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { ButtonModule } from '@syncfusion/ej2-angular-buttons'
import { DropDownListAllModule } from '@syncfusion/ej2-angular-dropdowns'


import { Component, OnInit, ViewChild } from '@angular/core';
import { DropDownListComponent } from '@syncfusion/ej2-angular-dropdowns';
import { GridComponent, freezeDirection } from '@syncfusion/ej2-angular-grids'
import { data } from './datasource';

@Component({
imports: [
        
        GridModule,
        ButtonModule,
        DropDownListAllModule
    ],

providers: [FreezeService, SelectionService, EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    template: `
    <div style="display: flex">
    <label style="padding: 10px 10px 26px 0"> Change column: </label>
    <ejs-dropdownlist
      id="columnDropDown"
      #columnDropDown
      index="7"
      [dataSource]="columnData"
      [fields]="fields"
      width="100px"
    ></ejs-dropdownlist>
    <label style="padding: 10px 10px 26px 0; margin-left:5px">
      Change freeze direction:
    </label>
    <ejs-dropdownlist
      id="directionDropDown"
      #directionDropDown
      index="0"
      [dataSource]="directionData"
      [fields]="fields"
      width="80px"
    ></ejs-dropdownlist>
      <div>
        <button style="margin-left:5px" ejs-button (click)="freezeDirectionFn()">Update</button>
      </div>
    </div>
    <ejs-grid #grid id='grid' style="padding: 5px 5px" [dataSource]='data' height='315' [enableHover]='false'>
        <e-columns>
          <e-column field='OrderID' headerText='Order ID' width='90' textAlign='Right'></e-column>
          <e-column field='Freight' headerText='Freight' width='90' format='C2' textAlign='Right' freeze='Fixed'></e-column>
          <e-column field='CustomerID' headerText='Customer ID' width='100' freeze='Right'></e-column>
          <e-column field='OrderDate' headerText='Order Date' width='100' format="yMd" textAlign='Right'></e-column>
          <e-column field='ShipName' headerText='Ship Name' width='100'></e-column>
          <e-column field='ShipAddress' headerText='Ship Address' width='120'></e-column>
          <e-column field='ShipCity' headerText='Ship City' width='110'></e-column>
          <e-column field='ShipCountry' headerText='Ship Country' width='100' freeze='Left'></e-column>
        </e-columns>
    </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    @ViewChild('grid')
    public grid?: GridComponent;
    @ViewChild('columnDropDown')
    public columnDropDown?: DropDownListComponent;
    @ViewChild('directionDropDown')
    public directionDropDown?: DropDownListComponent;
    public fields: object = { text: 'name', value: 'id' };
    public columnData?: object[]
    public directionData?: object[];

    ngOnInit(): void {
      this.data = data;
      this.columnData=[
        { id: 'OrderID', name: 'Order ID' },
        { id: 'Freight', name: 'Freight' },
        { id: 'CustomerID', name: 'Customer ID' },
        { id: 'OrderDate', name: 'Order Date' },
        { id: 'ShipName', name: 'Ship Name' },
        { id: 'ShipAddress', name: 'Ship Address' },
        { id: 'ShipCity', name: 'Ship City' },
        { id: 'ShipCountry', name: 'Ship Country' },
      ];
      this.directionData= [
        { id: 'Left', name: 'Left' },
        { id: 'Right', name: 'Right' },
        { id: 'Fixed', name: 'Fixed' },
      ];
    }

    freezeDirectionFn() {
      (this.grid as GridComponent).getColumnByField((this.columnDropDown as DropDownListComponent).value as string).freeze=(this.directionDropDown as DropDownListComponent).value as freezeDirection
      (this.grid as GridComponent).refreshColumns();
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Change default frozen line color

You can customize the frozen line borders of frozen columns in the Syncfusion Grid component by applying custom CSS styles to the specific frozen column. This allows you to change the border color of the left frozen columns, right frozen columns, and fixed frozen columns to match your application’s design and theme.

To change default frozen line color, use the following class name and apply the border color based on your requirement.

For left frozen columns:

.e-grid .e-leftfreeze.e-freezeleftborder {
    border-right-color: rgb(198, 30, 204);
}

For right frozen columns:

.e-grid .e-rightfreeze.e-freezerightborder {
    border-left-color: rgb(19, 228, 243);
}

For fixed frozen columns, you need to specify both left and right border as mentioned below

.e-grid .e-fixedfreeze.e-freezeleftborder{
    border-left-color: rgb(9, 209, 9); 
}

.e-grid .e-fixedfreeze.e-freezerightborder{
    border-right-color: rgb(10, 224, 10);
}

The following example demonstrates how to change the default frozen line color using CSS.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, FreezeService, SelectionService, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'


import { Component, OnInit } from '@angular/core';
import { data } from './datasource';

@Component({
imports: [
        
        GridModule,
    ],

providers: [FreezeService, SelectionService, EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' height=315 [allowSelection]='false' [enableHover]='false'>
    <e-columns>
      <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=90></e-column>
      <e-column field='CustomerID' headerText='Customer ID' width=100 freeze='Left'></e-column>
      <e-column field='OrderDate' headerText='Order Date' width=100 format='yMd' textAlign='Right'></e-column>
      <e-column field='EmployeeID' headerText='Employee ID' textAlign='Right' width=80></e-column>
      <e-column field='ShipName' headerText='Ship Name' width=130></e-column>
      <e-column field='ShipAddress' headerText='Ship Address' width=140 freeze='Fixed'></e-column>
      <e-column field='ShipCity' headerText='Ship City' width=100></e-column>
      <e-column field='ShipCountry' headerText='Ship Country' width=100 freeze='Right'></e-column>
      <e-column field='ShipRegion' headerText='Ship Region' width=80></e-column>
      <e-column field='ShipPostalCode' headerText='Ship Postal Code' width=110></e-column>
      <e-column field='Freight' headerText='Freight' width=80></e-column>
    </e-columns>
  </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
  
    ngOnInit(): void {
      this.data=data
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Render DatePicker in frozen columns

The Syncfusion Angular Grid supports rendering a DatePicker component within frozen columns during editing. This is achieved using the edit property, which defines custom create, write, read, and destroy methods to manage the DatePicker’s lifecycle. These methods handle initialization, value setting, value retrieval, and cleanup, respectively.

To enable editing, configure the editSettings property (e.g., { allowEditing: true, mode: 'Normal' }). Refer to the editSettings documentation for details.

The following example renders a DatePicker in the OrderDate column, which is frozen, by appending the DatePicker to the input element in the editing row.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, FreezeService, PageService, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { Component, OnInit } from '@angular/core';
import { data } from './datasource';
import { DatePicker } from '@syncfusion/ej2-calendars';
@Component({
imports: [GridModule],
providers: [FreezeService, PageService, EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    template: `
    <ejs-grid [dataSource]='data' height=315 [frozenColumns]='2' [editSettings]='editSettings' [toolbar]='toolbar' allowPaging='true' [pageSettings]='pageSettings'>
      <e-columns>
        <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=90 isPrimaryKey="true"></e-column>
        <e-column field='OrderDate' headerText='Order Date' width=100 format='yMd' [edit]='datePickerParams'></e-column>
        <e-column field='CustomerID' headerText='Customer ID' width=100 freeze='Left'></e-column>
        <e-column field='ShipCity' headerText='Ship City' width=100></e-column>
      </e-columns>
    </ejs-grid>`
})
export class AppComponent implements OnInit {
  public data?: object[];
  public pageSettings?: Object;
  public toolbar?: string[];
  public editSettings?: Object;
  public datePickerParams?: Object;
  public datePickerObj: DatePicker;
  ngOnInit(): void {
    this.data=data;
    this.pageSettings = { pageCount: 5 };
    this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
    this.editSettings = {allowEditing: true, allowAdding: true, allowDeleting: true };
    this.datePickerParams = {
      create: this.createDatePicker,
      read: this.readDatePicker,
      destroy: this.destroyDatePicker,
      write: this.writeDatePicker,
    };
  }
  public createDatePicker = (): HTMLElement => {
    return document.createElement('input');
  };
  public readDatePicker = (): Date => {
    return this.datePickerObj.value;
  };
  public destroyDatePicker = (): void => {
    this.datePickerObj.destroy();
  };
  public writeDatePicker = (args): void => {
    this.datePickerObj = new DatePicker({
      value: new Date(args.rowData[args.column.field]),
      floatLabelType: 'Never',
    });
    this.datePickerObj.appendTo(args.element);
  };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Deprecated methods

Previous Current Explanation
getMovableRows() gridInstance.getMovableRows()[0].querySelectorAll(‘.e-unfreeze’) getRows() gridInstance.getRows()[0].querySelectorAll(‘.e-unfreeze’) The previous architecture used separate tables for left, right, and movable contents, returning only movable rows when calling the method, whereas the current architecture combines them into one table, returning all rows and introduces the e-unfreeze class for selecting movable rows
getFrozenRightRows() gridInstance.getFrozenRightRows()[0].querySelectorAll(‘.e-rightfreeze’) getRows() gridInstance.getRows()[0].querySelectorAll(‘.e-rightfreeze’) In the previous architecture, it returned only the table rows from the right freeze table, but in the current architecture, all rows of the entire table are returned, introducing the e-rightfreeze class for selecting right freeze rows.
getMovableRowByIndex()
getFrozenRowByIndex()
getFrozenRightRowByIndex()
getRowByIndex() gridInstance.getRowByIndex(1).querySelectorAll(‘.e-unfreeze’) In the previous architecture, separate methods were used to select rows from different table sections, while in the current architecture, the getMovableRowByIndex(), getFrozenRightRowByIndex(), and getFrozenRowByIndex() methods now return the same table row based on the given index. Additionally, class names for table cells (td’s) have been separated into e-leftfreeze, e-unfreeze, and e-rightfreeze, making it easier to customize cells within a row.
getMovableCellFromIndex()
getFrozenRightCellFromIndex()
getCellFromIndex() gridInstance.getCellFromIndex(1,1) In the previous approach, the getMovableCellFromIndex() method was used to choose a specific cell within the movable table, and the getFrozenRightCellFromIndex() method was utilized to target a particular cell within the right freeze table. However, in the current architecture, you have the flexibility to select a specific cell in either the movable or right freeze table by using both the getFrozenRightCellFromIndex() and getMovableCellFromIndex() methods. This new method simplifies the process of selecting and retrieving specific cells within these tables, offering more versatility and convenience.
getMovableDataRows()
getFrozenRightDataRows()
getFrozenDataRows()
getDataRows() gridInstance.getDataRows()[0].querySelectorAll(‘.e-unfreeze’) In the previous approach, there were separate methods (getMovableDataRows(), getFrozenRightDataRows(), and getFrozenDataRows()) for obtaining viewport data rows from the freeze, movable, and right tables individually. However, in the new approach, these methods have been enhanced to return the entire viewport data rows for all sections together, simplifying data retrieval. You can now extract specific cells within these rows using selectors such as e-leftfreeze for the left freeze, e-unfreeze for the movable, and e-rightfreeze for the right freeze tables, providing greater flexibility in working with the data.
getMovableColumnHeaderByIndex()
getFrozenRightColumnHeaderByIndex()
getFrozenLeftColumnHeaderByIndex()
getColumnHeaderByIndex() gridInstance.getColumnHeaderByIndex(1) In the previous architecture, the methods selected movable, right freeze, and left freeze headers separately. However, in the new approach, when using the getMovableColumnHeaderByIndex(), getFrozenRightColumnHeaderByIndex(), and getFrozenLeftColumnHeaderByIndex() methods, you will still obtain the same results as in the previous architecture.

When a validation message is displayed in the frozen part (Left, Right, Fixed) of the table, scrolling is prevented until the validation message is cleared.

Limitations

While freezing columns in the Syncfusion Angular Grid provides enhanced visibility and scrolling capabilities, there are certain limitations to consider. The following features are not supported when using frozen columns:

  • Detail Template
  • Hierarchy Grid
  • Autofill