Mastering AG-Grid Printing in React Applications
Printing data from a web application, especially from complex interactive components like data grids, often presents unique challenges. While users might simply hit Ctrl+P, the raw browser print output of a highly dynamic AG-Grid table might not always be the clean, well-formatted document you'd expect. This installment of our AG-Grid React series dives deep into effectively printing your AG-Grid tables, leveraging both AG-Grid's built-in capabilities and custom CSS to deliver a polished user experience.
The Challenge of Printing Dynamic Data Grids
Modern data grids, including AG-Grid, are designed for interactivity: virtualized scrolling, dynamic column resizing, filtering, sorting, and rich cell renderers. When you try to print such a component directly using the browser's default print functionality, several issues can arise:
- Truncated Content: Due to virtualization, only the currently visible rows might be printed, not the entire dataset.
- Unwanted UI Elements: Navigation bars, side panels, buttons, and other application chrome might appear on the printout.
- Poor Formatting: Column widths might not translate well, fonts might be too small or too large, and interactive elements (like sort icons) become static visual noise.
- Performance: Rendering a very large, complex grid directly for print can be slow or lead to crashes.
To overcome these, we need a more controlled approach.
Leveraging AG-Grid's Built-in Print Export
AG-Grid provides a powerful utility function designed specifically for printing: gridApi.exportDataAsPrintableHtml(). This method generates an HTML string representing the grid's data in a print-friendly format, complete with basic styling.
Introducing exportDataAsPrintableHtml
This function doesn't directly open the print dialog. Instead, it returns a string of HTML that you can then inject into a new browser window or an iframe, allowing you to control the printing process. This generated HTML is optimized for print, typically including all rows (not just visible ones) and a simplified structure.
Step-by-Step Implementation
Let's walk through how to integrate this into a React component.
-
Get Grid API Instance:
You'll need access to the
gridApi. In a functional React component, this is typically done usinguseStateoruseRefin conjunction with theonGridReadycallback prop of theAgGridReactcomponent.import React, { useRef, useState, useCallback } from 'react'; import { AgGridReact } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; const MyPrintableGrid = () => { const gridRef = useRef(); const [rowData] = useState([ { make: 'Toyota', model: 'Celica', price: 35000 }, { make: 'Ford', model: 'Mondeo', price: 32000 }, { make: 'Porsche', model: 'Boxster', price: 72000 } ]); const [columnDefs] = useState([ { field: 'make' }, { field: 'model' }, { field: 'price' } ]); const onGridReady = useCallback((params) => { gridRef.current.api = params.api; gridRef.current.columnApi = params.columnApi; }, []); // ... print function will go here }; -
Create a Print Function:
Define a function that calls
exportDataAsPrintableHtml()and then opens this HTML in a new window for printing.// Inside MyPrintableGrid component const onBtPrint = useCallback(() => { const htmlString = gridRef.current.api.exportDataAsPrintableHtml({ // Optional: customization options (see next step) }); // Create a new window to display and print the HTML const newWindow = window.open('', '', 'height=700,width=1200'); if (newWindow) { newWindow.document.write(htmlString); newWindow.document.close(); newWindow.print(); } else { alert('Please allow pop-ups for printing.'); } }, []); -
Customize Print Options:
The
exportDataAsPrintableHtmlmethod accepts an options object to fine-tune the output:suppressHeader: Boolean to hide column headers.suppressFooters: Boolean to hide pagination/footer elements.fileName: String for the suggested filename if saving.columnKeys: An array of column IDs to include only specific columns.onlySelected: Boolean to print only selected rows.processCellCallback: A function to modify cell values before printing.processHeaderCallback: A function to modify header values.autoPrint: Boolean (defaulttrue) to automatically callwindow.print()on the new window.
// Example with customization const onBtPrintCustom = useCallback(() => { const htmlString = gridRef.current.api.exportDataAsPrintableHtml({ suppressHeader: false, // Show headers columnKeys: ['make', 'price'], // Only print 'make' and 'price' columns processCellCallback: (params) => { if (params.column.getId() === 'price') { return `$${params.value}`; // Format price } return params.value; }, autoPrint: false // Don't auto-print, let user preview }); const newWindow = window.open('', '', 'height=700,width=1200'); if (newWindow) { newWindow.document.write(htmlString); newWindow.document.close(); // If autoPrint was false, you might add a print button to the new window here } }, []);
Understanding the Output
The HTML generated by exportDataAsPrintableHtml is a simplified table structure, often losing the interactive elements and complex styling of the live grid. This is intentional, as it's meant for static consumption. It will include basic AG-Grid print styles to ensure readability.
Enhancing Print Output with Custom CSS (`@media print`)
While AG-Grid's export provides a good base, you'll often want to apply your application's branding or further optimize the layout for paper. This is where CSS @media print rules come into play. These styles are only applied when a page is being printed or viewed in print preview.
Basic Print Styles Example
You can include a stylesheet specifically for print within your main application, or directly inject styles into the HTML generated by exportDataAsPrintableHtml.
/* In your main CSS file or a dedicated print.css */
/* Hide elements not relevant for printing */
@media print {
body {
margin: 0; /* Remove default body margins */
padding: 0;
}
/* Hide application navigation, buttons, footers etc. */
.app-header,
.app-sidebar,
.print-button, /* Hide the button that triggers print */
.app-footer {
display: none !important;
}
/* Make sure the AG-Grid container takes full width */
.ag-theme-alpine {
width: 100% !important;
height: auto !important; /* Allow content to flow across pages */
overflow: visible !important; /* Prevent scrollbars */
box-shadow: none !important; /* Remove shadows */
border: none !important;
}
/* Adjust font sizes for readability on paper */
.ag-cell, .ag-header-cell {
font-size: 10pt !important;
padding: 2px 5px !important;
}
/* Ensure grid lines are visible if needed, AG-Grid does this by default but can be overridden */
.ag-row, .ag-header-row {
border-bottom: 1px solid #ccc !important;
}
}
Advanced Print Styling Tips for AG-Grid
-
Hiding Unwanted Elements:
Use
display: none !important;for any elements you don't want to appear on the printout. This includes pagination controls, interactive buttons, or complex renderers that don't translate well to static paper. -
Adjusting Column Widths:
Sometimes, columns that look great on screen are too wide or too narrow on paper. You can target specific column classes or use general table cell styling:
@media print { /* For specific columns, if AG-Grid provides distinct classes */ .ag-header-cell-text[aria-label="Make"], .ag-cell[col-id="make"] { width: 15% !important; } /* Or more generally */ .ag-cell { max-width: 200px; /* Prevent excessively wide columns */ } } -
Page Breaks:
For large tables, you'll want to ensure that rows don't get cut in half across pages. AG-Grid's print export typically handles this reasonably well, but you can explicitly influence page breaks using CSS:
page-break-before: always;: Force a page break before an element.page-break-after: always;: Force a page break after an element.page-break-inside: avoid;: Prevent page breaks inside an element (e.g., a row).
@media print { .ag-row { page-break-inside: avoid; } } -
Repeating Table Headers:
For multi-page tables, it's crucial to have the table headers repeat on each new page. The HTML generated by AG-Grid's export uses standard
<table>,<thead>,<tbody>, and<tr>elements, which CSS can leverage:@media print { thead { display: table-header-group; /* This is the key for repeating headers */ } }Ensure that the generated HTML has a proper
<thead>containing your column headers for this to work effectively.
Putting It All Together: A Complete React Example
import React, { useRef, useState, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import './PrintGridStyles.css'; // Your custom print CSS
const MyPrintableGrid = () => {
const gridRef = useRef();
const [rowData] = useState([
{ id: 1, make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ id: 2, make: 'Ford', model: 'Mondeo', price: 32000, year: 2021 },
{ id: 3, make: 'Porsche', model: 'Boxster', price: 72000, year: 2022 },
{ id: 4, make: 'BMW', model: 'M3', price: 60000, year: 2023 },
{ id: 5, make: 'Mercedes', model: 'C-Class', price: 55000, year: 2020 },
{ id: 6, make: 'Audi', model: 'A4', price: 48000, year: 2021 },
{ id: 7, make: 'Volkswagen', model: 'Golf', price: 28000, year: 2022 },
{ id: 8, make: 'Tesla', model: 'Model 3', price: 50000, year: 2023 },
{ id: 9, make: 'Nissan', model: 'Titan', price: 40000, year: 2020 },
{ id: 10, make: 'Honda', model: 'CR-V', price: 30000, year: 2021 },
// Add more data to test multi-page printing
{ id: 11, make: 'Subaru', model: 'Outback', price: 38000, year: 2022 },
{ id: 12, make: 'Mazda', model: 'CX-5', price: 31000, year: 2023 },
{ id: 13, make: 'Hyundai', model: 'Elantra', price: 25000, year: 2020 },
{ id: 14, make: 'Kia', model: 'Telluride', price: 45000, year: 2021 },
{ id: 15, make: 'Volvo', model: 'XC60', price: 52000, year: 2022 },
{ id: 16, make: 'Land Rover', model: 'Discovery', price: 68000, year: 2023 },
{ id: 17, make: 'Jeep', model: 'Wrangler', price: 36000, year: 2020 },
{ id: 18, make: 'Fiat', model: '500', price: 20000, year: 2021 },
{ id: 19, make: 'Chevrolet', model: 'Malibu', price: 29000, year: 2022 },
{ id: 20, make: 'GMC', model: 'Sierra', price: 42000, year: 2023 },
]);
const [columnDefs] = useState([
{ field: 'id', headerName: 'ID', width: 80 },
{ field: 'make', headerName: 'Manufacturer', minWidth: 150 },
{ field: 'model', headerName: 'Model', minWidth: 180 },
{ field: 'year', headerName: 'Year', width: 100 },
{ field: 'price', headerName: 'Price', valueFormatter: p => `$${p.value.toLocaleString()}`, minWidth: 120 }
]);
const defaultColDef = {
flex: 1,
minWidth: 100,
sortable: true,
filter: true,
resizable: true,
};
const onGridReady = useCallback((params) => {
gridRef.current.api = params.api;
gridRef.current.columnApi = params.columnApi;
}, []);
const onBtPrint = useCallback(() => {
// Construct styles to be included in the new window.
// It's often better to link a CSS file, but for simplicity, we embed.
const printStyles = `
<style>
body { font-family: Arial, sans-serif; margin: 15px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.ag-header-cell-text { font-weight: bold; }
.ag-grid-print-table .ag-cell, .ag-grid-print-table .ag-header-cell {
font-size: 10pt !important;
padding: 4px 8px !important;
}
/* Repeat table header on each page */
thead { display: table-header-group; }
/* Prevent rows from breaking across pages */
.ag-row { page-break-inside: avoid; }
</style>
`;
const htmlString = gridRef.current.api.exportDataAsPrintableHtml({
// Exclude 'id' column from printing
columnKeys: ['make', 'model', 'year', 'price'],
processHeaderCallback: (params) => {
// Example: make header uppercase
return params.column.getColDef().headerName.toUpperCase();
},
processCellCallback: (params) => {
// If it's the price column, ensure it's formatted as currency
if (params.column.getId() === 'price') {
return params.valueFormatter ? params.valueFormatter(params) : `$${params.value.toLocaleString()}`;
}
return params.value;
},
autoPrint: true, // Automatically open print dialog
title: 'My AG-Grid Report' // Custom title for the print window
});
const newWindow = window.open('', '_blank');
if (newWindow) {
newWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>AG-Grid Print Output</title>
${printStyles}
</head>
<body>
${gridRef.current.api.getPrintableHtmlTitle() || 'AG-Grid Report'}
${htmlString}
</body>
</html>
`);
newWindow.document.close();
} else {
alert('Please allow pop-ups for printing.');
}
}, []);
return (
<div style={{ width: '100%', height: 'calc(100vh - 100px)' }}>
<div className="example-header print-button-container">
<button onClick={onBtPrint} className="print-button">Print AG-Grid Table</button>
</div>
<div className="ag-theme-alpine" style={{ height: '100%', width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
pagination={true} // Enable pagination for live grid, but print will show all
paginationPageSize={10}
></AgGridReact>
</div>
</div>
);
};
export default MyPrintableGrid;
And your PrintGridStyles.css for general application print optimization:
/* PrintGridStyles.css */
@media print {
/* Hide the print button itself */
.print-button-container {
display: none;
}
/* Adjust page margins for printing */
@page {
margin: 1.5cm; /* Example: 1.5cm margin on all sides */
}
/* Ensure header/footer styling for print if your app has them */
.app-header, .app-footer, .app-sidebar {
display: none !important;
}
/* General styling for the grid when printed */
.ag-theme-alpine {
border: none !important;
box-shadow: none !important;
overflow: visible !important; /* Important for seeing all rows */
height: auto !important; /* Allow content to expand */
}
/* You can override AG-Grid's default print styles here if needed */
.ag-grid-print-table {
/* This class is added by AG-Grid to the root table element in the printed HTML */
font-family: 'Times New Roman', serif; /* Use a classic print font */
font-size: 9pt;
}
.ag-grid-print-table th, .ag-grid-print-table td {
border: 0.5px solid #ccc; /* Finer borders for print */
}
.ag-grid-print-table th {
background-color: #e0e0e0; /* Lighter header background */
font-weight: bold;
}
}
Important Considerations for Printing AG-Grid Tables
-
Large Datasets:
While
exportDataAsPrintableHtmlis optimized, extremely large datasets (tens of thousands of rows) might still cause performance issues or browser crashes when rendered in a new window for printing. For such scenarios, consider server-side PDF generation or offering data export (e.g., CSV, Excel) as an alternative. -
User Experience:
Always provide clear feedback to the user. If pop-ups are blocked, inform them. Consider adding a "Print Preview" button that opens the new window with
autoPrint: false, allowing them to review before committing to printing. -
Browser Compatibility:
Test print functionality across different browsers (Chrome, Firefox, Edge, Safari). While standard, minor rendering differences in print media can occur.
-
Security Context:
Opening new windows programmatically might be restricted in certain security contexts or by browser settings. Ensure your users are aware of potential pop-up blockers.
Conclusion
Printing AG-Grid tables in React doesn't have to be a daunting task. By leveraging AG-Grid's dedicated exportDataAsPrintableHtml function, coupled with thoughtful application of CSS @media print rules, you can transform your interactive data grids into impeccably formatted, print-ready documents. This approach provides fine-grained control over what gets printed and how it looks, ensuring a professional and consistent user experience.