- [Overview](#overview) - [Basic Inputs](#basic-inputs) - [Display & Layout Inputs](#display--layout-inputs) - [Data Management Inputs](#data-management-inputs) - [Row Selection Inputs](#row-selection-inputs) - [Row Editing Inputs](#row-editing-input
npm install ngx-st-tablesngx-st-material-table component is a powerful Angular Material table with features including:
string
''
typescript
tableTitle="Users List"
`
$3
- Type: number
- Default: 10
- Description: Number of rows to display per page.
- Example:
`typescript
[pageSize]="25"
`
$3
- Type: number
- Default: 0
- Description: Total number of records (used for lazy loading pagination). Only needed when lazyLoading is true.
- Example:
`typescript
[dataLength]="totalRecords"
`
$3
- Type: any[]
- Default: []
- Description: Array of data objects to display in the table. Each object represents one row.
- Important: Uses immutable data pattern. Always pass a new array reference when updating data.
- Example:
`typescript
[data]="users"
`
$3
- Type: StMaterialTableColumnModel[]
- Default: []
- Description: Array of column definitions that configure how each column is displayed and behaves.
- See: Column Configuration section for details.
- Example:
`typescript
[initColumns]="columnDefs"
`
---
Display & Layout Inputs
$3
- Type: boolean
- Default: false
- Description: Shows a global search input that searches across all visible columns.
- Example:
`typescript
[showGlobalSearch]="true"
`
$3
- Type: boolean
- Default: false
- Description: Allows users to show/hide columns via a column picker dialog.
- Example:
`typescript
[allowPickColumns]="true"
`
$3
- Type: boolean
- Default: false
- Description: Allows users to reorder columns by dragging them.
- Example:
`typescript
[allowReorderColumns]="true"
`
$3
- Type: string
- Default: ''
- Description: Key name for storing table state (pagination, sorting, filters, column order) in localStorage. If empty, state is not persisted.
- Example:
`typescript
localStorageName="users-table-state"
`
$3
- Type: boolean
- Default: false
- Description: Shows a loading spinner overlay on the table.
- Example:
`typescript
[isLoading]="loading"
`
$3
- Type: TemplateRef
- Default: undefined
- Description: Custom template for additional filter controls displayed in the table caption area.
- Example:
`html
Active
Inactive
[extraCustomFilter]="customFilters"
`
$3
- Type: (row: any) => boolean
- Default: undefined
- Description: Custom filter function that is applied to every row in the table. The function receives a row object and should return true to include the row or false to exclude it. This filter is automatically applied whenever table data changes and works in combination with other filters (global search, column filters).
- Use Cases:
- Filter rows based on complex business logic
- Show only rows matching specific conditions
- Dynamically filter based on external state changes
- Note: Only works with local data (when lazyLoading is false). For server-side filtering, handle filtering in your backend.
- Examples:
`typescript
// Show only active users
[initFilterMethod]="(row) => row.status === 'active'"
// Show users with quantity greater than 10
[initFilterMethod]="(row) => row.quantity > 10"
// Complex filtering with multiple conditions
[initFilterMethod]="filterRows.bind(this)"
filterRows(row: any): boolean {
return row.isActive && row.amount > 0 && row.department === this.selectedDepartment;
}
// Dynamic filtering based on date range
[initFilterMethod]="(row) => {
const rowDate = new Date(row.createdAt);
return rowDate >= this.startDate && rowDate <= this.endDate;
}"
`
---
Data Management Inputs
$3
- Type: boolean
- Default: false
- Description: Enables server-side pagination, sorting, and filtering. When true, table emits loadData events instead of handling data locally.
- Required with: dataLength input must be provided
- Example:
`typescript
[lazyLoading]="true"
[dataLength]="totalRecords"
(loadData)="onLoadData($event)"
`
$3
- Type: boolean
- Default: false
- Description: When true and data array length hasn't changed, updates existing row objects in place without recreating table rows. This preserves focus state during editing and prevents UI flickering.
- Use case: Ideal for real-time updates or auto-save scenarios where you frequently update data.
- Example:
`typescript
[setNewDataWithoutRefresh]="true"
`
---
Row Selection Inputs
$3
- Type: boolean
- Default: false
- Description: Enables row selection with checkboxes. Adds a selection column to the left of the table.
- Example:
`typescript
[allowSelectRow]="true"
(selectRowChange)="onSelectionChange($event)"
`
$3
- Type: string
- Default: ''
- Description: Property name from the row object to use as the display label for selected rows (shown in chips).
- Example:
`typescript
selectionFieldLabel="name"
`
$3
- Type: boolean
- Default: false
- Description: When true, only one row can be selected at a time (radio button behavior instead of checkboxes).
- Example:
`typescript
[selectRowOnlyOne]="true"
`
$3
- Type: (row: any) => string
- Default: undefined
- Description: Custom formatter function to generate the display value for selected rows. Takes precedence over selectionFieldLabel.
- Use case: When you need to display multiple fields or apply custom formatting.
- Example:
`typescript
[selectRowValueDisplay]="formatSelectedRow"
formatSelectedRow(row: any): string {
return ${row.firstName} ${row.lastName} (${row.email});
}
`
$3
- Type: any or any[]
- Default: undefined
- Description: Pre-selected row(s) when the table initializes.
- Example:
`typescript
[initSelectedRow]="preSelectedUsers"
`
---
Row Editing Inputs
$3
- Type: boolean
- Default: false
- Description: Enables inline row editing. Adds edit/delete action buttons to each row (unless autoSaveOnChange is true).
- Note: Columns must have allowEditColumn: true to be editable.
- Example:
`typescript
[allowEditRow]="true"
(saveEditedRow)="onRowSaved($event)"
`
$3
- Type: boolean
- Default: true
- Description: Shows the edit button in the actions column. Set to false to hide it while still allowing editing functionality.
- Example:
`typescript
[allowEditInEditRow]="false"
`
$3
- Type: boolean
- Default: true
- Description: Shows the delete button in the actions column.
- Example:
`typescript
[allowDeleteInEditRow]="false"
`
$3
- Type: boolean
- Default: false
- Description: Shows a button in the table caption that toggles edit mode for all rows simultaneously.
- Example:
`typescript
[showEditAllRows]="true"
`
$3
- Type: boolean
- Default: false
- Description: When true, field changes are saved immediately without requiring save/cancel buttons. Perfect for quantity inputs, toggles, or selection tables where instant updates are needed.
- Behavior:
- Removes save/cancel buttons from the table
- Emits fieldValueChanged event on every field change
- Parent should update their data source immediately
- Example:
`typescript
[autoSaveOnChange]="true"
(fieldValueChanged)="onFieldChanged($event)"
onFieldChanged(event: { row: any; field: string; value: any; index: number }) {
// Update your data source
this.users[event.index][event.field] = event.value;
// Optionally call API to persist
this.updateUser(event.row);
}
`
$3
- Type: (row: any) => boolean
- Default: () => true
- Description: Function to determine if a specific row can be edited. Return false to disable editing for that row.
- Example:
`typescript
[canEditRowValidator]="canEditUser"
canEditUser(row: any): boolean {
return row.status !== 'archived' && row.isEditable;
}
`
$3
- Type: (row: any) => boolean
- Default: () => true
- Description: Function to determine if a specific row can be deleted. Return false to disable delete button for that row.
- Example:
`typescript
[canDeleteRowValidator]="canDeleteUser"
canDeleteUser(row: any): boolean {
return row.status !== 'system' && !row.hasRelatedRecords;
}
`
---
Row Actions Inputs
$3
- Type: (row: any) => void
- Default: undefined
- Description: Function called when a row is clicked (only if allowSelectRow and allowExtendRow are false).
- Example:
`typescript
[rowClickAction]="onRowClick"
onRowClick(row: any): void {
this.router.navigate(['/users', row.id]);
}
`
$3
- Type: boolean
- Default: false
- Description: Shows an "Add New Row" button below the table that adds a new editable row.
- Example:
`typescript
[allowCreateRow]="true"
(saveCreatedRow)="onRowCreated($event)"
`
$3
- Type: boolean
- Default: false
- Description: Shows a custom create button in the table caption (separate from allowCreateRow).
- Example:
`typescript
[showCreateButton]="true"
[createButtonLabel]="'Add User'"
[createButtonAction]="openCreateDialog"
`
$3
- Type: string
- Default: 'Create'
- Description: Label text for the create button (when showCreateButton is true).
- Example:
`typescript
createButtonLabel="Add New User"
`
$3
- Type: () => void
- Default: () => {}
- Description: Function called when the create button is clicked.
- Example:
`typescript
[createButtonAction]="openCreateDialog"
openCreateDialog(): void {
// Open a dialog or navigate to create page
}
`
$3
- Type: boolean
- Default: false
- Description: Disables the create button.
- Example:
`typescript
[disableCreateButton]="!hasPermission"
`
---
Row Expansion Inputs
$3
- Type: boolean
- Default: false
- Description: Enables expandable rows with a toggle button. Clicking a row expands/collapses its detail view.
- Required with: extendedRowTemplate must be provided.
- Example:
`typescript
[allowExtendRow]="true"
[extendedRowTemplate]="detailTemplate"
`
$3
- Type: TemplateRef<{ data: any }>
- Default: undefined
- Description: Template for the expanded row content. Receives the row data via context.
- Example:
`html
User Details
Email: {{ data.email }}
Phone: {{ data.phone }}
Address: {{ data.address }}
[extendedRowTemplate]="detailTemplate"
`
---
Column Configuration
Columns are configured via the initColumns input using the StMaterialTableColumnModel interface.
$3
#### Basic Properties
##### field
- Type: string
- Required: Yes
- Description: Property name from the data object to display in this column.
- Example: field: 'firstName'
##### header
- Type: string
- Required: Yes
- Description: Column header text displayed in the table header.
- Example: header: 'First Name'
##### type
- Type: StMaterialColumnType
- Options: 'string' | 'number' | 'boolean' | 'date' | 'custom-template' | 'actions'
- Default: 'string'
- Description: Data type of the column, affects display formatting and default filter/edit types.
- Example: type: 'date'
##### width
- Type: string
- Default: Auto
- Description: CSS width value for the column.
- Example: width: '150px' or width: '20%'
##### flexRight
- Type: boolean
- Default: false
- Description: Aligns column content to the right.
- Example: flexRight: true
#### Sorting & Filtering
##### sort
- Type: boolean
- Default: true
- Description: Enables sorting for this column.
- Example: sort: false
##### filter
- Type: boolean
- Default: true
- Description: Enables filtering for this column.
- Example: filter: true
##### filterType
- Type: StMaterialColumnFilterType
- Options: 'string' | 'number' | 'boolean' | 'date' | 'custom'
- Default: Auto-detected from type
- Description: Type of filter input to show for this column.
- Example: filterType: 'date'
##### customFilterOptions
- Type: { value: string; label: string }[]
- Default: undefined
- Description: Options for a dropdown filter (when filterType is 'custom').
- Example:
`typescript
customFilterOptions: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' }
]
`
#### Display Customization
##### customTemplate
- Type: TemplateRef
- Default: undefined
- Description: Custom template for rendering cell content (use with type: 'custom-template').
- Example:
`html
{{ row.status }}
{
field: 'status',
header: 'Status',
type: 'custom-template',
customTemplate: statusTemplate
}
`
##### customValueDisplay
- Type: (row: any) => string
- Default: undefined
- Description: Function to transform the cell value for display.
- Example:
`typescript
{
field: 'price',
header: 'Price',
customValueDisplay: (row) => $${row.price.toFixed(2)}
}
`
##### translateValue
- Type: { [value: string]: string }
- Default: undefined
- Description: Map of values to translated/display values.
- Example:
`typescript
{
field: 'status',
header: 'Status',
translateValue: {
'A': 'Active',
'I': 'Inactive',
'P': 'Pending'
}
}
`
#### Row Editing Configuration
##### allowEditColumn
- Type: boolean
- Default: false
- Description: Makes this column editable when row is in edit mode.
- Example: allowEditColumn: true
##### rowEditType
- Type: StMaterialRowEditType
- Options: 'string' | 'number' | 'boolean' | 'date' | 'custom' | 'custom-dynamic-select' | 'number-qty-input'
- Default: Auto-detected from type
- Description: Type of input to show when editing this column.
- 'string': Text input field
- 'number': Standard HTML number input
- 'boolean': Toggle/checkbox control
- 'date': Date picker
- 'custom': Dropdown with static options
- 'custom-dynamic-select': Dropdown with row-dependent options
- 'number-qty-input': Quantity input component with increment/decrement buttons
- Example: rowEditType: 'number'
- Qty Input Example:
`typescript
{
field: 'quantity',
header: 'Quantity',
type: 'number',
allowEditColumn: true,
rowEditType: 'number-qty-input',
customEditRowValidator: (oldValue, newValue, row) => {
if (newValue < 0) {
return { isValid: false, errorMessage: 'Quantity cannot be negative' };
}
return { isValid: true, errorMessage: '' };
}
}
`
##### customRowEditOptions
- Type: { value: any; label: string }[]
- Default: undefined
- Description: Static options for a dropdown editor (when rowEditType is 'custom').
- Example:
`typescript
customRowEditOptions: [
{ value: 'admin', label: 'Administrator' },
{ value: 'user', label: 'Regular User' },
{ value: 'guest', label: 'Guest' }
]
`
##### dynamicRowEditOptions
- Type: (row: any) => { value: any; label: string }[]
- Default: undefined
- Description: Dynamic function to generate dropdown options based on the current row (when rowEditType is 'custom-dynamic-select').
- Use case: When options depend on other field values in the row.
- Example:
`typescript
dynamicRowEditOptions: (row) => {
if (row.country === 'USA') {
return [
{ value: 'NY', label: 'New York' },
{ value: 'CA', label: 'California' }
];
} else {
return [
{ value: 'LON', label: 'London' },
{ value: 'MAN', label: 'Manchester' }
];
}
}
`
##### editColumnRequired
- Type: boolean
- Default: false
- Description: Makes this column required when editing. Shows error if empty on save.
- Example: editColumnRequired: true
##### disableEdit
- Type: (row: any) => boolean
- Default: undefined
- Description: Function to determine if this specific column should be disabled for editing in a particular row.
- Example:
`typescript
disableEdit: (row) => row.status === 'locked'
`
##### customEditRowValidator
- Type: (oldValue: any, newValue: any, row: any) => { isValid: boolean; errorMessage: string }
- Default: undefined
- Description: Custom validation function for this column during editing.
- Example:
`typescript
customEditRowValidator: (oldValue, newValue, row) => {
if (newValue < 0) {
return { isValid: false, errorMessage: 'Value cannot be negative' };
}
if (newValue > row.maxValue) {
return { isValid: false, errorMessage: 'Value exceeds maximum' };
}
return { isValid: true, errorMessage: '' };
}
`
#### Actions Column Configuration
##### actions
- Type: StMaterialTableActionColumnModel[]
- Default: undefined
- Description: Array of action buttons to show in this column (use with type: 'actions').
- Example:
`typescript
{
field: 'actions',
header: 'Actions',
type: 'actions',
sort: false,
filter: false,
actions: [
{
iconName: 'edit',
tooltipName: 'Edit User',
iconColor: 'primary',
action: (row) => this.editUser(row),
show: (row) => row.canEdit
},
{
iconName: 'delete',
tooltipName: 'Delete User',
iconColor: 'warn',
action: (row) => this.deleteUser(row),
show: (row) => row.canDelete
}
]
}
`
##### actionsInMenu
- Type: boolean
- Default: false
- Description: Shows actions in a dropdown menu instead of as individual buttons.
- Example: actionsInMenu: true
#### Action Button Model
Each action in the actions array has the following properties:
- iconName (string, required): Material icon name
- tooltipName (string, optional): Tooltip text
- iconColor ('primary' | 'warn' | 'accent', optional): Icon color
- action ((row: any, index?: number) => void, optional): Function called when clicked
- show ((row: any) => boolean, optional): Function to conditionally show/hide the action
- url (string[], optional): Router navigation path (alternative to action)
#### Other Properties
##### notShowInColumnPick
- Type: boolean
- Default: false
- Description: Hides this column from the column picker dialog.
- Example: notShowInColumnPick: true
##### selectColumnLabel
- Type: string
- Default: undefined
- Description: Used for special columns like selection, editing, extending. Sets the aria-label.
---
Outputs (Events)
$3
- Type: StMaterialTableLoadData
- When emitted: When lazy loading is enabled and data needs to be loaded (pagination, sorting, filtering changes).
- Payload:
`typescript
{
first: number; // Starting index
rows: number; // Number of rows to load
globalFilter: string; // Global search text
globalFilterColumns: string[]; // Columns to search
sortField: string; // Field to sort by
sortOrder: SortDirection; // 'asc' | 'desc'
filters: { [key: string]: StMaterialTableFilter }; // Column filters
}
`
- Example:
`typescript
(loadData)="onLoadData($event)"
onLoadData(event: StMaterialTableLoadData): void {
this.loading = true;
this.userService.getUsers(event).subscribe(response => {
this.users = response.data;
this.totalRecords = response.total;
this.loading = false;
});
}
`
$3
- Type: { row: any; index: number }
- When emitted: When user saves an edited existing row.
- Payload: The modified row object and its index.
- Example:
`typescript
(saveEditedRow)="onRowEdited($event)"
onRowEdited(event: { row: any; index: number }): void {
this.userService.updateUser(event.row).subscribe(
() => {
// Update your data array
this.users[event.index] = event.row;
this.snackbar.success('User updated successfully');
},
error => {
this.snackbar.error('Failed to update user');
}
);
}
`
$3
- Type: any
- When emitted: When user saves a newly created row (via allowCreateRow).
- Payload: The new row object.
- Example:
`typescript
(saveCreatedRow)="onRowCreated($event)"
onRowCreated(row: any): void {
this.userService.createUser(row).subscribe(
(createdUser) => {
// Add to your data array
this.users = [...this.users, createdUser];
this.snackbar.success('User created successfully');
},
error => {
this.snackbar.error('Failed to create user');
}
);
}
`
$3
- Type: { row: any; index: number }
- When emitted: When user deletes a row.
- Payload: The deleted row object and its index.
- Example:
`typescript
(rowDeleted)="onRowDeleted($event)"
onRowDeleted(event: { row: any; index: number }): void {
this.userService.deleteUser(event.row.id).subscribe(
() => {
// Remove from your data array
this.users = this.users.filter(u => u.id !== event.row.id);
this.snackbar.success('User deleted successfully');
},
error => {
this.snackbar.error('Failed to delete user');
}
);
}
`
$3
- Type: any[]
- When emitted: When row selection changes.
- Payload: Array of selected row objects.
- Example:
`typescript
(selectRowChange)="onSelectionChange($event)"
onSelectionChange(selectedRows: any[]): void {
this.selectedUsers = selectedRows;
console.log(${selectedRows.length} users selected);
}
`
$3
- Type: { row: any; field: string; value: any; index: number }
- When emitted: When autoSaveOnChange is enabled and a field value changes.
- Payload: The row, field name, new value, and row index.
- Important: You must update your data source immediately when receiving this event.
- Example:
`typescript
(fieldValueChanged)="onFieldChanged($event)"
onFieldChanged(event: { row: any; field: string; value: any; index: number }): void {
// Update local data
this.users[event.index][event.field] = event.value;
// Optionally persist to server
this.userService.updateUser(event.row).subscribe();
}
`
---
Usage Examples
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'id', header: 'ID', width: '80px' },
{ field: 'firstName', header: 'First Name', filter: true },
{ field: 'lastName', header: 'Last Name', filter: true },
{ field: 'email', header: 'Email', filter: true },
{ field: 'age', header: 'Age', type: 'number' }
];
users: any[] = [
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com', age: 30 },
{ id: 2, firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com', age: 25 }
];
`
`html
tableTitle="Users"
[pageSize]="10"
[data]="users"
[initColumns]="columns"
[showGlobalSearch]="true"
[allowPickColumns]="true"
localStorageName="users-table">
`
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'name', header: 'Name', filter: true },
{ field: 'status', header: 'Status', filter: true, filterType: 'custom',
customFilterOptions: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' }
]
}
];
users: any[] = [];
totalRecords = 0;
loading = false;
onLoadData(event: StMaterialTableLoadData): void {
this.loading = true;
this.userService.getUsers(event).subscribe(response => {
this.users = response.data;
this.totalRecords = response.total;
this.loading = false;
});
}
`
`html
[lazyLoading]="true"
[isLoading]="loading"
[dataLength]="totalRecords"
[data]="users"
[initColumns]="columns"
(loadData)="onLoadData($event)">
`
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{
field: 'name',
header: 'Name',
allowEditColumn: true,
editColumnRequired: true
},
{
field: 'quantity',
header: 'Quantity',
type: 'number',
allowEditColumn: true,
rowEditType: 'number',
editColumnRequired: true,
customEditRowValidator: (oldValue, newValue, row) => {
if (newValue < 0) {
return { isValid: false, errorMessage: 'Quantity cannot be negative' };
}
if (newValue > 100) {
return { isValid: false, errorMessage: 'Quantity cannot exceed 100' };
}
return { isValid: true, errorMessage: '' };
}
},
{
field: 'status',
header: 'Status',
allowEditColumn: true,
rowEditType: 'custom',
customRowEditOptions: [
{ value: 'draft', label: 'Draft' },
{ value: 'published', label: 'Published' }
]
}
];
onRowEdited(event: { row: any; index: number }): void {
this.updateItem(event.row);
}
canEditItem(row: any): boolean {
return row.status !== 'archived';
}
`
`html
[data]="items"
[initColumns]="columns"
[allowEditRow]="true"
[allowCreateRow]="true"
[canEditRowValidator]="canEditItem"
(saveEditedRow)="onRowEdited($event)"
(saveCreatedRow)="onRowCreated($event)"
(rowDeleted)="onRowDeleted($event)">
`
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'product', header: 'Product' },
{
field: 'quantity',
header: 'Quantity',
type: 'number',
allowEditColumn: true,
rowEditType: 'number'
},
{
field: 'active',
header: 'Active',
type: 'boolean',
allowEditColumn: true
}
];
onFieldChanged(event: { row: any; field: string; value: any; index: number }): void {
// Update local data immediately
this.items[event.index][event.field] = event.value;
// Persist to server
this.itemService.updateItem(event.row).subscribe();
}
`
`html
[data]="items"
[initColumns]="columns"
[allowEditRow]="true"
[autoSaveOnChange]="true"
[setNewDataWithoutRefresh]="true"
(fieldValueChanged)="onFieldChanged($event)">
`
$3
`typescript
// Component - Using the dedicated qty-input component for quantity columns
columns: StMaterialTableColumnModel[] = [
{ field: 'product', header: 'Product', width: '200px' },
{
field: 'quantity',
header: 'Quantity',
type: 'number',
width: '120px',
allowEditColumn: true,
rowEditType: 'number-qty-input', // Uses qty-input component
customEditRowValidator: (oldValue, newValue, row) => {
if (newValue < 0) {
return { isValid: false, errorMessage: 'Quantity cannot be negative' };
}
if (newValue > row.maxStock) {
return { isValid: false, errorMessage: Maximum available: ${row.maxStock} };
}
return { isValid: true, errorMessage: '' };
}
},
{
field: 'price',
header: 'Unit Price',
type: 'number',
customValueDisplay: (row) => $${row.price.toFixed(2)}
},
{
field: 'total',
header: 'Total',
type: 'number',
sort: false,
filter: false,
customValueDisplay: (row) => $${(row.quantity * row.price).toFixed(2)}
}
];
items = [
{ id: 1, product: 'Widget A', quantity: 5, price: 10.00, maxStock: 100 },
{ id: 2, product: 'Widget B', quantity: 3, price: 15.50, maxStock: 50 },
{ id: 3, product: 'Widget C', quantity: 2, price: 25.00, maxStock: 75 }
];
onFieldChanged(event: { row: any; field: string; value: any; index: number }): void {
// Update local data immediately
this.items[event.index][event.field] = event.value;
// Persist to server if needed
this.itemService.updateItem(event.row).subscribe(
(updatedItem) => {
this.items[event.index] = updatedItem;
},
(error) => {
console.error('Failed to update item', error);
// You might want to rollback the change
}
);
}
`
`html
tableTitle="Order Items"
[pageSize]="25"
[data]="items"
[initColumns]="columns"
[allowEditRow]="true"
[autoSaveOnChange]="true"
[setNewDataWithoutRefresh]="true"
[showGlobalSearch]="true"
localStorageName="order-items-table"
(fieldValueChanged)="onFieldChanged($event)">
`
Benefits of using number-qty-input rowEditType:
- Dedicated increment/decrement buttons for easy quantity adjustments
- Cleaner UI compared to standard number inputs (hidden spinner arrows)
- Customizable input width to fit your layout
- Integrated validation support
- Immediate auto-save when used with autoSaveOnChange
- Better user experience for quantity-centric tables
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'id', header: 'ID' },
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' }
];
selectedUsers: any[] = [];
onSelectionChange(selected: any[]): void {
this.selectedUsers = selected;
}
formatSelectedUser(row: any): string {
return ${row.name} (${row.email});
}
`
`html
[data]="users"
[initColumns]="columns"
[allowSelectRow]="true"
selectionFieldLabel="name"
[selectRowValueDisplay]="formatSelectedUser"
(selectRowChange)="onSelectionChange($event)">
`
$3
`html
Additional Details
Address: {{ data.address }}
Phone: {{ data.phone }}
Notes: {{ data.notes }}
[data]="users"
[initColumns]="columns"
[allowExtendRow]="true"
[extendedRowTemplate]="detailTemplate">
`
$3
`html
'badge-success': row.status === 'active',
'badge-danger': row.status === 'inactive',
'badge-warning': row.status === 'pending'
}">
{{ row.status | uppercase }}
`
`typescript
// Component
@ViewChild('statusBadgeTemplate') statusBadgeTemplate!: TemplateRef;
ngAfterViewInit() {
this.columns = [
{ field: 'name', header: 'Name' },
{
field: 'status',
header: 'Status',
type: 'custom-template',
customTemplate: this.statusBadgeTemplate
}
];
}
`
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' },
{
field: 'actions',
header: 'Actions',
type: 'actions',
sort: false,
filter: false,
width: '120px',
actions: [
{
iconName: 'visibility',
tooltipName: 'View Details',
action: (row) => this.viewUser(row)
},
{
iconName: 'edit',
tooltipName: 'Edit User',
iconColor: 'primary',
action: (row) => this.editUser(row),
show: (row) => row.canEdit
},
{
iconName: 'delete',
tooltipName: 'Delete User',
iconColor: 'warn',
action: (row, index) => this.deleteUser(row, index),
show: (row) => row.canDelete
}
]
}
];
viewUser(row: any): void {
this.router.navigate(['/users', row.id]);
}
editUser(row: any): void {
// Open edit dialog
}
deleteUser(row: any, index: number): void {
// Delete logic
}
`
$3
`typescript
// Component
columns: StMaterialTableColumnModel[] = [
{ field: 'country', header: 'Country', allowEditColumn: true },
{
field: 'state',
header: 'State',
allowEditColumn: true,
rowEditType: 'custom-dynamic-select',
dynamicRowEditOptions: (row) => {
if (row.country === 'USA') {
return [
{ value: 'NY', label: 'New York' },
{ value: 'CA', label: 'California' },
{ value: 'TX', label: 'Texas' }
];
} else if (row.country === 'UK') {
return [
{ value: 'ENG', label: 'England' },
{ value: 'SCT', label: 'Scotland' },
{ value: 'WLS', label: 'Wales' }
];
}
return [];
}
}
];
`
---
Best Practices
1. Use localStorageName to persist user preferences (column order, filters, sorting)
2. Enable lazyLoading for large datasets (1000+ records)
3. Use autoSaveOnChange for simple quantity/selection tables where immediate updates are desired
4. Use setNewDataWithoutRefresh when frequently updating data to prevent UI flickering
5. Provide validators (canEditRowValidator, customEditRowValidator) to enforce business rules
6. Use custom templates for complex cell rendering instead of trying to format with customValueDisplay
7. Set appropriate column widths to prevent layout shifts
8. Use notShowInColumnPick for action columns or columns that should always be visible
9. Implement proper error handling in your event handlers (saveEditedRow, rowDeleted, etc.)
10. Use selectRowValueDisplay when simple field labels aren't enough for selected row display
---
Common Patterns
$3
Use allowExtendRow with extendedRowTemplate to show additional details without navigation.
$3
Use autoSaveOnChange with setNewDataWithoutRefresh for spreadsheet-like editing.
$3
Use allowSelectRow with selectRowOnlyOne for choosing a single item (like a lookup).
$3
Combine allowEditRow, allowCreateRow, and action handlers for full CRUD operations.
$3
Use showGlobalSearch, column filters, and localStorageName for a searchable report with saved preferences.
$3
Use initFilterMethod for programmatic filtering based on external state or complex business logic.
`typescript
export class MyComponent {
minAmount = 100;
selectedDepartment = 'Sales';
filterMethod = (row: any): boolean => {
return row.amount >= this.minAmount &&
row.department === this.selectedDepartment;
};
// When minAmount or selectedDepartment changes,
// update the filter method to trigger refiltering
updateFilters() {
this.filterMethod = (row: any): boolean => {
return row.amount >= this.minAmount &&
row.department === this.selectedDepartment;
};
}
}
`
`html
[data]="salesData"
[initColumns]="columns"
[initFilterMethod]="filterMethod">
`
---
Troubleshooting
$3
- Ensure you're passing a new array reference: this.data = [...updatedData]
- Or use setNewDataWithoutRefresh if you want in-place updates
$3
- Enable setNewDataWithoutRefresh for frequent updates
- Ensure data array length doesn't change during edits
$3
- Check that columns have filter: true
- Verify filterType is set correctly
- For custom filters, provide customFilterOptions
$3
- Ensure lazyLoading is true
- Check that loadData event handler is connected
- Verify you're setting dataLength with total record count
$3
- Check allowEditRow is true
- Verify columns have allowEditColumn: true
- Check canEditRowValidator` isn't returning false