Today I’ll show you how to implement one of the most sought-after features with the grid: automatically wrapping column text in both cells and headers, thus allowing you to easily show large or unconventional amounts of text in your grids.

Live demo code

See this live example code in all the major frameworks here:
  •  Angular
  •  React
  •  JavaScript
  •  Vue

In this article we'll build a grid whose headers and rows can automatically adjust their heights to accommodate any column header text length, whether that be a single line or four, no ellipses and no cutoffs, if it's there, we'll show it!

In this blog, we'll be going over:

Part 1: Setting Up

Okay, so before we jump into the meat of the article, let’s quickly run through the simple grid that I will be building this example upon. This Plunker will be our base-case.

As you can see, there are three columns, each has a header with a long text—courtesy of Lorem Ipsum—and a couple of rows just for good measure—because what is a grid without any rows!?

Part 2: Column Header Template

When customising column headers, it can be useful to declare our own column header template which the grid will use instead of the default. This allows us to see how we're interacting with our grid's headers to achieve our goal. In this case, we will copy and use the column header template provided in our documentation:

var defaultColDef = {
    flex: 1,
    resizable: true,
    sortable: true,
    headerComponentParams: {
        template:
            '<div class="ag-cell-label-container" role="presentation">' +
            '  <span ref="eMenu" class="ag-header-icon ag-header-cell-menu-button"></span>' +
            '  <div ref="eLabel" class="ag-header-cell-label" role="presentation">' +
            '    <span ref="eSortOrder" class="ag-header-icon ag-sort-order"></span>' +
            '    <span ref="eSortAsc" class="ag-header-icon ag-sort-ascending-icon"></span>' +
            '    <span ref="eSortDesc" class="ag-header-icon ag-sort-descending-icon"></span>' +
            '    <span ref="eSortNone" class="ag-header-icon ag-sort-none-icon"></span>' +
            '    <span ref="eText" class="ag-header-cell-text" role="columnheader" style="white-space: normal;"></span>' +
            '    <span ref="eFilter" class="ag-header-icon ag-filter-icon"></span>' +
            '  </div>' +
            '</div>',
    },
};
As this template will be used for all of our columns, I’ve declared it in the defaultColDef

Note: The style declared for the eText span: white-space: normal; is crucial for making sure the text will display how we want it. If you're not familiar with this, the I'd recommend reading up on it here.  

Part 3: Getting the Column Header Height

We'll use two functions to get the effect we're after; the first will ask the question: "How tall should the headers be?" And the second will implement the answer from the first. Let’s call the first function headerHeightGetter().

To get the required height, we'll use the element.clientHeight property of the texts inside each column header. If you’re unfamiliar with clientHeight, check out its entry on W3Schools or MDN.

However, before querying my elements on the DOM, I need to put them in an array. This will allow me to later map() over them to find the tallest header to set the height of all column headers. To do this, I’m going to use document.querySelectorAll(), and give it the class declared in the header template: 'ag-header-cell-text'. As querySelectorAll() will return a Nodelist—find out more here—we will be using the spread syntax to create an array which we can then query via dot notation as shown below:

    var columnHeaderTexts = [
        ...document.querySelectorAll('.ag-header-cell-text'),
    ];

Once we’ve got our array, we can map over its elements to get the header heights like this:

    var clientHeights = columnHeaderTexts.map(
        headerText => headerText.clientHeight
    );

Lastly, using Math.max() we can find the largest number in the clientHeights and return it.

function headerHeightGetter() {
    var columnHeaderTexts = [
        ...document.querySelectorAll('.ag-header-cell-text'),
    ];
    var clientHeights = columnHeaderTexts.map(
        headerText => headerText.clientHeight
    );
    var tallestHeaderTextHeight = Math.max(...clientHeights);

    return tallestHeaderTextHeight;
}

Part 4: Setting the Column Header Height

Now we have the height required to fully represent our longest column header text, we need to set it via ag-Grid's API. To do this, let’s first create a function called headerHeightSetter(). In this function we create two variables:

  1. The first variable called padding to give the column header a little more room than what’s required to fit the text, we don’t want the text to be just-about squished in, we want the text to have a little room to breathe. Please set the padding value to fit your needs.
  2. The second variable will come from headerHeightGetter() and will be combined with the padding to give us the finalHeight.

Here's the full code implementing this below:

function headerHeightSetter() {
    var padding = 20;
    var height = headerHeightGetter() + padding;
    gridOptions.api.setHeaderHeight(height);
}

Part 5: Header Text Wrapping

Now we’ve built the engine, time to hook up the drive-shaft and give this baby a spin! As the headerHeightSetter() function already calls headerHeightGetter(), all we need to do is trigger it when the grid renders. This can be done in the gridOptions where I’ve hooked headerHeightSetter() up to the onFirstDataRendered callback. This means that our height will be dynamically set when data is rendered on our grid.

var gridOptions = {
    columnDefs: columnDefs,
    rowData: rowData,
    defaultColDef: defaultColDef,
    onFirstDataRendered: headerHeightSetter,
    onColumnResized: headerHeightSetter,
};

However, we’re still missing a case, what if the grid—or a column—is resized? I’d expect the columns to automatically resize to fill the space. Luckily this can be done by hooking up the headerHeightSetter() to another callback: onColumnResized.

Now, each time the width of the grid or columns is changed, the setter is called and the heights are recalculated! Add the resizable: true property to your defaultColDef and run the grid. The column header height adjusts as the user resizes it, as shown below:

Part 6: How Does Sorting Work?

So now we have a grid that can handle multi-line header texts. as it happens, it can also handle sorting. But it wasn't just as simple as declaring sortable: true in the defaultColDef.

The astute amongst you may have noticed that in the header template there were elements related to sorting:

'    <span ref="eSortAsc" class="ag-header-icon ag-sort-ascending-icon"></span>' +
'    <span ref="eSortDesc" class="ag-header-icon ag-sort-descending-icon"></span>' +
'    <span ref="eSortNone" class="ag-header-icon ag-sort-none-icon"></span>' +

When we provided a custom header template, we told the grid that wanted full control of everything to do with the header, which includes sorting functionality. Without these lines code, our sorting functionality would not work so make sure to include them or your own version of them. Coincidentally, this also means that they are fully customisable to your heart's desires!


Part 7: Cell Text Wrapping

It's also possible to achieve a similar text wrapping effect in cells. However, this effect is achieved differently from headers.

In this case we will be using ag-Grid's inbuilt auto row height & text wrapping features. Both of these features can be turned on by simply amending a column's definitions—or defaultColDef—like so:

var defaultColDef = {
    flex: 1,
    resizable: true,
    sortable: true
    wrapText: true,     // <-- HERE
    autoHeight: true,   // <-- & HERE    

Summary

And there we go, that's multi-line column headers and cell texts! I hope this can help you implement the dynamic multi-line header behaviour your users expect.

To find out more about column headers in ag-Grid, be sure to check out our documentation which is choc-full of many more features to try out.

Lastly if you're new to ag-Grid and want to see what all the hubbub is about, why not try it out — for free — by checking out our getting started guides.