Simple Table
oge-simple-table renders small lists — order lines, an audit log, a settings summary — as a native table whose rows you write yourself, with header cells that stay aligned and optional paging.
It is deliberately small: no sorting, filtering, selection, editing or virtualization, and no dependency on the grid package. Reach for the data grid when a list needs any of those. Screen readers get a real <table> — caption, column headers, cells — and a wide table scrolls inside its own labelled region on a narrow screen.
Basics
Pass value and, optionally, columns — without them each own key of the first row becomes a column. The table is a native with a headers; a column's align applies to its header and its cells alike.
| SKU | Product | Qty | Unit price |
|---|---|---|---|
| DSK-01 | Oak desk | 2 | 420 |
| CHR-07 | Mesh office chair | 4 | 260 |
| LMP-12 | Desk lamp | 6 | 45 |
| SHF-03 | Wall shelf | 3 | 89 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeSimpleTable } from '@oge-ui/layout';
import type { OgeSimpleTableColumn } from '@oge-ui/layout';
interface OrderLine {
sku: string;
product: { name: string };
qty: number;
price: number;
status: 'shipped' | 'pending' | 'backordered';
}
@Component({
selector: 'demo-root',
imports: [OgeSimpleTable],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- A native <table>: caption, <th scope="col"> headers, one alignment
class shared by a column's header and cells. -->
<oge-simple-table [value]="lines" [columns]="columns" caption="Order lines" />
`,
})
export class Demo {
protected readonly lines: OrderLine[] = [
{ sku: 'DSK-01', product: { name: 'Oak desk' }, qty: 2, price: 420, status: 'shipped' },
{ sku: 'CHR-07', product: { name: 'Mesh office chair' }, qty: 4, price: 260, status: 'pending' },
{ sku: 'LMP-12', product: { name: 'Desk lamp' }, qty: 6, price: 45, status: 'backordered' },
{ sku: 'SHF-03', product: { name: 'Wall shelf' }, qty: 3, price: 89, status: 'shipped' },
];
protected readonly columns: OgeSimpleTableColumn[] = [
{ field: 'sku', caption: 'SKU', width: '7rem' },
{ field: 'product.name', caption: 'Product' },
{ field: 'qty', caption: 'Qty', align: 'end', width: '5rem' },
{ field: 'price', caption: 'Unit price', align: 'end' },
];
}Row templates
An ogeSimpleTableRow template writes the whole of a row with free markup — badges, links, pipes. Bind [ogeSimpleTableRowOf] to the rows and let-line is typed under strictTemplates; columns[i].cellClass puts a cell on the same alignment as its header.
| SKU | Product | Qty | Unit price |
|---|---|---|---|
DSK-01 | Oak desk | 2 | $840.00 |
CHR-07 | Mesh office chair pending | 4 | $1,040.00 |
LMP-12 | Desk lamp backordered | 6 | $270.00 |
SHF-03 | Wall shelf | 3 | $267.00 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeSimpleTable, OgeSimpleTableRow } from '@oge-ui/layout';
import { CurrencyPipe } from '@angular/common';
import type { OgeSimpleTableColumn } from '@oge-ui/layout';
interface OrderLine {
sku: string;
product: { name: string };
qty: number;
price: number;
status: 'shipped' | 'pending' | 'backordered';
}
@Component({
selector: 'demo-root',
imports: [OgeSimpleTable, OgeSimpleTableRow, CurrencyPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- [ogeSimpleTableRowOf] types let-line as OrderLine under strictTemplates.
columns[i].cellClass lines a cell up with its header. -->
<oge-simple-table [value]="lines" [columns]="columns" ariaLabel="Order lines with status">
<ng-template
ogeSimpleTableRow
[ogeSimpleTableRowOf]="lines"
let-line
let-columns="columns"
>
<tr>
<td><code>{{ line.sku }}</code></td>
<td>
{{ line.product.name }}
@if (line.status !== 'shipped') {
<span class="status">{{ line.status }}</span>
}
</td>
<td [class]="columns[2].cellClass">{{ line.qty }}</td>
<td [class]="columns[3].cellClass">
{{ line.price * line.qty | currency: 'USD' }}
</td>
</tr>
</ng-template>
</oge-simple-table>
`,
})
export class Demo {
protected readonly lines: OrderLine[] = [
{ sku: 'DSK-01', product: { name: 'Oak desk' }, qty: 2, price: 420, status: 'shipped' },
{ sku: 'CHR-07', product: { name: 'Mesh office chair' }, qty: 4, price: 260, status: 'pending' },
{ sku: 'LMP-12', product: { name: 'Desk lamp' }, qty: 6, price: 45, status: 'backordered' },
{ sku: 'SHF-03', product: { name: 'Wall shelf' }, qty: 3, price: 89, status: 'shipped' },
];
protected readonly columns: OgeSimpleTableColumn[] = [
{ field: 'sku', caption: 'SKU', width: '7rem' },
{ field: 'product.name', caption: 'Product' },
{ field: 'qty', caption: 'Qty', align: 'end', width: '5rem' },
{ field: 'price', caption: 'Unit price', align: 'end' },
];
}Paging, footer and empty state
paging shows pageSize rows with previous / next buttons and a range text that is announced politely. An ogeSimpleTableFooter template receives every row — not just the page — for totals, and ogeSimpleTableEmpty fills the full-width row shown without data.
| Time | User | Action |
|---|---|---|
| 09:00 | ayse | signed in |
| 09:04 | mehmet | exported a report |
| 09:08 | ali | changed a role |
| 09:12 | ayse | signed in |
| 09:16 | mehmet | exported a report |
| 12 events | ||
Page 1
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeSimpleTable, OgeSimpleTableFooter, OgeSimpleTableEmpty } from '@oge-ui/layout';
import type { OgeSimpleTableColumn } from '@oge-ui/layout';
@Component({
selector: 'demo-root',
imports: [OgeSimpleTable, OgeSimpleTableFooter, OgeSimpleTableEmpty],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- paging: previous / next and a polite "1–5 of 12" text. The footer
receives every row (not just the page) for totals. -->
<oge-simple-table
[value]="events"
[columns]="columns"
[paging]="true"
[pageSize]="5"
[(pageIndex)]="page"
[compact]="true"
caption="Audit log"
>
<ng-template ogeSimpleTableFooter let-all="rows">
<tr>
<td colspan="3">{{ all.length }} events</td>
</tr>
</ng-template>
<ng-template ogeSimpleTableEmpty>No events yet</ng-template>
</oge-simple-table>
<p>Page {{ page() + 1 }}</p>
`,
})
export class Demo {
protected readonly page = signal(0);
protected readonly events = Array.from({ length: 12 }, (_, i) => ({
at: `09:${String(i * 4).padStart(2, '0')}`,
user: ['ayse', 'mehmet', 'ali'][i % 3],
action: ['signed in', 'exported a report', 'changed a role'][i % 3],
}));
protected readonly columns: OgeSimpleTableColumn[] = [
{ field: 'at', caption: 'Time', width: '6rem' },
{ field: 'user', caption: 'User' },
{ field: 'action', caption: 'Action' },
];
}