• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Building a Reusable Table Component in Angular

Lomanu4 Оффлайн

Lomanu4

Команда форума
Администратор
Регистрация
1 Мар 2015
Сообщения
1,481
Баллы
155
In this article, we'll create a flexible and reusable table component in Angular that supports dynamic columns, data binding, and custom actions. Table will support sorting, filtering and can be extended to support other features. Let's break it down step by step.

What will we do in this article?

  • Basic Table Structure without Actions
  • Extend component to support actions
  • Enable component to enable/disable actions based on table data row
  • Extend component to support sorting
Prerequisites


Before we begin, ensure you have an Angular project set up. If not, follow these commands to create a new Angular project:


npm install -g @angular/cli
ng new my-app
cd my-app
ng serve --open

Once your Angular application is set up, you’re ready to proceed.

Step 1. Basic Table Structure without Actions


Let's start with creating a basic table structure:

1. First, define the interfaces


export interface IColumnDef<T> {
headerText: string; // Column header text
field?: keyof T; // Property name from data object
}

export interface IUser { // Sample data interface
name: string;
email: string;
role: string;
status: string;
}

2. Create column definitions in constants file


import { IColumnDef, IUser } from '../models/general.models';

export const COLUMN_DEFINITIONS: IColumnDef<IUser>[] = [
{ headerText: 'Name', field: 'name' },
{ headerText: 'Email', field: 'email' },
{ headerText: 'Role', field: 'role' }
];

As we have defined field property in IColumnDef as keyOf T, it will give an error while defining column definitions in case field property is not from data object, in this case it's IUser.

Step 2: Create and use the Table Component


Generate a new table component:

ng generate component components/table

Define the Component Class (table.component.ts):


import { Component, input, output } from '@angular/core';

@Component({
selector: 'app-table',
standalone: true,
imports: [],
templateUrl: './table.component.html',
styleUrl: './table.component.scss'
})
export class TableComponent<T> {
// Input signals for columns and data
columns = input<IColumnDef<T>[]>([]);
tableData = input<T[]>([]);

}

Define basic table component HTML


<table class="table">
<thead>
<tr>
@for(col of columns(); track col){
<th scope="col">{{col.headerText}}</th>
}
</tr>
</thead>
<tbody>
@if(tableData().length >= 0){
@for(data of tableData(); track data){
<tr>
@for(col of columns(); track col){
@if(col.field){
<td>{{data[col.field]}}</td>
}
}
</tr>
}
}
</tbody>
</table>

Use the table component, Ex. using in app.component.ts

Add app-table to HTML


<app-table [columns]="columns" [tableData]="tableData"></app-table>

Define columns and Data in .ts file


columns= COLUMN_DEFINITIONS;
tableData:IUser[] = [
{name: 'Kapil', email:'kapilkumar0037@gmail.com', role:'admin', status:'active'},
{name: 'Kapil1', email:'kapilkumar0038@gmail.com', role:'admin', status:'active'},

];

That's it. basic table is ready with 3 columns.

Now display status column as well in the table, to do that we just need to add one more column in column definitions and


import { IColumnDef, IUser } from '../models/general.models';

export const COLUMN_DEFINITIONS: IColumnDef<IUser>[] = [
{ headerText: 'Name', field: 'name' },
{ headerText: 'Email', field: 'email' },
{ headerText: 'Role', field: 'role' },
{ headerText: 'Status', field: 'status' }
];

As status is already there in data, it will be displayed in the table.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Next Step: Add actions to the table


1. Update IColumnDef interface


export interface IColumnDef<T> {
headerText: string;
field?: keyof T;
columnType?: string;
actions?: IActions<T>[];
}

export interface IActions<T> {
label: string;
icon: string;
tooltip: string;
}

2. Update COLUMNN Definitions constants


import { IColumnDef, IUser } from "../models/general.models";

export const COLUMN_DEFINITIONS: IColumnDef<IUser>[] = [
{
headerText: "Name",
field: "name",
sortable: true
},
{
headerText: "Email",
field: "email"
},
{
headerText: "Role",
field: "role"
},
{
headerText: "Status",
field: "status"
},
{
headerText: "Action",
columnType: "action",
actions: [
{
label: "Edit",
icon: "pi pi-pencil",
tooltip: "Edit",
},
{
label: "Delete",
icon: "pi pi-trash",
tooltip: "Delete",
}
]
},
]

3. Update table component .ts file to handle action click


actionClickEmit = output<{action: IActions<T>, rowData: T}>();

actionClick(action: IActions<T>, rowData: T) {
this.actionClickEmit.emit({action, rowData});
}

4. Update table component .html file to display actions


<table class="table">
<thead>
<tr>
@for(col of columns(); track col){
<th scope="col">{{col.headerText}}</th>
}
</tr>
</thead>
<tbody>
@if(tableData().length >= 0){
@for(data of tableData(); track data){
<tr>
@for(col of columns(); track col){
@if(col.field){
<td>{{data[col.field]}}</td>
} @else if(col.columnType == 'action'){
<td>
@for(action of col.actions; track action){
<button type="button" class="btn btn-primary me-2"
(click)="actionClick(action, data)">{{action.label}}</button>
}
</td>
}
}
</tr>
}
}


</tbody>
</table>

That's it, Edit and Delete actions added to the table, and it will emit the output events on click


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Enable/Disable actions based on table data


Ex. The Requirement is to disable Edit if status is not active and disable the delete button if role is admin

  1. Edit the IActions interface to have a disabled functions

export interface IActions<T> {
label: string;
icon: string;
tooltip: string;
disabled?: (data: T) => boolean;
}

2. Add the button disabling logic to Column definitions in the constants file
Updated actions in column definitions
disabled: (data: IUser) => data.status=== "inactive"
disabled: (data: IUser) => data.role === "admin"



export const COLUMN_DEFINITIONS: IColumnDef<IUser>[] = [
{
headerText: "Name",
field: "name",
sortable: true
},
{
headerText: "Email",
field: "email"
},
{
headerText: "Role",
field: "role"
},
{
headerText: "Status",
field: "status"
},
{
headerText: "Action",
columnType: "action",
actions: [
{
label: "Edit",
icon: "pi pi-pencil",
tooltip: "Edit",
disabled: (data: IUser) => data.status=== "inactive"
},
{
label: "Delete",
icon: "pi pi-trash",
tooltip: "Delete",
disabled: (data: IUser) => data.role === "admin"
}
]
},
]

3. Update the table component HTML to call the disabled function
disable login added to button
[disabled]="action?.disabled(data)"


<table class="table">
<thead>
<tr>
@for(col of columns(); track col){
<th scope="col">{{col.headerText}}</th>
}
</tr>
</thead>
<tbody>
@if(tableData().length >= 0){
@for(data of tableData(); track data){
<tr>
@for(col of columns(); track col){
@if(col.field){
<td>{{data[col.field]}}</td>
} @else if(col.columnType == 'action'){
<td>
@for(action of col.actions; track action){
<button [disabled]="action?.disabled(data)" type="button" class="btn btn-primary me-2"
(click)="actionClick(action, data)">{{action.label}}</button>
}
</td>
}
}
</tr>
}
}
</tbody>
</table>
  1. Update user data in app component to have one inactive record

tableData:IUser[] = [
{name: 'Kapil', email:'kapilkumar0037@gmail.com', role:'user', status:'inactive'},
{name: 'Kapil1', email:'kapilkumar0038@gmail.com', role:'admin', status:'active'},

];


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Sorting the table


Now it's time to add the sorting logic, table component should allow sorting for one or more columns depends on configuration

1. Add sortable properties to our IColumnDef interface


export interface IColumnDef<T> {
headerText: string;
field?: keyof T;
columnType?: string;
actions?: IActions<T>[];
sortable?: boolean;
sortDirection?: 'asc' | 'desc' | '';
}

2. Update column definitions and define sortable columns


export const COLUMN_DEFINITIONS: IColumnDef<IUser>[] = [
{
headerText: "Name",
field: "name",
sortable: true,
sortDirection: "asc"
},
{
headerText: "Email",
field: "email"
},
{
headerText: "Role",
field: "role",
sortable: true,
sortDirection: ""
},
{
headerText: "Status",
field: "status"
},
{
headerText: "Action",
columnType: "action",
actions: [
{
label: "Edit",
icon: "pi pi-pencil",
tooltip: "Edit",
disabled: (data: IUser) => data.status=== "inactive"
},
{
label: "Delete",
icon: "pi pi-trash",
tooltip: "Delete",
disabled: (data: IUser) => data.role === "admin"
}
]
},
]

*3. Add sorting logic to table component *
As we using signal input, we can not update the same input after sorting. So adding a new signal to keep sorted data
Our component with sorting will be having below code


import { Component, computed, effect, input, output, signal } from '@angular/core';
import { IActions, IColumnDef } from '../../models/general.models';

@Component({
selector: 'app-table',
standalone: true,
imports: [],
templateUrl: './table.component.html',
styleUrl: './table.component.scss'
})
export class TableComponent<T> {
columns = input<IColumnDef<T>[]>([]);
tableData = input<T[]>([]);
actionClickEmit = output<{action: IActions<T>, rowData: T}>();

actionClick(action: IActions<T>, rowData: T) {
this.actionClickEmit.emit({action, rowData});
}

// Internal signals
sortedData = signal<T[]>([]);

constructor() {
// Initialize sortedData when tableData changes
effect(() => {
this.sortedData.set(this.tableData());
}, { allowSignalWrites: true });
}

sort(column: IColumnDef<T>) {
if (!column.sortable || !column.field) return;

// Reset other columns
this.columns().forEach(col => {
if (col !== column && col.sortDirection !== undefined) col.sortDirection = '';
});

// Toggle sort direction
column.sortDirection = column.sortDirection === 'asc' ? 'desc' : 'asc';

// Sort data
const sorted = [...this.sortedData()].sort((a, b) => {
const aVal = a[column.field!];
const bVal = b[column.field!];
return column.sortDirection === 'asc' ?
aVal > bVal ? 1 : -1 :
aVal < bVal ? 1 : -1;
});

this.sortedData.set(sorted);
}
}
  1. Update table component .html file to handle sorting

<table class="table">
<thead>
<tr>
@for(col of columns(); track col){
<th scope="col" (click)="sort(col)">
{{col.headerText}}
@if(col.sortDirection === 'asc'){↑}
@if(col.sortDirection === 'desc'){↓}
@if(col.sortDirection === ''){↓↑}
</th>
}
</tr>
</thead>
<tbody>
@if(sortedData().length >= 0){
@for(data of sortedData(); track data){
<tr>
@for(col of columns(); track col){
@if(col.field){
<td>{{data[col.field]}}</td>
} @else if(col.columnType == 'action'){
<td>
@for(action of col.actions; track action){
<button [disabled]="action?.disabled(data)" type="button" class="btn btn-primary me-2"
(click)="actionClick(action, data)">{{action.label}}</button>
}
</td>
}
}
</tr>
}
}
</tbody>
</table>

That's it, sorting will work for all the columns now. currently in column definitions it is defined for Name and Role. if we need it for status column as well need to update column definition for status column as below and it will work.


{
headerText: "Status",
field: "status",
sortable: true,
sortDirection: ""
},


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Key Features

  • Generic Typing: The table component uses generics () to ensure type safety for different data structures
  • Dynamic Columns: Columns are configured through the columns input property
  • Action Buttons: Supports custom action buttons with disable conditions
  • Signal-based Inputs/Outputs: Uses Angular's new signals for reactive data handling
  • Sorting based on configuration
  • Flexible Structure: Easy to extend with additional features like sorting, filtering, etc.
Github for complete code



Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Future Enhancements

  • Implement filtering
  • Add pagination
  • Support for custom cell templates
  • Add loading state
  • Responsive design
  • Custom styling options
Conclusion


This reusable table component provides a solid foundation for displaying tabular data in Angular applications. It's type-safe, flexible, and can be easily extended with additional features as needed.

Remember to add appropriate styling and consider adding features like filtering, and pagination based on your specific needs.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу