Columns
Banded headers, lookup columns (a stored code rendered — and edited/filtered — as its label), calculated display columns, initial sorting via sortOrder, and responsive hiding: shrink the window and watch City, then Last Name disappear.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeColumn, OgeColumnGroup, OgeGrid } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeColumn, OgeColumnGroup, OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-grid [data]="employees" keyField="id" [wordWrap]="true"
[editing]="{ mode: 'cell' }" [filterRow]="true">
<oge-column field="id" [width]="70" dataType="number" sortOrder="desc" />
<!-- banded columns: one shared header over several columns -->
<oge-column-group caption="Person">
<oge-column field="firstName" />
<oge-column field="lastName" [hidingPriority]="1" />
</oge-column-group>
<!-- lookup: store a code, display (and edit/filter with) a label -->
<oge-column field="department" caption="Department"
[lookup]="{ dataSource: departments, valueExpr: 'code', displayExpr: 'label' }" />
<!-- calculated column: display-only derived value -->
<oge-column caption="Yearly" dataType="number" [calculateCellValue]="yearly" />
<!-- responsive: lowest hidingPriority disappears first on narrow screens -->
<oge-column field="city" [hidingPriority]="0" />
</oge-grid>
`,
})
export class Demo {
readonly employees = [
{ id: 1, firstName: 'Ali', lastName: 'Yılmaz', department: 'Engineering', city: 'İstanbul', salary: 8400, hireDate: '2019-03-11' },
{ id: 2, firstName: 'Ayşe', lastName: 'Kaya', department: 'Sales', city: 'Ankara', salary: 7200, hireDate: '2020-07-02' },
{ id: 3, firstName: 'Mehmet', lastName: 'Demir', department: 'Engineering', city: 'İzmir', salary: 9100, hireDate: '2018-01-23' },
{ id: 4, firstName: 'Zeynep', lastName: 'Şahin', department: 'Finance', city: 'İstanbul', salary: 6800, hireDate: '2021-11-15' },
{ id: 5, firstName: 'Emre', lastName: 'Çelik', department: 'Support', city: 'Bursa', salary: 5400, hireDate: '2022-05-09' },
];
protected readonly departments = [
{ code: 'Engineering', label: 'Engineering' },
{ code: 'Sales', label: 'Sales' },
{ code: 'Finance', label: 'Finance' },
{ code: 'Support', label: 'Support' },
];
protected readonly yearly = (row: { salary: number }): number => row.salary * 12;
}Mobile / adaptive
Responsive hiding never makes data unreachable: once a column with a hidingPriority leaves a narrow grid, every row gets a + toggle (a real button with aria-expanded) that shows the hidden columns' caption / value pairs, formatted like the cells. columnHidingMode="hide" restores the old drop-only behavior.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeColumn, OgeGrid } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeColumn, OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- a narrow grid: columns with a hidingPriority leave the row, and each
row's + toggle reveals their caption / value pairs (columnHidingMode
'detail' is the default — 'hide' drops the data instead) -->
<div style="max-width: 420px">
<oge-grid [data]="employees" keyField="id" columnHidingMode="detail">
<oge-column field="firstName" caption="First Name" [width]="140" />
<oge-column field="lastName" caption="Last Name" [width]="140" [hidingPriority]="3" />
<oge-column field="department" caption="Department" [width]="140" [hidingPriority]="2" />
<oge-column field="city" caption="City" [width]="120" [hidingPriority]="1" />
<oge-column field="salary" caption="Salary" dataType="number" [width]="120"
[format]="money" [hidingPriority]="0" />
</oge-grid>
</div>
`,
})
export class Demo {
readonly employees = [
{ id: 1, firstName: 'Ali', lastName: 'Yılmaz', department: 'Engineering', city: 'İstanbul', salary: 8400, hireDate: '2019-03-11' },
{ id: 2, firstName: 'Ayşe', lastName: 'Kaya', department: 'Sales', city: 'Ankara', salary: 7200, hireDate: '2020-07-02' },
{ id: 3, firstName: 'Mehmet', lastName: 'Demir', department: 'Engineering', city: 'İzmir', salary: 9100, hireDate: '2018-01-23' },
{ id: 4, firstName: 'Zeynep', lastName: 'Şahin', department: 'Finance', city: 'İstanbul', salary: 6800, hireDate: '2021-11-15' },
{ id: 5, firstName: 'Emre', lastName: 'Çelik', department: 'Support', city: 'Bursa', salary: 5400, hireDate: '2022-05-09' },
];
protected readonly money = (value: unknown): string =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(
Number(value),
);
}Custom sort keys & filter expressions
calculateSortValue replaces the value a column sorts by — here the department lookup sorts by its Turkish label instead of the stored code. calculateFilterExpression rewrites what the filter row produces: typing a number into Salary Band filters the underlying salary by thousands.
<oge-column field="department"
[lookup]="{ dataSource: departments, valueExpr: 'code', displayExpr: 'label' }"
[calculateSortValue]="departmentLabel" />
<oge-column field="salary" caption="Salary Band (k)"
[calculateFilterExpression]="salaryBandFilter" />
// sort the lookup column by its display label, not the stored code
departmentLabel = (row) => departments.find(d => d.code === row.department)?.label;
// '60' in the filter row means the 60k band: 60000 <= salary < 61000
salaryBandFilter = (value, operator) => value == null ? null : {
type: 'and', operands: [
{ type: 'binary', field: 'salary', op: 'ge', value: +value * 1000 },
{ type: 'binary', field: 'salary', op: 'lt', value: (+value + 1) * 1000 },
],
};Right-to-left
Set rtlEnabled (or wrap the grid in a dir="rtl" container — it auto-detects the inherited direction). Layout, pinned columns, chevrons and keyboard arrows all mirror; the library uses CSS logical properties throughout.
<oge-grid [data]="employees" keyField="id" [rtlEnabled]="true">
<oge-column field="id" caption="No" [width]="70" dataType="number" pinned="left" />
<oge-column field="firstName" caption="Ad" />
<oge-column field="salary" caption="Maaş" dataType="number" />
</oge-grid>Notes
- Lookup columns keep the raw value in your data; display, the filter select, the header filter and the default editor all speak the label. CSV export writes the label too.
- Calculated columns (
calculateCellValue) are display-only: without afieldthey can't sort or filter (server contracts stay serializable). - Bands are purely visual — resize, pinning and the column chooser keep working on the leaf columns.
sortOrder/sortIndex/groupIndexonly seed the initial state; user interaction andstateKeyrestores always win.