-1

I am working on an Angular project using ng-bootstrap Sortable Table component, this one: https://ng-bootstrap.github.io/#/components/table/examples#sortable

My problem is that I need that the table is automatically sorted by a specific default column.

In particular in my HTML I have something like this:

<div class="table-responsive">
    <table class="table table-striped">
    <thead>
        <tr>
            <th scope="col" sortable="UID" (sort)="onSort($event)">ID</th>
            <th scope="col" sortable="date" (sort)="onSort($event)">Data</th>
            <th scope="col" sortable="buyerName" (sort)="onSort($event)">Cliente</th>
            <th scope="col" sortable="distribuzione" (sort)="onSort($event)">Azienda
            </th>
            <th scope="col" sortable="status" (sort)="onSort($event)">Stato</th>
            <th scope="col" sortable="hasInvoice" (sort)="onSort($event)">Fattura</th>
            <th scope="col" sortable="total" (sort)="onSort($event)">Totale</th>
            <th scope="col">Azioni</th>
        </tr>
    </thead>
    <tbody>
        <ng-container *ngIf="listOrders$ | async as listOrders;else loading">
            <pre class="py-4" *ngIf="listOrders.length === 0">Nessun Risultato</pre>

            <ng-container
                *ngFor="let order of listOrders | slice: (page-1) * pageSize : page * pageSize;index as i">
                <tr class="fade-in">
                    <td>
                        <ngb-highlight result="{{order.UID}}" [term]="filter.value">
                        </ngb-highlight>
                    </td>
                    
                    ..............................................................
                    ..............................................................
                    ..............................................................

It works fine but I have to implemented the following behavior: the table have to be automatically sorted by date column values, this field:

<th scope="col" sortable="date" (sort)="onSort($event)">Data</th>

On the official documentation I can't find information on how set a specific default sorting field (descending way) but it seems strange to me (it should be a pretty standard behavior).

Is it possible specify a default sorting column in some way?

I tried to do as explained here: Bootstrap table default sorting

<table class="table table-striped" data-sort-name="date" data-sort-order="desc">

but I am obtaining no result.

EDIT-1: As suggested I also tried to call the onSort() method into the ngOnInit() method to order my table at initialization time but it is not working. Following the entire code of my component TypeScript code:

import { Router } from '@angular/router';
import { OrderDetailsComponent } from './order-details/order-details.component';
import { User } from './../../../shared/interfaces/user';
import { OrderService } from 'src/app/services/order/order.service';
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
import { FormControl } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import {
  takeUntil,
  tap,
  startWith,
  debounceTime,
  map,
  take,
  debounce,
} from 'rxjs/operators';
import { AuthService } from 'src/app/services/auth/auth.service';
import { Order } from 'src/app/shared/interfaces/order';
import {
  compare,
  NgbSortableHeaderDirective,
  SortEvent,
} from '../shared/directives/ngb-sortable-header.directive';

@Component({
  selector: 'app-order-admin',
  templateUrl: './order-admin.component.html',
  styleUrls: ['./order-admin.component.scss'],
})
export class OrderAdminComponent implements OnInit {
  user$: Observable<User>;
  listOrders$: BehaviorSubject<Order[]> = new BehaviorSubject(null);
  page = 1;
  pageSize = 20;
  collectionSize = 2;
  filter = new FormControl('');
  destroy$ = new Subject();
  @ViewChildren(NgbSortableHeaderDirective)
  headers: QueryList<NgbSortableHeaderDirective>;

  constructor(
    private authService: AuthService,
    private orderService: OrderService,
    private modalService: NgbModal,
    private router: Router
  ) {}

  async ngOnInit(): Promise<void> {
    if (await this.authService.isLoggedIn()) {
      this.user$ = await this.authService.getCurrentUserData();
      this.user$.subscribe(
        (attributes) => {
          console.log('user got a next value: ', attributes);
          console.log('attributes.profileType', attributes.profileType);
          this.authService.redirectIfWrongUser(attributes.profileType);
        },
        (err) => console.error('user get error ', err)
      );
    }
    await this.initOrders();

    this.onSort({ column: "date", direction: "desc" });
  }

  async initOrders(): Promise<void> {
    let OrderList: Order[];

    this.orderService
      .getOrders()
      .pipe(
        take(1),
        map((orders) => {
          return orders.map((order) => {
            return {
              UID: order.UID,
              date: this.getDate(order.date),
              buyerName: order.user.name,
              status: order.status,
              company: order.user.companyName,
              address: order.user.billing_address,
              quantity: this.calculateTotalQuantity(order.items),
              buyerID: order.user.UID,
              hasInvoice: order.hasInvoice ? 'Yes' : 'No',
              total: order.total,
              distribuzione: order.items[0].wine.rootCellar,
              // originalVar: order
            };
          });
        }),
        tap(console.log),
        tap((Orders: Order[]) => (this.collectionSize = Orders.length)),
        tap((c: Order[]) => this.listOrders$.next(c))
      )
      .subscribe((Orders: Order[]) => (OrderList = Orders));

    this.filter.valueChanges
      .pipe(
        startWith(''),
        debounceTime(300),
        takeUntil(this.destroy$),
        map((t) => (typeof t === 'number' ? t.toString() : t)),
        tap((text) =>
          !!OrderList
            ? this.listOrders$.next(this.search(text, OrderList))
            : null
        )
      )
      .subscribe();
  }

  calculateTotalQuantity(items: Order['items']): string {
    return items
      .map((item) => item.quantityCart)
      .reduce((accumulator, currentValue) => accumulator + currentValue)
      .toString();
  }

  getDate(d) {
    return d ? d.toDate().toISOString() : '';
  }

  search(text: string, OrderList): Order[] {
    const searchResult = OrderList.filter((c) => {
      const term = text.toLowerCase();

      return (
        c.company?.toLowerCase().includes(term) ||
        c.status?.toString().toLowerCase().includes(term) ||
        c.buyerName?.toString().toLowerCase().includes(term) ||
        c.address?.toString().toLowerCase().includes(term) ||
        c.quantity?.toString().toLowerCase().includes(term) ||
        c.date?.toString().toLowerCase().includes(term) ||
        c.total?.toString().toLowerCase().includes(term) ||
        c.distribuzione?.toString().toLowerCase().includes(term) ||
        c.hasInvoice?.toString().toLowerCase().includes(term)
      );
    });

    this.collectionSize = searchResult.length;

    return searchResult;
  }

  onSort({ column, direction }: SortEvent): void {
    this.headers.forEach((header) => {
      if (header.sortable !== column) {
        header.direction = '';
      }
    });

    if (direction === '' || column === '') {
      return;
    } else {
      const OrdersList = this.listOrders$.value;
      const sortedList = [...OrdersList].sort((a, b) => {
        const res = compare(a[column], b[column]);
        return direction === 'asc' ? res : -res;
      });
      this.listOrders$.next(sortedList);
    }
  }

  openDetailsModal(order): void {
    const modalRef = this.modalService.open(OrderDetailsComponent, {
      backdrop: 'static',
      keyboard: false,
    });

    modalRef.componentInstance.orderUID = order.UID;
    modalRef.componentInstance.buyerID = order.buyerID;
    modalRef.componentInstance.status = order.status;

    modalRef.closed
      .pipe(
        take(1),
        tap((changed) => {
          if (!changed) {
            return;
          }
          this.listOrders$.next(null);
          setTimeout(() => this.initOrders(), 400);
        })
      )
      .subscribe();
  }
}

As you can see, at the end of ngOnInit() method I put:

 this.onSort({ column: "date", direction: "desc" });

The problem is that now I am obtaining the following error into the console when I run the page:

ERROR Error: Uncaught (in promise): TypeError: OrdersList is not iterable
TypeError: OrdersList is not iterable
    at OrderAdminComponent.onSort (order-admin.component.ts:156)
    at OrderAdminComponent.<anonymous> (order-admin.component.ts:63)
    at Generator.next (<anonymous>)
    at fulfilled (tslib.es6.js:71)
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Object.onInvoke (core.js:28535)
    at ZoneDelegate.invoke (zone-evergreen.js:363)
    at Zone.run (zone-evergreen.js:123)
    at zone-evergreen.js:857
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at resolvePromise (zone-evergreen.js:798)
    at zone-evergreen.js:705
    at fulfilled (tslib.es6.js:71)
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Object.onInvoke (core.js:28535)
    at ZoneDelegate.invoke (zone-evergreen.js:363)
    at Zone.run (zone-evergreen.js:123)
    at zone-evergreen.js:857
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Object.onInvokeTask (core.js:28522)

Why? What is wrong? What am I missing? How can I try to fix it?

2 Answers2

0

There are 2 solutions I would like to recommend, might be helpful to you!

  1. Get the data sorted by date from the server itself so that when you render the data in the front end, it's already sorted according to the desired column.

  2. In the lifecycle method ngOnInit(), you can call the onSort() function as per your required column, it will sort the data as soon as the component renders on the DOM tree.

  • I tried to put the onSort() call into the ngOnInit() method but I obtain an error. I put the description of what I have done and on the occurred error at the end of my original post – Andrea Nobili Jun 24 '21 at 14:38
  • @AndreaNobili, a ViewChildren it's only accessible in ngAfterViewInit – Eliseo Jun 24 '21 at 15:23
0

it's only in ngAfterViewInit

  ngAfterViewInit() {
    setTimeout(() => {

      //if futhermore we can show the "arrow"
      const header = this.headers.find(x => x.sortable == 'name');
      header.direction = 'asc';

      //simply call the function onSort
      this.onSort({ column: 'name', direction: 'asc' });
    });
  }

NOTE: I enclosed all in a setTimeout to not received the message "ExpressionChangedAfterItHasBeenCheckedError"

NOTE2: If enclosed in a setTimeout, you can use also ngOnInit

Eliseo
  • 50,109
  • 4
  • 29
  • 67