FrozenCells API (Angular)

The Cell Freeze feature in FlexGrid allows users to lock certain rows and columns, ensuring they remain visible while scrolling through other parts of the grid. Previously, FlexGrid supported freezing cells on the left and top using FrozenRows and FrozenColumns. The feature has now been enhanced with the FrozenCells to include options for freezing cells on the right and bottom as well.

Supported Operations

  1. Freeze columns on the left or right
  2. Freeze rows at the top or bottom
  3. Combine one horizontal (left or right) and one vertical (top or bottom) freeze

Important: While the frozenCells API provides options for all four sides, you can only freeze cells on two sides simultaneously:

  • One horizontal side (either left OR right)
  • One vertical side (either top OR bottom)

Note:

  • If both left and right are set, left will take effect.
  • Similarly, if both top and bottom, top will be applied.

Learn about FlexGrid | FlexGrid API Reference

This example uses Angular.

app.component.ts
index.html
app.component.html
styles.css
Copy to CodeMine
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, NgModule, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import * as wjcGrid from '@mescius/wijmo.grid'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { data: any[]; // DataSvc will be passed by derived classes constructor() { this.data = this._getData(); } @ViewChild('flex', { static: true }) grid: wjcGrid.FlexGrid; freezeCells( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { const frozenCells = { top: parseInt(topFreeze.value) || 0, bottom: parseInt(bottomFreeze.value) || 0, left: parseInt(leftFreeze.value) || 0, right: parseInt(rightFreeze.value) || 0, }; // Apply the frozenCells to the grid this.grid.frozenCells = frozenCells; // Read back what was actually applied const appliedFrozenCells = this.grid.frozenCells; // Prepare the message const messageElement = document.getElementById('errorMsg'); let appliedFreezeMsg = 'Applied freeze: '; if (Object.keys(appliedFrozenCells).length === 0) { appliedFreezeMsg += 'None'; } else { appliedFreezeMsg += (Object as any) .entries(appliedFrozenCells) .map(([key, value]: [string, any]) => `${key}: ${value}`) .join(', '); } // Display the message messageElement.style.display = 'block'; messageElement.style.color = 'green'; messageElement.textContent = appliedFreezeMsg; } clearFreezeCells( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { this.grid.frozenCells = { top: 0, bottom: 0, left: 0, right: 0 }; topFreeze.value = bottomFreeze.value = leftFreeze.value = rightFreeze.value = '0'; this.handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze); document.getElementById('errorMsg').innerHTML = ''; } handleInputChange( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { const topValue = parseInt(topFreeze.value, 10) || 0; const bottomValue = parseInt(bottomFreeze.value, 10) || 0; const leftValue = parseInt(leftFreeze.value, 10) || 0; const rightValue = parseInt(rightFreeze.value, 10) || 0; this.toggleDisable(topValue, bottomFreeze); this.toggleDisable(bottomValue, topFreeze); this.toggleDisable(leftValue, rightFreeze); this.toggleDisable(rightValue, leftFreeze); } toggleDisable(primaryValue: number, secondaryElement: HTMLInputElement) { secondaryElement.disabled = primaryValue > 0; } private _getData() { // generate some random data var countries = 'US,Germany,UK,Japan,Italy,Greece'.split(','), data = []; for (var i = 0; i < 200; i++) { data.push({ id: i, country: countries[i % countries.length], downloads: Math.round(Math.random() * 20000), sales: Math.random() * 10000, expenses: Math.random() * 5000, num1: Math.random() * 5000, num2: Math.random() * 5000, num3: Math.random() * 5000, num4: Math.random() * 5000, num5: Math.random() * 5000, }); } return data; } } // @NgModule({ imports: [WjGridModule, BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule {} // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, NgModule, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import * as wjcGrid from '@mescius/wijmo.grid'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { data: any[]; // DataSvc will be passed by derived classes constructor() { this.data = this._getData(); } @ViewChild('flex', { static: true }) grid: wjcGrid.FlexGrid; freezeCells( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { const frozenCells = { top: parseInt(topFreeze.value) || 0, bottom: parseInt(bottomFreeze.value) || 0, left: parseInt(leftFreeze.value) || 0, right: parseInt(rightFreeze.value) || 0, }; // Apply the frozenCells to the grid this.grid.frozenCells = frozenCells; // Read back what was actually applied const appliedFrozenCells = this.grid.frozenCells; // Prepare the message const messageElement = document.getElementById('errorMsg'); let appliedFreezeMsg = 'Applied freeze: '; if (Object.keys(appliedFrozenCells).length === 0) { appliedFreezeMsg += 'None'; } else { appliedFreezeMsg += (Object as any) .entries(appliedFrozenCells) .map(([key, value]: [string, any]) => `${key}: ${value}`) .join(', '); } // Display the message messageElement.style.display = 'block'; messageElement.style.color = 'green'; messageElement.textContent = appliedFreezeMsg; } clearFreezeCells( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { this.grid.frozenCells = { top: 0, bottom: 0, left: 0, right: 0 }; topFreeze.value = bottomFreeze.value = leftFreeze.value = rightFreeze.value = '0'; this.handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze); document.getElementById('errorMsg').innerHTML = ''; } handleInputChange( topFreeze: HTMLInputElement, bottomFreeze: HTMLInputElement, leftFreeze: HTMLInputElement, rightFreeze: HTMLInputElement, ) { const topValue = parseInt(topFreeze.value, 10) || 0; const bottomValue = parseInt(bottomFreeze.value, 10) || 0; const leftValue = parseInt(leftFreeze.value, 10) || 0; const rightValue = parseInt(rightFreeze.value, 10) || 0; this.toggleDisable(topValue, bottomFreeze); this.toggleDisable(bottomValue, topFreeze); this.toggleDisable(leftValue, rightFreeze); this.toggleDisable(rightValue, leftFreeze); } toggleDisable(primaryValue: number, secondaryElement: HTMLInputElement) { secondaryElement.disabled = primaryValue > 0; } private _getData() { // generate some random data var countries = 'US,Germany,UK,Japan,Italy,Greece'.split(','), data = []; for (var i = 0; i < 200; i++) { data.push({ id: i, country: countries[i % countries.length], downloads: Math.round(Math.random() * 20000), sales: Math.random() * 10000, expenses: Math.random() * 5000, num1: Math.random() * 5000, num2: Math.random() * 5000, num3: Math.random() * 5000, num4: Math.random() * 5000, num5: Math.random() * 5000, }); } return data; } } // @NgModule({ imports: [WjGridModule, BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule {} // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexGrid Frozen Rows and Columns</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="flex-grow-0"> <div id="freezeControls"> <h2 class="text-center">Grid Freeze Cells API</h2> <div class="inputs"> <div class="inputRow"> <div class="ctrl"> <label for="topFreeze" class="form-label">Freeze Rows (Top):</label> <input #topFreeze type="number" id="topFreeze" min="0" value="0" class="form-control" (change)="handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze)" /> </div> <div class="ctrl"> <label for="bottomFreeze" class="form-label">Freeze Rows (Bottom):</label> <input #bottomFreeze type="number" id="bottomFreeze" min="0" value="0" class="form-control" (change)="handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze)" /> </div> </div> <div class="inputRow"> <div class="ctrl"> <label for="leftFreeze" class="form-label">Freeze Columns (Left):</label> <input #leftFreeze type="number" id="leftFreeze" min="0" value="0" class="form-control" (change)="handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze)" /> </div> <div class="ctrl"> <label for="rightFreeze" class="form-label">Freeze Columns (Right):</label> <input #rightFreeze type="number" id="rightFreeze" min="0" value="0" class="form-control" (change)="handleInputChange(topFreeze, bottomFreeze, leftFreeze, rightFreeze)" /> </div> </div> </div> <div class="d-grid gap-2 freezeBtns"> <button type="button" class="btn btn-primary" id="btnFreezeCells" (click)="freezeCells(topFreeze, bottomFreeze, leftFreeze, rightFreeze)">Apply Freeze</button> <button type="button" class="btn btn-primary" id="btnClearFreezeCells" (click)="clearFreezeCells(topFreeze, bottomFreeze, leftFreeze, rightFreeze)">Clear Freeze</button> </div> <p id="errorMsg" class="text-center">{{ errorMessage }}</p> </div> </div> <div class="flex-grow-1"> <wj-flex-grid #flex [allowPinning]="'Both'" [(itemsSource)]="data"></wj-flex-grid> </div>
.wj-flexgrid { max-height: 400px; max-width: 1000px; margin: 10px 0; } /* style frozen cells */ .wj-cell.wj-frozen:not(.wj-header):not(.wj-group):not(.wj-state-selected):not(.wj-state-multi-selected), .wj-cell.wj-frozen.wj-alt:not(.wj-header):not(.wj-group):not(.wj-state-selected):not(.wj-state-multi-selected) { background: rgb(255, 255, 125); } html, body { height: 800px; margin: 0; padding: 0; overflow: hidden; } .container-fluid { height: 100vh; display: flex; flex-direction: column; padding: 15px; box-sizing: border-box; overflow: hidden; margin: 0px 10px } #freezeControls { max-width:1000px; background-color: #f8f9fa; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; margin-bottom: 15px; } #freezeControls h2 { color: #007bff; margin-bottom: 20px; } .control-group { margin-bottom: 15px; } #errorMsg { color: #dc3545; font-weight: bold; margin-top: 10px; } .grid-container { flex-grow: 1; min-height: 0; /* Important for Firefox */ display: flex; flex-direction: column; } .inputRow{ display: flex; justify-content: space-between; } .ctrl{ width: 100%; margin: 0px 10px; } .freezeBtns button{ margin: 10px } .flex-container { display: flex; }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' }, '*.mjs': { format: 'esm' }, }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', '@angular/core': 'npm:@angular/core/fesm2022/core.mjs', '@angular/core/primitives/signals': 'npm:@angular/core/fesm2022/primitives/signals.mjs', '@angular/core/primitives/event-dispatch': 'npm:@angular/core/fesm2022/primitives/event-dispatch.mjs', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.mjs', '@angular/compiler': 'npm:@angular/compiler/fesm2022/compiler.mjs', '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2022/platform-browser.mjs', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2022/platform-browser-dynamic.mjs', '@angular/http': 'npm:@angular/http/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);