Top

Tables


# First Name Last Name Username Role Country
1 Alexander Orton @mdorton Admin USA
2 John Deo Deo @johndeo User USA
3 Randy Orton the Bird @twitter admin UK
4 Randy Mark Ottandy @mdothe user AUS
5 Ram Jacob Thornton @twitter admin IND
<div class="table-responsive">
  <table class="table">
    <thead>
      <tr>
        <th scope="col">#</th>
        <th scope="col">First Name</th>
        <th scope="col">Last Name</th>
        <th scope="col">Username</th>
        <th scope="col">Role</th>
        <th scope="col">Country</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">1</th>
        <td>Alexander</td>
        <td>Orton</td>
        <td>@mdorton</td>
        <td>Admin</td>
        <td>USA</td>
      </tr>
      <tr>
        <th scope="row">2</th>
        <td>John Deo</td>
        <td>Deo</td>
        <td>@johndeo</td>
        <td>User</td>
        <td>USA</td>
      </tr>
      <tr>
        <th scope="row">3</th>
        <td>Randy Orton</td>
        <td>the Bird</td>
        <td>@twitter</td>
        <td>admin</td>
        <td>UK</td>
      </tr>
      <tr>
        <th scope="row">4</th>
        <td>Randy Mark</td>
        <td>Ottandy</td>
        <td>@mdothe</td>
        <td>user</td>
        <td>AUS</td>
      </tr>
      <tr>
        <th scope="row">5</th>
        <td>Ram Jacob</td>
        <td>Thornton</td>
        <td>@twitter</td>
        <td>admin</td>
        <td>IND</td>
      </tr>
    </tbody>
  </table>
</div>
Name Position Office Age Start date Salary
Tiger Nixon System Architect Edinburgh 61 2011/04/25 $320,800
Garrett Winters Accountant Tokyo 63 2011/07/25 $170,750
<div class="card-body">
  <div class="table-responsive product-table custom-scrollbar">
    <div class="dataTables_wrapper no-footer">
      <div class="dataTables_length" id="basic-1_length">
        <label>Show
          <select name="pageSize" [(ngModel)]="service.pageSize">
            <option [ngValue]="10">10</option>
            <option [ngValue]="25">25</option>
            <option [ngValue]="50">50</option>
            <option [ngValue]="100">100</option>
          </select> entries
        </label>
      </div>
      <div id="basic-1_filter" class="dataTables_filter">
        <label>Search:
          <input type="search" name="searchTerm" [(ngModel)]="service.searchTerm">
        </label>
        @if (service.loading$ | async) {
        <span class="col col-form-label">Loading...</span>
        }
      </div>
      <table class="display dataTable no-footer" id="basic-1">
        <thead>
          <tr>
            <th appSupportTicket="name" (sort)="onSort($event)">Name</th>
            <th appSupportTicket="position" (sort)="onSort($event)">Position</th>
            <th appSupportTicket="salary" (sort)="onSort($event)">Salary</th>
            <th appSupportTicket="office" (sort)="onSort($event)">Office</th>
            <th appSupportTicket="extn" (sort)="onSort($event)">Extn.</th>
            <th appSupportTicket="email" (sort)="onSort($event)">E-mail</th>
          </tr>
        </thead>
        <tbody>
          @for (ticket of supportTicket$ | async; track ticket){
          <tr>
            <td> {{ ticket.name }}</td>
            <td>{{ ticket.position }}</td>
            <td>${{ ticket.salary }}</td>
            <td>{{ ticket.office}}</td>
            <td>{{ ticket.extn }}</td>
            <td>{{ ticket.email }}</td>
          </tr>
          }
        </tbody>
      </table>
      <div class="dataTables_paginate paging_simple_numbers" id="basic-1_paginate">
        <ngb-pagination [collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
        </ngb-pagination>
      </div>
    </div>
  </div>
  </div>
To use datatable you have to add the following code in .ts File
import { DecimalPipe } from '@angular/common';
import { CommonModule, DecimalPipe } from '@angular/common';
import { Component, inject, viewChildren } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Observable } from 'rxjs';

import { supportTicket } from '../../../../shared/data/data/support-ticket';
import {
  SortEvent,
  SupportTicketDirective,
} from '../../../../shared/directive/support-ticket.directive';
import { SupportTicketService } from '../../../../shared/services/support-ticket.service';

@Component({
  selector: 'app-data-table',
  imports: [CommonModule, NgbModule, FormsModule, SupportTicketDirective],
  templateUrl: './data-table.html',
  styleUrl: './data-table.scss',
  providers: [SupportTicketService, DecimalPipe],
})
export class DataTable {
  service = inject(SupportTicketService);

  supportTicket$: Observable;
  total$: Observable;

  readonly headers = viewChildren(SupportTicketDirective);

  constructor() {
    const service = this.service;

    this.supportTicket$ = service.supportTicket$;
    this.total$ = service.total$;
  }

  onSort({ column, direction }: SortEvent) {
    // resetting other headers
    this.headers().forEach(header => {
      if (header.appSupportTicket() !== column) {
        header.currentDirection.set('');
      }
    });

    this.service.sortColumn = column;
    this.service.sortDirection = direction;
  }
}

To use datatable you have to add the following code is .service File
import { Injectable, PipeTransform } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Injectable, PipeTransform, inject } from '@angular/core';

import {
  BehaviorSubject,
  Observable,
  Subject,
  debounceTime,
  delay,
  of,
  switchMap,
  tap,
} from 'rxjs';

import { supportTicket, supportTicketTable } from '../data/data/support-ticket';
import { SortColumn, SortDirection } from '../directive/support-ticket.directive';

interface SearchResult {
  supportTicket: supportTicket[];
  total: number;
}

interface State {
  page: number;
  pageSize: number;
  searchTerm: string;
  sortColumn: SortColumn;
  sortDirection: SortDirection;
}

const compare = (v1: string | number, v2: string | number) => (v1 < v2 ? -1 : v1 > v2 ? 1 : 0);

function sort(
  supportTicket: supportTicket[],
  column: SortColumn,
  direction: string,
): supportTicket[] {
  if (direction === '' || column === '') {
    return supportTicket;
  } else {
    return [...supportTicket].sort((a, b) => {
      const res = compare(a[column], b[column]);
      return direction === 'asc' ? res : -res;
    });
  }
}

function matches(ticket: supportTicket, term: string, pipe: PipeTransform) {
  return (
    ticket.name.toLowerCase().includes(term.toLowerCase()) ||
    ticket.position.toLowerCase().includes(term.toLowerCase()) ||
    ticket.office.toLowerCase().includes(term.toLowerCase()) ||
    pipe.transform(ticket.salary).includes(term) ||
    pipe.transform(ticket.progress).includes(term) ||
    pipe.transform(ticket.extn).includes(term) ||
    ticket.email.toLowerCase().includes(term.toLowerCase())
  );
}
@Injectable({
  providedIn: 'root',
})
export class SupportTicketService {
  private pipe = inject(DecimalPipe);

  private _loading$ = new BehaviorSubject(true);
  private _search$ = new Subject();
  private _supportTicket$ = new BehaviorSubject([]);
  private _total$ = new BehaviorSubject(0);

  private _state: State = {
    page: 1,
    pageSize: 10,
    searchTerm: '',
    sortColumn: '',
    sortDirection: 'asc',
  };

  constructor() {
    this._search$
      .pipe(
        tap(() => this._loading$.next(true)),
        debounceTime(200),
        switchMap(() => this._search()),
        delay(200),
        tap(() => this._loading$.next(false)),
      )
      .subscribe(result => {
        this._supportTicket$.next(result.supportTicket);
        this._total$.next(result.total);
      });

    this._search$.next();
  }

  get supportTicket$() {
    return this._supportTicket$.asObservable();
  }
  get total$() {
    return this._total$.asObservable();
  }
  get loading$() {
    return this._loading$.asObservable();
  }

  get page() {
    return this._state.page;
  }
  get pageSize() {
    return this._state.pageSize;
  }

  get searchTerm() {
    return this._state.searchTerm;
  }

  get sortColumn() {
    return this._state.sortColumn;
  }

  get sortDirection() {
    return this._state.sortDirection;
  }

  set page(page: number) {
    this._set({ page });
  }
  set pageSize(pageSize: number) {
    this._set({ pageSize });
  }

  set searchTerm(searchTerm: string) {
    this._set({ searchTerm });
  }

  set sortColumn(sortColumn: SortColumn) {
    this._set({ sortColumn });
  }

  set sortDirection(sortDirection: SortDirection) {
    this._set({ sortDirection });
  }

  private _set(patch: Partial) {
    Object.assign(this._state, patch);
    this._search$.next();
  }

  private _search(): Observable {
    const { sortColumn, sortDirection, pageSize, page, searchTerm } = this._state;

    // 1. sort
    let supportTicket = sort(supportTicketTable, sortColumn, sortDirection);

    // 2. filter
    supportTicket = supportTicket.filter(order => matches(order, searchTerm, this.pipe));
    const total = supportTicket.length;

    // 3. paginate
    supportTicket = supportTicket.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);
    return of({ supportTicket, total });
  }
}

To use datatable you have to add the following code in .directive File

import { Directive, HostBinding, HostListener, input, output, signal } from '@angular/core';

import { supportTicket } from '../data/data/support-ticket';

export type SortColumn = keyof supportTicket | '';
export type SortDirection = 'asc' | 'desc' | '';
const rotate: { [key: string]: SortDirection } = { asc: 'desc', desc: '', '': 'asc' };

export interface SortEvent {
  column: SortColumn;
  direction: SortDirection;
}
@Directive({
  selector: '[appSupportTicket]',
  host: {
    '[class.asc]': 'direction === "asc"',
    '[class.desc]': 'direction === "desc"',
    '(click)': 'rotate()',
  },
})
export class SupportTicketDirective {
  readonly appSupportTicket = input('');
  readonly direction = input(''); // Initial direction

  // Local writable signal
  public currentDirection = signal(this.direction());

  // Output event
  readonly sort = output();

  // HostBinding for CSS classes
  @HostBinding('class.asc')
  get isAsc() {
    return this.currentDirection() === 'asc';
  }

  @HostBinding('class.desc')
  get isDesc() {
    return this.currentDirection() === 'desc';
  }

  // Handle click
  @HostListener('click')
  rotate() {
    this.currentDirection.set(rotate[this.currentDirection()]);
    this.sort.emit({ column: this.appSupportTicket(), direction: this.currentDirection() });
  }
}