Writing a table¶
Copy of
sumaris-app/.agents/rules/table.md(rule written for AI coding agents, when creating or modifying table components (AppTable)). Do not edit here: runnpm run site:sync-rulesto update it.
- A table component must extend
AppTable<T, F>whereTis the entity type andFis the filter type; - Use
changeDetection: ChangeDetectionStrategy.OnPush; - Use the file suffix
.table.<extension>(see common rules);
Providers¶
- In the
@Componentdecorator, declare the following providers:- A
AppValidatorServiceprovider mapped to a concrete validator service for the entity; - A
AppTableprovider usingforwardRef(() => MyTableComponent);providers: [ { provide: AppValidatorService, useClass: MyEntityValidatorService }, { provide: AppTable, useExisting: forwardRef(() => MyTableComponent) } ]
- A
Constructor¶
- The constructor receives an
Injectorand passes tosuper():- The
injector; - The list of columns, wrapped between
RESERVED_START_COLUMNSandRESERVED_END_COLUMNS:super(injector, [ ...RESERVED_START_COLUMNS, 'column1', 'column2', // ... user columns ...RESERVED_END_COLUMNS ], dataSource, null); - An
EntitiesTableDataSource<T>instance, built with the entity class, a data service, and the validator service; - The initial filter (usually
null);
- The
- Configure table options in the constructor:
this.autoLoad = false; // or true, depending on the use case this.inlineEdition = true; // enable inline row editing this.i18nColumnPrefix = 'MY_TABLE.'; this.confirmBeforeDelete = true; this.saveBeforeSort = true; this.saveBeforeFilter = true;
Validator service¶
- Create a dedicated validator service that extends
AppValidatorService(or a subclass likeReferentialValidatorService); - Implement
getFormGroup(data?: T, opts?: any): UntypedFormGroupto define the form controls and their validators; - If the entity has nested properties, add sub-form groups:
getFormGroup(data?: MyEntity, opts?: any): UntypedFormGroup { const form = super.getFormGroup(data, opts); form.addControl('properties', this.getPropertiesForm(data?.properties)); return form; }
Lifecycle¶
- In
ngOnInit():- Call
super.ngOnInit(); - Register filter form value changes with
this.registerSubscription(...); - Register autocomplete fields with
this.registerAutocompleteField(...); - Call
this.markAsReady()at the end;
- Call
- In
ngAfterViewInit():- Call
super.ngAfterViewInit(); - Restore filter from settings if needed;
- Trigger initial data load;
- Call
- In
ngOnDestroy():- Call
super.ngOnDestroy(); - Clean up any custom subscriptions (timers, etc.);
- Call
HTML template¶
- Use a
<table mat-table matSort matSortDisableClear>element bound to[dataSource]="dataSource"; - For each column, define an
<ng-container matColumnDef="columnName">with:- A
<th mat-header-cell>withmat-sort-header(if sortable) and a translated label; - A
<td mat-cell>with a<mat-form-field>bound torow.validator.controls['columnName'];
- A
- Use
[appAutofocus]="row.editing && focusColumn === 'columnName'"and[readonly]="!row.editing"on inputs; - For the selection column, use
matColumnDef="select"with<mat-checkbox>bound toselection; - For the ID column, use
matColumnDef="id"displayingrow.currentData?.id; - For action buttons, use the
<app-actions-column>component with event bindings:<app-actions-column [stickyEnd]="true" (cancelOrDeleteClick)="cancelOrDelete($event.event, $event.row)" (confirmAndAddClick)="confirmAndAdd($event.event, $event.row)" (backward)="confirmAndBackward($event.event, $event.row)" (forward)="confirmAndForward($event.event, $event.row)"> </app-actions-column> - Define the row templates:
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [class.mat-row-selected]="row.editing" [class.mat-row-error]="row.validator?.invalid" [class.mat-row-disabled]="!row.editing" [class.mat-row-dirty]="row.validator?.dirty" (click)="clickRow($event, row)" (keydown.escape)="escapeEditingRow($event)"> </tr> - Add a loading skeleton, a "no result" message, and optionally an
<ion-infinite-scroll>for mobile; - Add a
<mat-paginator>in the footer for desktop pagination;
Module¶
- Declare the table component in a dedicated module importing
CommonModule,SharedModule,CoreModule, and the standaloneTranslatePipe(from@ngx-translate/core) — notTranslateModule.forChild(), which is no longer used in this codebase;
Reference example¶
- See
ngx-sumaris-components/src/app/core/table/testing/for a complete working example:table.testing.ts— component class;table.testing.html— template;table-validator.service.ts— validator;table.testing.module.ts— module declaration;