1

I am trying to sort table data using an example from StackBlitz.

I am not getting any errors in my code or console. However, the table itself is not doing any sorting at all.

HTML

<thead class="bg-info white">
    <tr>
        <th scope="col" sortable="grade.ccode" (sort)="onSort($event)">
            Code
            <i class="la la-sort float-md-end"></i>
        </th>
        <th scope="col" sortable="gradescription" (sort)="onSort($event)">
            Description
            <i class="la la-sort float-md-end"></i>
        </th>
        <th>Delete Grade</th>
    </tr>
</thead>


<tr *ngFor="let grade of paginationService.paginatedData| gradeSort:filter" (dblclick)="openUpdateModal(editGradeModal, grade)" >
    <!-- <tr *ngFor="let grade of paginationService.paginatedData| grade:filter" (dblclick)="openUpdateModal(editGradeModal, grade)" ></tr> -->
    <td>
        <a href='javascript: void(0);'><u><ngb-highlight [result]="grade.code" [term]="paginationService.searchTerm"></ngb-highlight></u></a>
    </td>
    <td>
        <ngb-highlight [result]="grade.description" [term]="paginationService.searchTerm"></ngb-highlight>
    </td>
    <td>
        <button id="btnDeleteGrade" type="button"
            class="btn btn-block btn-primary glow"
            style="width:100px" (click)="onDeleteGrade(grade)">
            Delete
        </button>
    </td>
</tr>

TypeScript

import { SortableHeaderDirective, SortEvent, compare} from 'src/app/core/directives/sortable-header.directive'

export class ListGradesComponent implements OnInit {
    @ViewChildren(SortableHeaderDirective) headers: QueryList<SortableHeaderDirective>;
    onSort({ column, direction }: SortEvent) {
        // resetting other headers
        this.headers.forEach(header => {
            if (header.sortable !== column) {
                header.direction = '';
            }
        });

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

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

Sortable-header-directive.ts

import { Directive, EventEmitter, Input, Output } from '@angular/core';
import { Country } from './data';

export type SortColumn = keyof Country | '';
export type SortDirection = 'asc' | 'desc' | '';

const rotate: { [key: string]: SortDirection } = {
    asc: 'desc',
    desc: '',
    '': 'asc',
};

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

export interface SortEvent {
    column: SortColumn;
    direction: SortDirection;
}

@Directive({
    selector: 'th[sortable]',
    host: {
        '[class.asc]': 'direction === "asc"',
        '[class.desc]': 'direction === "desc"',
        '(click)': 'rotate()',
    },
})
export class SortableHeaderDirective {
    @Input() sortable: SortColumn = '';
    @Input() direction: SortDirection = '';
    @Output() sort = new EventEmitter<SortEvent>();

    rotate() {
        this.direction = rotate[this.direction];
        this.sort.emit({ column: this.sortable, direction: this.direction });
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zidane
  • 1,696
  • 3
  • 21
  • 35
  • 1
    Can I know why you are using (sort)="onSort($event)" in template? I dont see any usage of it in component – Selaka Nanayakkara Jul 08 '23 at 13:09
  • You're loop over `let grade of paginationService.paginatedData| gradeSort:filter`, but you're sort `this.grades`. But, really if you have a service that return data paginated, you can not use a sort directive (the sort directive "sort" the array yet loaded). You need pass to your service the "sort", some like this [SO](https://stackoverflow.com/questions/65276233/how-to-put-angular-bootstrap-table-complete-example-working-with-a-real-servic/65281320#65281320) – Eliseo Jul 10 '23 at 06:10

2 Answers2

2

I'm not entirely sure, but I think your problem is just caused by a change that is not getting detected.

You're sorting this.grades but it is an object, and objects are tracked by it's reference not their content, so changing the object won't trigger a change-detection automatically. For change-detection to work, you must either replace the object entirely, or trigger the change-detection manually. In your case I would just go for the first option:

export class ListGradesComponent implements OnInit {
    @ViewChildren(SortableHeaderDirective) headers: QueryList<SortableHeaderDirective>;
    onSort({ column, direction }: SortEvent) {
        // resetting other headers
        this.headers.forEach(header => {
            if (header.sortable !== column) {
                header.direction = '';
            }
        });

        if (direction === ''|| column ==='') {
            this.grades = this.grades;
        } else {
            // Replace the object here for change-detection to catch this
            const sorted = this.grades.sort((a, b) => {
                const res = compare(a[column], b[column]);
                return direction === 'asc' ? res : -res;
            });
            this.grades = [...sorted];  // <-- replacing this.grades with a new object through the spread-operator
        }

        //this.service.sortColumn = column;
        //this.service.sortDirection = direction;
    }
}
andzep
  • 1,877
  • 24
  • 35
1

Ok, this is how the StackBlitz solution that you provided does the sorting.

when the (sort)="onSort($event)" is clicked it passes two values:

  1. column name of the sort to be performed
  2. the direction of the sort (it is passed as an empty string and then has two values "asc" and "desc" based on the times it is clicked)

To the child, (i.e., the sorting directive), the directive sorts it and passes two values back to the parent the column name and the direction and then the

 onSort({ column, direction }: SortEvent)

The method is called and then your parent sorts the column based on the direction value that it got from the child.

There are two main values responsible for the sorting the column name and the direction.

In your code, I see some inconsistency with the column names sortable="grade.ccode" sortable="gradescription"

How are you accessing the "ccode" inside grade?

It should be just sortable="code" sortable="description" and also all must be within the table tag. I don't see the full code. If it is written then that should be fine.

Please do verify that the column name is passed correctly from you parent to the child.