3 minute read

Define Column Types and Cell Types

1
2
3
4
5
6
7
8
9
enum TableCellType {
  Input = 'input',
  InputNumber = 'inputNumber',
  Checkbox = 'checkbox',
  Dropdown = 'dropdown',
  Icon = 'icon',
  Date = 'date',
  Link = 'link',
}
1
2
3
4
5
6
7
8
9
10
type TableColumn = {
  header: string;header.
  field: string;
  cellType?: TableCellType;
  width?: string;
  editable?: boolean;
  align?: 'left' | 'right' | 'center';
  iconClass?: string;
  iconOnClick?: AnyFunc;
};
  • header: string: Represents the column’s display name in the table header.
  • field: string: Specifies the key in the data source that the column will use.

Define Columns

1
2
3
4
5
6
7
8
9
10
cols: TableColumn[] = [
    {
      header: 'Code',
      field: 'code',
      editable: true,
      cellType: TableCellType.Input,
      width: '100px',
    },
    // More column definitions...
  ];

Define Form Group

To handle the data updates, I use a FormGroup that keeps track of each row’s data:

1
2
3
4
5
6
7
8
9
formGroup = new FormGroup({
  id: new FormControl(),
  code: new FormControl(),
  name: new FormControl(),
  active: new FormControl(),
  price: new FormControl(),
  category: new FormControl(),
  quantity: new FormControl(),
});

Since only one row can be edited at a time, the row’s data is updated into the form group during editing. To listen for editing events, I use onEditInit and onEditComplete

1
2
3
4
5
6
7
onEditInit(event: TableEditInitEvent) {
  const entity = this.products[event.index];
  this.formGroup.setValue(
    pick(entity, ['id', 'code', 'name', 'active', 'price', 'category', 'quantity']),
    { emitEvent: false }
  );
}
1
2
3
4
5
6
7
8
9
10
onEditComplete(event: TableEditCompleteEvent) {
  if (this.formGroup.dirty && event.index) {
    this.products = [
      ...this.products.slice(0, event.index),
      this.formGroup.value,
      ...this.products.slice(event.index + 1),
    ];
    this.formGroup.markAsPristine();
  }
}

Initialize PrimeNG Table

The table is initialized in the HTML as follows:

1
2
3
4
5
6
7
8
9
10
<p-table
  [value]="products"
  dataKey="id"
  [tableStyle]="{ 'min-width': '50rem' }"
  selectionMode="single"
  [columns]="cols"
  (onEditInit)="onEditInit($event)"
  (onEditComplete)="onEditComplete($event)"
  >
</p-table>

Initialize the Table Colgroup

To control the column widths:

1
2
3
4
5
6
7
8
9
<ng-template pTemplate="colgroup" let-columns>
  <colgroup>
    <col
      *ngFor="let col of columns"
      span="1"
      [style.width]="col.width ? col.width : 'auto'"
    />
  </colgroup>
</ng-template>

Initialize the Table Header

The header is rendered as follows:

1
2
3
4
5
6
7
8
9
10
11
<ng-template pTemplate="header" let-columns>
  <tr>
    <th
      *ngFor="let col of columns"
      [class.text-center]="col.align === 'center'"
      [class.text-right]="col.align === 'right'"
    >
      {{ col.header }}
    </th>
  </tr>
</ng-template>

Initialize the Table Body

For rendering the table body dynamically and handling inline editing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<ng-template
  pTemplate="body"
  let-rowData
  let-columns="columns"
  let-i="rowIndex"
>
  <tr>
    <td
      *ngFor="let col of columns"
      [class.text-center]="col.align === 'center'"
      [class.text-right]="col.align === 'right'"
      [pEditableColumn]="rowData[col.field]"
      [pEditableColumnField]="col.field"
      [pEditableColumnRowIndex]="i"
      [pEditableColumnDisabled]="!col.editable"
    >
      <p-cellEditor>
        <ng-template pTemplate="input">
          @switch (col.cellType) {
            @case (cellType.InputNumber) {
              <form [formGroup]="formGroup">
                <p-inputNumber [formControlName]="col.field" inputStyleClass="w-full text-right" />
              </form>
            }
            @default {
              <form [formGroup]="formGroup">
                <input [formControlName]="col.field" pInputText class="w-full" />
              </form>
            }
          }
        </ng-template>
        <ng-template pTemplate="output">
          @switch (col.cellType) {
            @case (cellType.Dropdown) {
              <p-dropdown 
                [options]="categories" 
                optionLabel="label"
                optionValue="label"
                appendTo="body"
                [ngModel]="rowData[col.field]"
                placeholder="Select a category" />
            }
            @case (cellType.Checkbox) {
              <p-checkbox [binary]="true"  [ngModel]="rowData[col.field]"/>
            }
            @case (cellType.Icon) {
              <i
                class="{{ col.iconClass }}"
                [ngStyle]="{ cursor: 'pointer' }"
                (click)="
                  col.iconOnClick &&
                    col.iconOnClick(rowData[col.field], i, $event)
                "
              ></i>
            }
            @default  {
              {{ rowData[col.field] }}
            }
          }
        </ng-template>
      </p-cellEditor>
    </td>
  </tr>
</ng-template>

The complete source code can be found on Stackblitz.

Summary

You may have noticed that the left and right arrow keys trigger a focus event on the table, which should ideally be disabled while editing a cell. To achieve this, you can create a custom directive and apply it to each cell to override this behavior.

Additionally, you can enhance the grid by adding a feature that selects the cell’s data automatically when it receives focus, making editing more convenient.

Just drop me a message if you have any question.

Thanks for reading!


Leave a comment