# Row Sorting

## Live example

{% hint style="success" %}
[Row Sorting](https://demos.dikesoft.com/dk-grid/row/sorting) live example.
{% endhint %}

## Allowing the sorting operation

To allow the user to sort the DikeGrid rows, you must set a flag at the grid scope by providing an input property named <mark style="color:orange;">`allowSorting`</mark>. Its **default** value is **false**.

{% code title="row-sorting.component.html" %}

```markup
<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid"
    [allowSorting]="gridProperties.allowSorting">
</dike-grid>
```

{% endcode %}

{% hint style="info" %}
You can change the value of the <mark style="color:orange;">`allowSorting`</mark> property at **runtime**.
{% endhint %}

Open the [Floating Configuration Panel](https://docs.dikesoft.com/overview#floating-configuration-panel) for the live example and **mark** the **Allow Sorting** checkbox.

![Floating Configuration Panel - Sorting](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FiLAwaKAtLVpzqnVPqLnQ%2Fsorting-panel-conf.png?alt=media\&token=2842ef4c-7dac-42d3-b571-6056e4b6be62)

## Sortable columns

If you want the user to sort the DikeGrid rows by a column, mark the column as **sortable**. Its default value is **false**.

{% hint style="info" %}
You can only make **sortable** data columns, not column groups.
{% endhint %}

See the following column configuration:

{% code title="row-sorting.component.html" %}

```markup
<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid"
    [allowSorting]="gridProperties.allowSorting">
    
    <dike-grid-column
        fieldName="employeeId"
        headerText="Employee Id"
        dataType="Text"
        width="350">
    </dike-grid-column>
    
    <dike-grid-column
        fieldName="personalInfoGroup"
        headerText="Personal Info">

        <dike-grid-column
            fieldName="completeNameGroup"
            headerText="Complete Name">

            <dike-grid-column
                fieldName="firstName"
                headerText="Name"
                dataType="Text"
                width="150"
                sortable>
            </dike-grid-column>

            <dike-grid-column
                fieldName="lastName"
                headerText="Surname"
                dataType="Text"
                width="150"
                sortable>
            </dike-grid-column>

        </dike-grid-column>

        <dike-grid-column
            fieldName="gender"
            headerText="Gender"
            dataType="Binary"
            width="130">
        </dike-grid-column>

        <dike-grid-column
            fieldName="age"
            headerText="Age"
            dataType="Numeric"
            contentAlign="center"
            width="100"
            sortable>
        </dike-grid-column>
    </dike-grid-column>

    <dike-grid-column
        fieldName="email"
        headerText="Email"
        dataType="Text"
        width="300">
    </dike-grid-column>

    <dike-grid-column
       fieldName="hireDate"
       headerText="Hire Date"
       dataType="Date"
       width="150"
       sortable>
    </dike-grid-column>
    
</dike-grid>
```

{% endcode %}

We have the following **sortable** columns with the previous configuration: **Name** and **Surname** under the **Complete Name** group, **Age** column under the **Personal Info** group, and the **Hire Date** column.

You can make a column **sortable** at **runtime** using the corresponding **API**. For example, see the following code snippet:

{% code title="row-sorting.component.ts" %}

```typescript
onSetColumnSortable(): void {
  if (this.columnControl.valid) {
    // Get the selected column:
    const column = this.columnControl.value as DikeColumnDef;

    if (isDikeDataColumnDef(column)) {
      // Toggle the sortable flag for the selected column:
      this.dikeGrid.columnDef.setColumnSortable(column, !column.sortable);
    }
  }
}
```

{% endcode %}

As you can see, once you have got the column you want to set as **sortable** or **non-sortable**, use the <mark style="color:green;">`DikeGridColumnDef`</mark> service instance to change the column state.

{% hint style="success" %}
Before using the **column API**, you must grab the instance of the [<mark style="color:green;">`DikeGridColumnDef`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridcolumndef) service.
{% endhint %}

## Sorting rows through the UI

Once you define columns as **sortable**, you can sort the DikeGrid rows by these sortable columns.

### Clicking on the column header

You can sort the DikeGrid rows by clicking on the column header.

When you have clicked on the column header of a **sortable** column, you will see an **arrow** that indicates the **direction** of the sorting operation.

![Sorting by Name column clicking on the column header](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2Fn0V2XXpeSoopQ0hwCTcd%2Frow-sorting-column-header.gif?alt=media\&token=42403f5a-821d-47e8-971c-66135c506b40)

### Column Context Menu

When you define a column as **sortable**, you will see the sorting submenu in the [Column Context Menu](https://docs.dikesoft.com/fundamentals/grid-structure/column-context-menu). Then, you will see the sorting options.

![Sorting By Name column using the Column Context Menu](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FfT6OPt2E3O95g9k1jUww%2Frow-sorting-context-menu.gif?alt=media\&token=9add6add-fd97-48d8-bd43-d7ac67ca69ec)

## DikeGrid Sorting API

You can sort the DikeGrid rows using the **sorting API**.

Before using the **sorting API**, you must retrieve the corresponding DikeGrid instance by querying the component's view.

{% tabs %}
{% tab title="row-sorting.component.html" %}

```markup
<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid">
</dike-grid>
```

{% endtab %}

{% tab title="row-sorting.component.ts" %}

```typescript
@Component({
  selector: 'row-sorting',
  templateUrl: './row-sorting.component.html',
  styleUrls: ['./row-sorting.component.scss'],

  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class RowSortingComponent implements OnInit {
  // Retrieve the DikeGridComponent<T> instance from the view:
  @ViewChild('grid') dikeGrid: DikeGridComponent<Employee>;
  
  //...
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
The **sorting API** is an instance of type [<mark style="color:green;">`DikeGridSorting`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridsorting) under the [<mark style="color:green;">`DikeGridComponent`</mark>](https://docs.dikesoft.com/reference/components/dkgridcomponent) through the **read-only** property named <mark style="color:orange;">`sorting`</mark>.
{% endhint %}

The **sorting API** has the following methods:

| Method                 | Description                                                                                   |
| ---------------------- | --------------------------------------------------------------------------------------------- |
| `sortBy()`             | It sorts the DikeGrid rows by the given column in the given direction.                        |
| `isDikeGridSortedBy()` | It evaluates if the DikeGrid is sorted by the given column.                                   |
| `getCurrentSortable()` | It returns the column and the direction of the sorting operation. Otherwise, it returns null. |
| `clearSorting()`       | It removes the current sorting operation.                                                     |

{% hint style="success" %}
For further details, see the [<mark style="color:green;">`DikeGridSorting`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridsorting#methods) definition.
{% endhint %}

{% hint style="info" %}
You can sort the DikeGrid rows only by **one** column **sortable** at a time.
{% endhint %}

To see the use of the **sorting API**, we have created the following UI:

{% tabs %}
{% tab title="row-sorting.component.html" %}

```markup
<form [formGroup]="rowSortingForm"
  class="mt-2 flex flex-row flex-wrap items-center justify-around">
  <mat-form-field appearance="fill" class="flex-none w-56 m-2">
    <mat-label>Columns</mat-label>
      <mat-select [formControl]="columnControl" (selectionChange)="onSelectionChange($event)" required>
          <mat-select-trigger>
            <div class="w-40 flex flex-row items-center justify-between">
                <div>{{ columnControl.value?.headerText }}</div>
                <mat-icon *ngIf="columnControl.value && columnControl.value.sortable">sort_by_alpha</mat-icon>
            </div>
          </mat-select-trigger>
          <mat-option *ngFor="let column of gridColumns" [value]="column" [disabled]="!!column.children">
              <div class="flex flex-row items-center justify-between">
                <div>{{ column.headerText }}</div>
                <mat-icon *ngIf="column.sortable">sort_by_alpha</mat-icon>
              </div>
          </mat-option>
      </mat-select>
    </mat-form-field>

  <mat-radio-group class="flex-none w-56 m-2 flex flex-row justify-around"
      #radioDirections="matRadioGroup" formControlName="direction" required>
      <mat-radio-button class="my-2" value="asc">asc</mat-radio-button>
      <mat-radio-button class="my-2" value="desc">desc</mat-radio-button>
  </mat-radio-group>

  <div class="flex-none w-56 flex flex-col m-2 items-center">
      <button mat-raised-button
          class="flex-none w-56 my-2"
          color="primary"
          [disabled]="!rowSortingForm.valid || radioDirections.disabled"
          (click)="onSortBy()">Sort by
      </button>

      <button mat-raised-button
          class="flex-none w-56 my-2"
          color="primary"
          [disabled]="!columnControl.valid"
          (click)="onSetColumnSortable()">Set column {{ columnControl.value?.sortable ? 'non-' : '' }}sortable
      </button>
  </div>
</form>

<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid">
</dike-grid>

```

{% endtab %}

{% tab title="row-sorting.component.ts" %}

```typescript
onSelectionChange(data: MatSelectChange): void {
  const column = data.value as DikeColumnDef;

  if (isDikeDataColumnDef(column)) {
    if (!column.sortable) {
      this.rowSortingForm.get('direction').disable();

    } else {
      this.rowSortingForm.get('direction').enable();
    }
  }
}

onSortBy(): void {
  if (this.rowSortingForm.valid) {
    const column = this.rowSortingForm.get('column').value as DikeColumnDef;
    const direction = this.rowSortingForm.get('direction').value as 'asc' | 'desc';

    // if the DikeGrid instance is sorted, get the current columns and the sort direction:
    const currentSortable = this.dikeGrid.sorting.getCurrentSortable();

    /**
     * We will sort the DikeGrid rows, if only if:
     *
     *    1. The DikeGrid is not sorted.
     *    2. Or, the DikeGrid is sorted but the selected column is different than the current column.
     *    3. Or, the DikeGrid is sorted by the selected column but the selected direction is different.
     */
    if (isDikeDataColumnDef(column) &&
      (!currentSortable || currentSortable.sortedColumn.columnId !== column.columnId || currentSortable.type !== direction)) {
      this.dikeGrid.sorting.sortBy(column, direction);
    }
  }
}

onSetColumnSortable(): void {
  if (this.columnControl.valid) {
    // Get the selected column:
    const column = this.columnControl.value as DikeColumnDef;

    if (isDikeDataColumnDef(column)) {
      // Toggle the sortable flag for the selected column:
      this.dikeGrid.columnDef.setColumnSortable(column, !column.sortable);
    }
  }
}

```

{% endtab %}
{% endtabs %}

The previous HTML code generates the following output:

![Row sorting using the API](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FR41TB87TR8SK1E3Hdjfy%2Frow-sorting-ui-for-api.png?alt=media\&token=e256d47f-0355-4733-9c10-fbe1cc951fd2)

With the previous UI, you can do the following tasks:

1. You can choose any data column from the dropdown control. Sortable columns have an icon indicating they are sortable.
2. You can change the sortable state of a data column.
3. To sort the DikeGrid rows, you must select the direction of the sorting operation. Then click on the **Sort By** button.

It is essential to notice that we are **listening** to any change in the **DikeGrid's columns**, attaching a callback to the `columnsChange` output property from the [<mark style="color:green;">`DikeGridComponent`</mark>](https://docs.dikesoft.com/reference/components/dkgridcomponent).

{% tabs %}
{% tab title="row-sorting.component.html" %}

```markup
<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid"
    (columnsChange)="onColumnsChange($event)">
</dike-grid>
```

{% endtab %}

{% tab title="row-sorting.component.ts" %}

```typescript
onColumnsChange(columns: DikeColumnDef[]): void {
    this.gridColumns = this.flatColumns(columns);
    // Clear selection:
    this.rowSortingForm.patchValue({ column: null, direction: '' });
}
```

{% endtab %}
{% endtabs %}

Every time we receive a new set of columns, we flat them into a single array, invoking the method `flatColumns()`. So indeed, we are **flattening** the **column groups**, traversing them **recursively**.

{% code title="row-sorting.component.ts" %}

```typescript
private flatColumns(columns: DikeColumnDef[]): DikeColumnDef[] {
  return columns.reduce((accum, column) => {
    if (isDikeGroupColumnDef(column) && !!column.children && column.children.length > 0) {
      return [ ...accum, column, ...this.flatColumns(column.children) ];
    }

    return [ ...accum, column ];
  }, [ ]);
}
```

{% endcode %}

If you see the content of the **dropdown** control, you see the columns **Employee Id**, **Personal Info** group, **Complete Name** group, then **Name** and **Surname** columns, etc.

![Flattening column groups](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FfUsQ68waYlfQFsPSrfx3%2Frow-sorting-flattening-groups.png?alt=media\&token=5d28022e-d685-42ee-867f-d34b60dd6dbf)

The column groups are **disabled**, such as **Personal Info** and **Complete Name**.

## Sort event

Every time we sort the DikeGrid rows, the DikeGrid instance emits the related event.

{% tabs %}
{% tab title="row-sorting.component.html" %}

```markup
<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid"
    (sortChange)="onSortChange($event)">
</dike-grid>
```

{% endtab %}

{% tab title="row-sorting.component.ts" %}

```typescript
onSortChange(sortedBy: DikeColumnSortEvent<Employee>): void {
  console.log('Sort change: ', sortedBy);
}
```

{% endtab %}
{% endtabs %}

The emitted object is of type <mark style="color:green;">`DikeColumnSortEvent`</mark>, which contains the **column** and the **direction** of the current sorting operation.

{% hint style="success" %}
Open the **dev console** to see the output of the `sortChange` event.
{% endhint %}

{% hint style="info" %}
You can also listen to the `sortChange` event from the [<mark style="color:green;">`DikeGridSorting`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridsorting#events) instance.
{% endhint %}

## Summary

To perform the sorting operation, you must allow it by providing an input property named <mark style="color:orange;">`allowSorting`</mark>. Then, when creating columns, you can define them as **sortable**. Nevertheless, you can change their state at **runtime** using the related **API**. You can perform the sorting operation through the **UI** or the **sorting API**.

### Complete code for this section

{% tabs %}
{% tab title="row-sorting.component.html" %}

```markup
<form [formGroup]="rowSortingForm"
  class="mt-2 flex flex-row flex-wrap items-center justify-around">
  <mat-form-field appearance="fill" class="flex-none w-56 m-2">
    <mat-label>Columns</mat-label>
      <mat-select [formControl]="columnControl" (selectionChange)="onSelectionChange($event)" required>
          <mat-select-trigger>
            <div class="w-40 flex flex-row items-center justify-between">
                <div>{{ columnControl.value?.headerText }}</div>
                <mat-icon *ngIf="columnControl.value && columnControl.value.sortable">sort_by_alpha</mat-icon>
            </div>
          </mat-select-trigger>
          <mat-option *ngFor="let column of gridColumns" [value]="column" [disabled]="!!column.children">
              <div class="flex flex-row items-center justify-between">
                <div>{{ column.headerText }}</div>
                <mat-icon *ngIf="column.sortable">sort_by_alpha</mat-icon>
              </div>
          </mat-option>
      </mat-select>
    </mat-form-field>
    
      <mat-radio-group class="flex-none w-56 m-2 flex flex-row justify-around"
          #radioDirections="matRadioGroup" formControlName="direction" required>
          <mat-radio-button class="my-2" value="asc">asc</mat-radio-button>
          <mat-radio-button class="my-2" value="desc">desc</mat-radio-button>
      </mat-radio-group>
    
      <div class="flex-none w-56 flex flex-col m-2 items-center">
          <button mat-raised-button
              class="flex-none w-56 my-2"
              color="primary"
              [disabled]="!rowSortingForm.valid || radioDirections.disabled"
              (click)="onSortBy()">Sort by
          </button>

          <button mat-raised-button
              class="flex-none w-56 my-2"
              color="primary"
              [disabled]="!columnControl.valid"
              (click)="onSetColumnSortable()">Set column {{ columnControl.value?.sortable ? 'non-' : '' }}sortable
          </button>
      </div>
</form>

<dike-grid id="grid-row-sorting" height="650px" #grid="dkgGrid"
    [displayRowId]="gridProperties.displayRowId"
    [gridElevation]="gridProperties.matElevation"
    [gridElevationValue]="gridProperties.elevationValue"
    [striped]="gridProperties.stripeRows"
    [verticalRowLines]="gridProperties.verticalRowLines"

    (columnsChange)="onColumnsChange($event)"
    (sortChange)="onSortChange($event)"

    [allowSorting]="gridProperties.allowSorting"
    [datasource]="dkgDataSource">

    <dike-grid-column
        fieldName="employeeId"
        headerText="Employee Id"
        dataType="Text"
        width="350">
    </dike-grid-column>

    <dike-grid-column
        fieldName="personalInfoGroup"
        headerText="Personal Info">

        <dike-grid-column
            fieldName="completeNameGroup"
            headerText="Complete Name">

            <dike-grid-column
                fieldName="firstName"
                headerText="Name"
                dataType="Text"
                width="150"
                sortable>
            </dike-grid-column>

            <dike-grid-column
                fieldName="lastName"
                headerText="Surname"
                dataType="Text"
                width="150"
                sortable>
            </dike-grid-column>

        </dike-grid-column>

        <dike-grid-column
            fieldName="gender"
            headerText="Gender"
            dataType="Binary"
            width="130">
        </dike-grid-column>

        <dike-grid-column
            fieldName="age"
            headerText="Age"
            dataType="Numeric"
            contentAlign="center"
            width="100"
            sortable>
        </dike-grid-column>
    </dike-grid-column>

    <dike-grid-column
        fieldName="email"
        headerText="Email"
        dataType="Text"
        width="300">
    </dike-grid-column>

    <dike-grid-column
        fieldName="hireDate"
        headerText="Hire Date"
        dataType="Date"
        width="150"
        sortable>
    </dike-grid-column>
</dike-grid>

```

{% endtab %}

{% tab title="row-sorting.component.ts" %}

```typescript
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSelectChange } from '@angular/material/select';

import { Subscription } from 'rxjs';

import { DikeColumnDef, DikeColumnSortEvent, DikeGridComponent, 
  DikeGridDataSourceInput, isDikeDataColumnDef, isDikeGroupColumnDef 
} from '@dikesoft/angular-data-grid';

import { DikeGridProperties } from 'app/core/config/dike-grid.properties';
import { Employee } from 'app/mock-api/common/employees/data.model';

import { DikeGridConfig } from 'app/services/dike-grid.config.service';
import { SampleData } from 'app/services/sample-data.service';

@Component({
  selector: 'row-sorting',
  templateUrl: './row-sorting.component.html',
  styleUrls: ['./row-sorting.component.scss'],

  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class RowSortingComponent implements OnInit, OnDestroy {
  // Retrieve the DikeGridComponent<T> instance from the view:
  @ViewChild('grid') dikeGrid: DikeGridComponent<Employee>;

  dkgDataSource: DikeGridDataSourceInput<Employee>;
  gridProperties: DikeGridProperties;

  gridColumns: DikeColumnDef[];
  rowSortingForm: FormGroup;
  columnControl: FormControl;

  private changeGridPropertiesSubscription: Subscription = Subscription.EMPTY;

  constructor(
    private cdr: ChangeDetectorRef,
    private gridConfig: DikeGridConfig,
    private sampleData: SampleData) { }

  ngOnInit(): void {
    this.dkgDataSource = this.sampleData.getEmployees(1000);
    // Listening to any config property change:
    this.setChangeGridPropertiesSubscription();
    this.createForm();
  }
  
  ngOnDestroy(): void {
    this.changeGridPropertiesSubscription.unsubscribe();
  }

  onSelectionChange(data: MatSelectChange): void {
    const column = data.value as DikeColumnDef;

    if (isDikeDataColumnDef(column)) {
      if (!column.sortable) {
        this.rowSortingForm.get('direction').disable();

      } else {
        this.rowSortingForm.get('direction').enable();
      }
    }
  }

  onSortBy(): void {
    if (this.rowSortingForm.valid) {
      const column = this.rowSortingForm.get('column').value as DikeColumnDef;
      const direction = this.rowSortingForm.get('direction').value as 'asc' | 'desc';

      // if the DikeGrid instance is sorted, get the current columns and the sort direction:
      const currentSortable = this.dikeGrid.sorting.getCurrentSortable();

      /**
       * We will sort the DikeGrid rows, if only if:
       *
       *    1. The DikeGrid is not sorted.
       *    2. Or, the DikeGrid is sorted but the selected column is different than the current column.
       *    3. Or, the DikeGrid is sorted by the selected column but the selected direction is different.
       */
      if (isDikeDataColumnDef(column) &&
        (!currentSortable || currentSortable.sortedColumn.columnId !== column.columnId || currentSortable.type !== direction)) {
        this.dikeGrid.sorting.sortBy(column, direction);
      }
    }
  }

  onSetColumnSortable(): void {
    if (this.columnControl.valid) {
      // Get the selected column:
      const column = this.columnControl.value as DikeColumnDef;

      if (isDikeDataColumnDef(column)) {
        // Toggle the sortable flag for the selected column:
        this.dikeGrid.columnDef.setColumnSortable(column, !column.sortable);
      }
    }
  }

  onSortChange(sortedBy: DikeColumnSortEvent<Employee>): void {
    console.log('Sort change: ', sortedBy);
  }

  onColumnsChange(columns: DikeColumnDef[]): void {
    this.gridColumns = this.flatColumns(columns);
    // Clear selection:
    this.rowSortingForm.patchValue({ column: null, direction: '' });
  }

  private flatColumns(columns: DikeColumnDef[]): DikeColumnDef[] {
    return columns.reduce((accum, column) => {
      if (isDikeGroupColumnDef(column) && !!column.children && column.children.length > 0) {
        return [ ...accum, column, ...this.flatColumns(column.children) ];
      }

      return [ ...accum, column ];
    }, [ ]);
  }

  private createForm(): void {
    this.columnControl = new FormControl(null, Validators.required);

    this.rowSortingForm = new FormGroup({
      column: this.columnControl,
      direction: new FormControl({ value: '', disabled: false }, Validators.required),
    });
  }

  private setChangeGridPropertiesSubscription(): void {
    this.changeGridPropertiesSubscription.unsubscribe();
    this.changeGridPropertiesSubscription = this.gridConfig.configChange.subscribe((props: DikeGridProperties) => {
      this.gridProperties = props;
      this.cdr.markForCheck();
    });
  }
}

```

{% endtab %}
{% endtabs %}
