Row and Column Freezing (Angular)

The MultiRow control allows you to freeze rows and columns so they remain in view as the user scrolls the grid. You can set the number of frozen rows and columns through the frozenColumns and frozenRows properties.

Frozen cells can be edited and selected as regular cells, exactly as in Excel and in the FlexGrid control.

This example allows you to toggle whether the first group of rows and columns should be frozen.

Learn about MultiRow | MultiRow API Reference

This example uses Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wjMultirow from '@mescius/wijmo.grid.multirow'; import '@angular/compiler'; import { Component, Inject, enableProdMode, ViewChild, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { WjGridMultirowModule } from '@mescius/wijmo.angular2.grid.multirow'; import { DataService } from './app.data'; // @Component({ standalone: true, providers: [DataService], imports: [WjGridMultirowModule, BrowserModule], selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { @ViewChild('freezeMultirow', { static: true }) freezeMultirow: wjMultirow.MultiRow; orders: any layoutDefinition: any; textContent: string; // DataSvc will be passed by derived classes constructor(@Inject(DataService) private dataService: DataService) { let data = dataService.getData(); this.orders = data.orders; this.layoutDefinition = data.ldTwoLines; this.textContent = 'Freeze'; } // onFreezeClick() { this.freezeMultirow.frozenColumns = this.freezeMultirow.frozenColumns ? 0 : 2; this.freezeMultirow.frozenRows = this.freezeMultirow.frozenRows ? 0 : 2; this.textContent = this.freezeMultirow.frozenRows == 0 ? 'Freeze' : 'Unfreeze'; } } //\\ // enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch(err => console.error(err)); });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo MultiRow Row and Column Freezing</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="container-fluid"> <wj-multi-row #freezeMultirow [itemsSource]="orders" [layoutDefinition]="layoutDefinition"></wj-multi-row> <br /> <button class="btn btn-default" (click)="onFreezeClick()" [innerHTML]="textContent"> </button> </div>
import { Injectable } from '@angular/core'; import * as wjCore from '@mescius/wijmo'; import * as wjGrid from '@mescius/wijmo.grid'; interface Cell { binding: string; header: string; cssClass?: string; format?: string; isReadOnly?: boolean; wordWrap?: boolean; width: number; } interface Line { Cells: Cell[]; header?: string; colspan?: number; } export interface AppData { orders: wjCore.CollectionView; groupedOrders: wjCore.CollectionView; pagedOrders: wjCore.CollectionView; addNewOrders: wjCore.CollectionView; ldOneLine: Cell[]; ldTwoLines: Line[]; ldThreeLines: Line[]; layoutDefs: wjCore.CollectionView; } @Injectable() export class DataService { getData(): AppData { // create some data let appData: any = {}, customers = [], firstNames = 'Aaron,Paul,John,Mark,Sue,Tom,Bill,Joe,Tony,Brad,Frank,Chris,Pat'.split(','), lastNames = 'Smith,Johnson,Richards,Bannon,Wong,Peters,White,Brown,Adams,Jennings'.split(','), cities = 'York,Paris,Rome,Cairo,Florence,Sidney,Hamburg,Vancouver'.split(','), states = 'SP,RS,RN,SC,CS,RT,BC'.split(','); for (let i = 0; i < 50; i++) { let first = firstNames[randBetween(0, firstNames.length - 1)], last = lastNames[randBetween(0, lastNames.length - 1)]; customers.push({ id: i, name: first + ' ' + last, address: randBetween(100, 10000) + ' ' + lastNames[randBetween(0, lastNames.length - 1)] + ' St.', city: cities[randBetween(0, cities.length - 1)], state: states[randBetween(0, states.length - 1)], zip: wjCore.format('{p1:d5}-{p2:d3}', { p1: randBetween(10000, 99999), p2: randBetween(100, 999) }), email: first + '.' + last + '@gmail.com', phone: wjCore.format('{p1:d3}-{p2:d4}', { p1: randBetween(100, 999), p2: randBetween(1000, 9999) }) }); } let cityMap = new wjGrid.DataMap(cities); let shippers = [ { id: 0, name: 'Speedy Express', email: 'speedy@gmail.com', phone: '431-3234', express: true }, { id: 1, name: 'Flash Delivery', email: 'flash@gmail.com', phone: '431-6563', express: true }, { id: 2, name: 'Logitrax', email: 'logitrax@gmail.com', phone: '431-3981', express: false }, { id: 3, name: 'Acme Inc', email: 'acme@gmail.com', phone: '431-3113', express: false } ]; let orders = []; let today = new Date(); for (let i = 0; i < 20; i++) { let shipped = wjCore.DateTime.addDays(today, -randBetween(1, 3000)); orders.push({ id: i, date: wjCore.DateTime.addDays(shipped, -randBetween(1, 5)), shippedDate: shipped, amount: randBetween(10000, 500000) / 100, customer: customers[randBetween(0, customers.length - 1)], shipper: shippers[randBetween(0, shippers.length - 1)] }); } function randBetween(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1) + min); } // expose orders CollectionView to the app appData.orders = new wjCore.CollectionView(orders); // expose grouped orders CollectionView to the app appData.groupedOrders = new wjCore.CollectionView(orders, { groupDescriptions: [ 'customer.city' ] }); // expose paged orders CollectionView to the app appData.pagedOrders = new wjCore.CollectionView(orders, { pageSize: 4 }); // expose addNew oders CollectionView to the app appData.addNewOrders = new wjCore.CollectionView(orders, { newItemCreator: function () { return { // add empty customer and shipper objects to new orders customer: {}, shipper: {} } }, }); appData.addNewOrders.moveCurrentToLast(); // refresh views when data source changes let ordersRefreshing = false; appData.orders.collectionChanged.addHandler(function () { ordersRefreshing = true; if (!pagedOrdersRefreshing) { appData.pagedOrders.refresh(); } if (!groupedOrdersRefreshing) { appData.groupedOrders.refresh(); } if (!addNewOrdersRefreshing) { appData.addNewOrders.refresh(); } ordersRefreshing = false; }); // addNew orders let addNewOrdersRefreshing = false; appData.addNewOrders.collectionChanged.addHandler(function () { addNewOrdersRefreshing = true; if (!ordersRefreshing) { appData.orders.refresh(); } if (!pagedOrdersRefreshing) { appData.pagedOrders.refresh(); } if (!groupedOrdersRefreshing) { appData.groupedOrders.refresh(); } addNewOrdersRefreshing = false; }); // grouped orders let groupedOrdersRefreshing = false; appData.groupedOrders.collectionChanged.addHandler(function () { groupedOrdersRefreshing = true; if (!ordersRefreshing) { appData.orders.refresh(); } if (!pagedOrdersRefreshing) { appData.pagedOrders.refresh(); } if (!addNewOrdersRefreshing) { appData.addNewOrders.refresh(); } groupedOrdersRefreshing = false; }); // paged orders let pagedOrdersRefreshing = false; appData.pagedOrders.collectionChanged.addHandler(function () { pagedOrdersRefreshing = true; if (!ordersRefreshing) { appData.orders.refresh(); } if (!addNewOrdersRefreshing) { appData.addNewOrders.refresh(); } if (!groupedOrdersRefreshing) { appData.groupedOrders.refresh(); } pagedOrdersRefreshing = false; }); // sample layout definitions appData.ldOneLine = [ { cells: [{ binding: 'id', header: 'ID', cssClass: 'id', isReadOnly: true }] }, { cells: [{ binding: 'date', header: 'Ordered' }] }, { cells: [{ binding: 'shippedDate', header: 'Shipped' }] }, { cells: [{ binding: 'amount', header: 'Amount', format: 'c', cssClass: 'amount' }] }, { cells: [{ binding: 'customer.name', header: 'Customer' }] }, { cells: [{ binding: 'customer.address', header: 'Address', wordWrap: true }] }, { cells: [{ binding: 'customer.city', header: 'City', dataMap: cityMap }] }, { cells: [{ binding: 'customer.state', header: 'State', width: 45 }] }, { cells: [{ binding: 'customer.zip', header: 'Zip' }] }, { cells: [{ binding: 'customer.email', header: 'Customer Email', cssClass: 'email' }] }, { cells: [{ binding: 'customer.phone', header: 'Customer Phone' }] }, { cells: [{ binding: 'shipper.name', header: 'Shipper' }] }, { cells: [{ binding: 'shipper.email', header: 'Shipper Email', cssClass: 'email' }] }, { cells: [{ binding: 'shipper.phone', header: 'Shipper Phone' }] }, { cells: [{ binding: 'shipper.express', header: 'Express' }] } ]; appData.ldTwoLines = [ { header: 'Order', colspan: 2, cells: [ { binding: 'id', header: 'ID', cssClass: 'id', isReadOnly: true }, { binding: 'date', header: 'Ordered' }, { binding: 'amount', header: 'Amount', format: 'c', cssClass: 'amount' }, { binding: 'shippedDate', header: 'Shipped' } ] }, { header: 'Customer', colspan: 3, cells: [ { binding: 'customer.name', header: 'Name' }, { binding: 'customer.email', header: 'EMail', colspan: 2, cssClass: 'email' }, { binding: 'customer.address', header: 'Address' }, { binding: 'customer.city', header: 'City', dataMap: cityMap }, { binding: 'customer.state', header: 'State', width: 45 } ] }, { header: 'Shipper', cells: [ { binding: 'shipper.name', header: 'Shipper', colspan: 2 }, { binding: 'shipper.email', header: 'EMail', cssClass: 'email' }, { binding: 'shipper.express', header: 'Express' } ] } ]; appData.ldThreeLines = [ { header: 'Order', colspan: 2, cells: [ { binding: 'id', header: 'ID', colspan: 2, cssClass: 'id' }, { binding: 'amount', header: 'Amount', format: 'c', colspan: 2, cssClass: 'amount' }, { binding: 'date', header: 'Ordered' }, { binding: 'shippedDate', header: 'Shipped' } ] }, { header: 'Customer', colspan: 3, cells: [ { binding: 'customer.name', header: 'Name' }, { binding: 'customer.email', header: 'EMail', colspan: 2, cssClass: 'email' }, { binding: 'customer.address', header: 'Address', colspan: 2 }, { binding: 'customer.phone', header: 'Phone' }, { binding: 'customer.city', header: 'City', dataMap: cityMap }, { binding: 'customer.state', header: 'State', width: 45 }, { binding: 'customer.zip', header: 'Zip' }, ] }, { header: 'Shipper', cells: [ { binding: 'shipper.name', header: 'Shipper' }, { binding: 'shipper.email', header: 'EMail', cssClass: 'email' }, { binding: 'shipper.express', header: 'Express' } ] } ]; appData.layoutDefs = new wjCore.CollectionView([ { name: 'Traditional', description: 'Traditional grid view, with one row per record. The user must scroll horizontally to see the whole record.', def: appData.ldOneLine }, { name: 'Compact', description: 'This view uses two rows per record. The layout is divided into three groups: order, customer, and shipper', def: appData.ldTwoLines }, { name: 'Detailed', description: 'This view uses three rows per record. The layout is divided into three groups: order, customer, and shipper', def: appData.ldThreeLines } ]); return appData; } }
.wj-multirow { height: 400px; width: 600px; margin: 6px 0; } /* custom styling for frozen cells */ .wj-flexgrid .wj-cell.wj-frozen:not(.wj-header):not(.wj-group):not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #f3fbcc; }
(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' } }, 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.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/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.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);