HelpBot Assistant

How can I help you?

Row Spanning in Angular Grid Component

19 Mar 202620 minutes to read

The Grid provides row spanning capabilities to merge two or more cells in a row into a single cell, reducing information repetition across multiple rows and enhancing readability.

Row spanning

Row spanning merges adjacent cells vertically into a single cell. The feature uses two key concepts:

  • rowSpan: Specifies the number of consecutive row cells to be merged vertically.
  • queryCellInfo: Triggered for each grid cell, allowing custom cell configuration.

The queryCellInfo event enables row spanning by setting the rowSpan attribute during cell rendering. The Grid processes this attribute and renders the merged cell across the specified number of rows.

The following example demonstrates row spanning in action:

  • “Davolio” cell spans two rows in the “Employee Name” column.
  • “Lunch Break” cell spans two rows and three columns simultaneously (combined row and column spanning) in the “1:00” column.
import { columnSpanData, ColumnSpanDataType } from './datasource';
import { Component, OnInit } from '@angular/core';
import { Column, GridLine, GridModule, QueryCellInfoEventArgs } from '@syncfusion/ej2-angular-grids';
import { EmitType } from '@syncfusion/ej2-base';

@Component({
    imports: [ GridModule ],
    standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' height='300px' [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='1:00' headerText='1:00 PM' width='120'></e-column>
            <e-column field='1:30' headerText='1:30 PM' width='120'></e-column>
            <e-column field='2:00' headerText='2:00 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 width?: string | number;
    public gridLines?: GridLine;
    public textWrap?: boolean;
    public queryCellInfoEvent: EmitType<QueryCellInfoEventArgs> = (args: QueryCellInfoEventArgs) => {
    const data: ColumnSpanDataType = args.data as ColumnSpanDataType;
    switch (data.EmployeeID) {
        case 10001:
            if (((args.column as 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;
            } else if ((args.column as Column).field === 'EmployeeName') {
                args.rowSpan = 2;
            } else if ((args.column as Column).field === '1:00') {
                args.colSpan = 3;
                args.rowSpan = 2;
            }
            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;
        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 {
        this.data = columnSpanData;
        this.gridLines = 'Both';
        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));

  • Spanning can be disabled for a particular Grid page by using the requestType value from the queryCellInfo event argument.
  • The rowSpan and colSpan attributes can be used together to merge cells both vertically and horizontally.

Limitations

  • The updateCell method does not support modifications to spanned cells.
  • The following features are incompatible:

    • Virtual scrolling
    • Infinite scrolling
    • Grouping
    • Row drag and drop
    • Autofill
    • Inline editing
    • Batch editing
    • CRUD operations

Row spanning using enableRowSpan property

For a simplified row spanning approach to vertically merge cells, use the enableRowSpan property.

When enableRowSpan is enabled:

  • The Grid automatically detects cells with matching data across adjacent rows.
  • Matching cells merge into a single cell visually.
  • No manual span configuration through queryCellInfo event required.
  • Improves readability by eliminating redundant data display.

This example demonstrates the enableRowSpan property for merging cells vertically:

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

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GridModule],
  providers: [FreezeService, SortService],
  template: `
    <ejs-grid #grid [dataSource]="telecastData" gridLines="Both" [enableHover]="false" [allowSelection]="false" [allowSorting]="true" [enableRowSpan]="true" [allowTextWrap]="true" [textWrapSettings]="{ wrapMode: 'Content' }" height="335" width="auto">
      <e-columns>
        <e-column field="Channel" headerText="Channel" width="150" freeze="Left" isPrimaryKey="true"></e-column>
        <e-column field="Genre" headerText="Genre" width="120" freeze="Left"></e-column>
        <e-column field="Program12AM" headerText="12 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program1AM" headerText="1 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program2AM" headerText="2 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program3AM" headerText="3 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program4AM" headerText="4 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program5AM" headerText="5 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program6AM" headerText="6 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program7AM" headerText="7 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program8AM" headerText="8 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program9AM" headerText="9 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program10AM" headerText="10 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program11AM" headerText="11 AM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program12PM" headerText="12 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program1PM" headerText="1 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program2PM" headerText="2 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program3PM" headerText="3 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program4PM" headerText="4 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program5PM" headerText="5 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program6PM" headerText="6 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program7PM" headerText="7 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program8PM" headerText="8 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program9PM" headerText="9 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program10PM" headerText="10 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
        <e-column field="Program11PM" headerText="11 PM" width="110" textAlign="Center" [allowSorting]="false"></e-column>
      </e-columns>
    </ejs-grid>
  `
})
export class AppComponent implements OnInit {

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

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

Row spanning can also be controlled at the column level. Set enableRowSpan to false in a column definition to disable merging for that column.

Limitation

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