Column spanning in Angular Grid component

17 Sep 202524 minutes to read

The column spanning feature in the Syncfusion Grid allows you to merge adjacent cells horizontally, creating a visually appealing and informative layout. By defining the colSpan attribute in the QueryCellInfo event, you can easily span cells and customize the appearance of the grid.

In the following demo, Employee Davolio doing analysis from 9.00 AM to 10.00 AM, so that cells have spanned.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule } from '@syncfusion/ej2-angular-grids'



import { Component, OnInit } from '@angular/core';
import { QueryCellInfoEventArgs, GridLine, Column } from '@syncfusion/ej2-angular-grids';
import { columnSpanData, ColumnSpanDataType } from './datasource';
import { EmitType } from '@syncfusion/ej2-base';

@Component({
imports: [
        
        GridModule
    ],


standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' [height]='height' [width]='width' [gridLines]='gridLines'
     [allowTextWrap]='textWrap' (queryCellInfo)='queryCellInfoEvent($event)'>
        <e-columns>
            <e-column field='EmployeeID' headerText='Employee ID' width='150' textAlign="Right" isPrimaryKey=true></e-column>
            <e-column field='EmployeeName' headerText='Employee Name' width='200'></e-column>
            <e-column field='9:00' headerText='9:00 AM' width='120'></e-column>
            <e-column field='9:30' headerText='9:30 AM' width='120'></e-column>
            <e-column field='10:00' headerText='10:00 AM' width='120'></e-column>
            <e-column field='10:30' headerText='10:30 AM' width='120'></e-column>
            <e-column field='11:00' headerText='11:00 AM' width='120'></e-column>
            <e-column field='11:30' headerText='11:30 AM' width='120'></e-column>
            <e-column field='12:00' headerText='12:00 PM' width='120'></e-column>
            <e-column field='12:30' headerText='12:30 PM' width='120'></e-column>
            <e-column field='2:30' headerText='2:30 PM' width='120'></e-column>
            <e-column field='3:00' headerText='3:00 PM' width='120'></e-column>
            <e-column field='3:30' headerText='3:30 PM' width='120'></e-column>
            <e-column field='4:00' headerText='4:00 PM' width='120'></e-column>
            <e-column field='4:30' headerText='4:30 PM' width='120'></e-column>
            <e-column field='5:00' headerText='5:00 PM' width='120'></e-column>
        </e-columns>
    </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data: object[];
    public height: string | number;
    public width: string | number;
    public gridLines: GridLine;
    public textWrap: boolean;
    public queryCellInfoEvent: EmitType<QueryCellInfoEventArgs> = (args: QueryCellInfoEventArgs) => {
        const datas: ColumnSpanDataType = args.data as ColumnSpanDataType;
        switch (datas.EmployeeID) {
            case 10001:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '2:30' || (args.column as Column).field === '4:30') {
                    (args.colSpan as number) = 2;
                } else if ((args.column as Column).field === '11:00') {
                    (args.colSpan as number) = 3;
                }
                break;
            case 10002:
                if ((args.column as Column).field === '9:30' || (args.column as Column).field === '2:30' ||
                    (args.column as Column).field === '4:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '11:00') {
                    (args.colSpan as number) = 4;
                }
                break;
            case 10003:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '11:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '10:30' || (args.column as Column).field === '3:30' ||
                    (args.column as Column).field === '4:30' || (args.column as Column).field === '2:30') {
                    (args.colSpan as number) = 2;
                }
                break;
            case 10004:
                if ((args.column as Column).field === '9:00') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '11:00') {
                    (args.colSpan as number) = 4;
                } else if ((args.column as Column).field === '4:00' || (args.column as Column).field === '2:30') {
                    (args.colSpan as number) = 2;
                }
                break;
            case 10005:
                if ((args.column as Column).field === '9:00') {
                    (args.colSpan as number) = 4;
                } else if ((args.column as Column).field === '11:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '3:30' || (args.column as Column).field === '4:30' || (args.column as Column).field === '2:30') {
                    (args.colSpan as number) = 2;
                }
                break;
            case 10006:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '4:30' ||
                    (args.column as Column).field === '2:30' || (args.column as Column).field === '3:30') {
                    (args.colSpan as number) = 2;
                } else if ((args.column as Column).field === '10:00' || (args.column as Column).field === '11:30') {
                    (args.colSpan as number) = 3;
                }
                break;
            case 10007:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '3:00' || (args.column as Column).field === '10:30') {
                    (args.colSpan as number) = 2;
                } else if ((args.column as Column).field === '11:30' || (args.column as Column).field === '4:00') {
                    (args.colSpan as number) = 3;
                }
                break;
            case 10008:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '10:30' || (args.column as Column).field === '2:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '4:00') {
                    (args.colSpan as number) = 2;
                }
                break;
            case 10009:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '11:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '4:30' || (args.column as Column).field === '2:30') {
                    (args.colSpan as number) = 2;
                }
                break;
            case 100010:
                if ((args.column as Column).field === '9:00' || (args.column as Column).field === '2:30' ||
                    (args.column as Column).field === '4:00' || (args.column as Column).field === '11:30') {
                    (args.colSpan as number) = 3;
                } else if ((args.column as Column).field === '10:30') {
                    (args.colSpan as number) = 2;
                }
                break;
        }
    }
    ngOnInit(): void {
        this.data = columnSpanData;
        this.gridLines = 'Both';
        this.height = 'auto';
        this.width = 'auto';
        this.textWrap = true;
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Change the border color while column spanning

To customize the border color of spanned cells, utilize the QueryCellInfo event. This event is triggered before each cell element is appended to the Grid, allowing you to modify cell styles, such as border color, for merged cells.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule } from '@syncfusion/ej2-angular-grids'



import { Component, OnInit } from '@angular/core';
import { QueryCellInfoEventArgs, GridLine, Column } from '@syncfusion/ej2-angular-grids';
import { columnSpanData, ColumnSpanDataType } from './datasource';
import { EmitType } from '@syncfusion/ej2-base';

@Component({
imports: [
        
        GridModule
    ],


standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' [height]='height' [width]='width' [gridLines]='gridLines'
     [allowTextWrap]='textWrap' (queryCellInfo)='queryCellInfoEvent($event)'>
        <e-columns>
            <e-column field='EmployeeID' headerText='Employee ID' width='150' textAlign="Right" isPrimaryKey=true></e-column>
            <e-column field='EmployeeName' headerText='Employee Name' width='200'></e-column>
            <e-column field='9:00' headerText='9:00 AM' width='120'></e-column>
            <e-column field='9:30' headerText='9:30 AM' width='120'></e-column>
            <e-column field='10:00' headerText='10:00 AM' width='120'></e-column>
            <e-column field='10:30' headerText='10:30 AM' width='120'></e-column>
            <e-column field='11:00' headerText='11:00 AM' width='120'></e-column>
            <e-column field='11:30' headerText='11:30 AM' width='120'></e-column>
            <e-column field='12:00' headerText='12:00 PM' width='120'></e-column>
            <e-column field='12:30' headerText='12:30 PM' width='120'></e-column>
            <e-column field='2:30' headerText='2:30 PM' width='120'></e-column>
            <e-column field='3:00' headerText='3:00 PM' width='120'></e-column>
            <e-column field='3:30' headerText='3:30 PM' width='120'></e-column>
            <e-column field='4:00' headerText='4:00 PM' width='120'></e-column>
            <e-column field='4:30' headerText='4:30 PM' width='120'></e-column>
            <e-column field='5:00' headerText='5:00 PM' width='120'></e-column>
        </e-columns>
    </ejs-grid>`,
})
export class AppComponent implements OnInit {

    public data: object[]=columnSpanData;
    public height: string | number="auto";
    public width: string | number="auto";
    public gridLines: GridLine="Both";
    public textWrap: boolean=true;
    public queryCellInfoEvent: EmitType<QueryCellInfoEventArgs> = (args: QueryCellInfoEventArgs) => {
        let data: ColumnSpanDataType = args.data as ColumnSpanDataType;
        switch (data.EmployeeID) {
            case 10001:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '2:30' || (args.column as Column ).field === '4:30') {
                    args.colSpan = 2;
                } else if ((args.column as Column ).field === '11:00') {
                    args.colSpan = 3;
                }
                break;
            case 10002:
                if ((args.column as Column ).field === '9:30' || (args.column as Column ).field === '2:30' ||
                    (args.column as Column ).field === '4:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '11:00') {
                    args.colSpan = 4;
                }
                break;
            case 10003:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '11:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '10:30' || (args.column as Column ).field === '3:30' ||
                    (args.column as Column ).field === '4:30' || (args.column as Column ).field === '2:30') {
                    args.colSpan = 2;
                }
                break;
            case 10004:
                if ((args.column as Column ).field === '9:00') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '11:00') {
                    args.colSpan = 4;
                } else if ((args.column as Column ).field === '4:00' || (args.column as Column ).field === '2:30') {
                    args.colSpan = 2;
                }
                break;
            case 10005:
                if ((args.column as Column ).field === '9:00') {
                    args.colSpan = 4;
                } else if ((args.column as Column ).field === '11:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '3:30' || (args.column as Column ).field === '4:30' || (args.column as Column ).field === '2:30') {
                    args.colSpan = 2;
                }
                break;
            case 10006:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '4:30' ||
                    (args.column as Column ).field === '2:30' || (args.column as Column ).field === '3:30') {
                    args.colSpan = 2;
                } else if ((args.column as Column ).field === '10:00' || (args.column as Column ).field === '11:30') {
                    args.colSpan = 3;
                }
                break;
            case 10007:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '3:00' || (args.column as Column ).field === '10:30') {
                    args.colSpan = 2;
                } else if ((args.column as Column ).field === '11:30' || (args.column as Column ).field === '4:00') {
                    args.colSpan = 3;
                }
                break;
            case 10008:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '10:30' || (args.column as Column ).field === '2:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '4:00') {
                    args.colSpan = 2;
                }
                break;
            default:
                this.extendQueryCellEvent(args, data.EmployeeID);
        }
        if ((args.colSpan as number ) > 1) {
            (args.cell as HTMLElement).style.border = '1px solid red';
        }
    };
    public extendQueryCellEvent: EmitType<QueryCellInfoEventArgs> = (args: QueryCellInfoEventArgs, value: number) => {
        switch (value) {
            case 10009:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '11:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '4:30' || (args.column as Column ).field === '2:30') {
                    args.colSpan = 2;
                }
                break;
            case 100010:
                if ((args.column as Column ).field === '9:00' || (args.column as Column ).field === '2:30' ||
                    (args.column as Column ).field === '4:00' || (args.column as Column ).field === '11:30') {
                    args.colSpan = 3;
                } else if ((args.column as Column ).field === '10:30') {
                    args.colSpan = 2;
                }
                break;
        }
    };
    ngOnInit(): void {
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Limitations

  • The updateCell method does not support column spanning.
  • Column spanning is not supported with the following features:
    1. Virtual scrolling
    2. Infinite scrolling
    3. Grouping
    4. Autofill

column spanning using enableColumnSpan property

The Syncfusion Angular Grid introduces a simplified approach to horizontally merge cells using the enableColumnSpan property.

When the enableColumnSpan property is enabled, the Grid automatically merges cells with matching data across adjacent columns without requiring manual span configuration using the queryCellInfo event. These merged cells are visually combined into a single cell, improving readability.

Here is an example of how to use the enableColumnSpan property to merge cells horizontally:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent, GridModule, FreezeService, SortService } from '@syncfusion/ej2-angular-grids';
import { shiftPlanData } from './datasource';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GridModule],
  providers: [FreezeService, SortService],
  template: `
    <ng-template #doctorTemplate let-data>
      <div class="doctor-name"></div>
      <div class="doctor-designation"></div>
    </ng-template>

    <ejs-grid #grid [dataSource]="shiftPlanData" gridLines="Both" [allowSorting]="true" [enableColumnSpan]="true" [allowTextWrap]="true" [textWrapSettings]="{ wrapMode: 'Content' }" height="450" width="auto">
      <e-columns>
        <e-column field="Name" headerText="Doctor Name" width="170" textAlign="Center" freeze="Left" [template]="doctorTemplate"></e-column>
        <e-column field="Time9AM" headerText="9:00 AM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time10AM" headerText="10:00 AM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time11AM" headerText="11:00 AM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time12PM" headerText="12:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time1PM" headerText="1:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time2PM" headerText="2:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time3PM" headerText="3:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time4PM" headerText="4:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time5PM" headerText="5:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time6PM" headerText="6:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Time7PM" headerText="7:00 PM" width="120" textAlign="Center" [allowSorting]="false"></e-column>
      </e-columns>
    </ejs-grid>
  `,
  styles: [`
    .doctor-name {
      margin-bottom: 5px;
    }
    .doctor-designation {
      font-size: 12px;
      color: #7f8c8d;
      font-weight: normal;
    }
  `]
})
export class AppComponent implements OnInit {

  public shiftPlanData?: Object[];
  @ViewChild('grid') 
  public grid?: GridComponent;

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

You can also control spanning at the column level. To prevent merging for specific columns, set enableColumnSpan to false in the column definition.

Limitations

  • Virtualization
  • Infinite Scrolling
  • Lazy Load Grouping
  • Row Drag and Drop
  • Column Virtualization
  • Detail Template
  • Editing
  • Export
  • Foreign Key
  • Hierarchy Grid