0

I'm trying to set the sort, sortUp, and sortDown icons to my table headers, following the table tutorial from ng-bootstrap.

There's a NgbdSortableHeader directive that changes the sorting of the column:

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

  rotate() {
    this.direction = rotate[this.direction];
    this.sort.emit({column: this.sortable, direction: this.direction});
  }
}

And the component that has the table:

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.scss'],
  providers: [UserService]
})
export class UsersComponent implements OnInit {
  faSort = faSort;
  faSortUp = faSortUp;
  faSortDown = faSortDown;

  users$: Observable<User[]>;
  total$: Observable<number>;

  @ViewChildren(NgbdSortableHeader) headers!: QueryList<NgbdSortableHeader>;

  constructor() {
  }

  ngOnInit(): void {
  }

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


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

I wonder if there's a way to access the current column sorting order and replace the icon from the item or hide them.

<th scope="col" sortable="firstName" (sort)="onSort($event)">
  Name
  <fa-icon [icon]="faSort"></fa-icon>
  <fa-icon [icon]="faSortUp"></fa-icon>
  <fa-icon [icon]="faSortDown"></fa-icon>
</th>
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • 1
    The `onSort` handler in your `UsersComponent` snippet is already receiving information about the name (i.e. `column`) and sort direction of the column that was last interacted with. If you expose these to your template, then you could simply use a whole series of `*ngIf` directives to control the visibility of each `fa-icon` (e.g. ``). However, this will get wordy, so consider encapsulating these icons in an aggregate component. – miqh Sep 23 '21 at 01:28
  • My initial idea (after posting this question) was to get the view child from the directive and change the icon of the `fa-icon` element in there. Then I would not need to change every table, just have one `fa-icon` element. – Nicke Manarin Sep 23 '21 at 01:36
  • @miqh I was able to change the icon from within the directive. – Nicke Manarin Sep 23 '21 at 02:14

1 Answers1

2

I was able to solve my issue by using @ChildComponent in the sort directive:

<th scope="col" sortable="firstName" (sort)="onSort($event)">
  Name
  <fa-icon [icon]="faSort" size="lg"></fa-icon>
</th>
import { Directive, EventEmitter, Input, Output, ViewChild, ContentChild, ElementRef } from "@angular/core";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { faSort, faSortUp, faSortDown } from "@fortawesome/pro-regular-svg-icons";

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

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

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

  @ContentChild(FaIconComponent) sortIcon?: FaIconComponent;

  rotate() {
    this.direction = rotate[this.direction];
    this.sort.emit({column: this.sortable, direction: this.direction});

    if (this.sortIcon !== undefined)
    {
        this.sortIcon.icon = this.direction === 'asc' ? faSortDown : this.direction === 'desc' ? faSortUp : faSort;
        this.sortIcon.render();
    }
  }
}
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • Another approach may be to have a `sortIcon = faSort;` property, change the value in the method `this.sortIcon = this.direction === 'asc' ? faSortDown ...` and bind it in the template ``, but your solution works just fine. – Yaroslav Admin Sep 23 '21 at 07:53
  • @YaroslavAdmin Interesting, thanks. I read that I have to call `render()` upon icon change, so I'm not sure how it would play out. – Nicke Manarin Sep 23 '21 at 12:54
  • Yes, you need to call `render()` when changing the icon programmatically, but when you use template binding - you don't need to. – Yaroslav Admin Sep 23 '21 at 13:34
  • 1
    Ahh, nice. Btw, since I have multiple columns, I would need to have multiple sortIcons, one for each column, and it would get verbose. – Nicke Manarin Sep 23 '21 at 13:44