# Custom DataSource

We name Custom DataSource when you provide a data source that extends from the <mark style="color:blue;">`abstract`</mark> class <mark style="color:green;">`DataSource`</mark>.

When the DikeGrid receives this type of data source, it connects to this DataSource and creates an instance of the type <mark style="color:green;">`DikeGridDataSource`</mark>, wrapping every data entry into a <mark style="color:green;">`DikeGridDataRowEntry`</mark> row.

Same as In-Memory DataSource, after wrapping operation, the DikeGrid **assigns** and **emits** a **unique id**. It also sets a **unique id** for every **row**.

See the following code snippet:

{% tabs %}
{% tab title="custom-data-source.component.html" %}

```markup
<dike-grid id="grid-custom-datasource" height="600px" #grid="dkgGrid"
    (dataDeliveryIdChange)="onDataDeliveryIdChange($event)">
</dike-grid>
```

{% endtab %}

{% tab title="custom-data-source.component.ts" %}

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

  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomDataSourceComponent implements OnInit, AfterViewInit, OnDestroy {
  // Retrieve the DikeGridComponent<T> instance from the view:
  @ViewChild('grid') dikeGrid: DikeGridComponent<Employee>;
 
  ngAfterViewInit(): void {
    // Once the DikeGrid intance is retrieved from the view, we create the DataSource instance:
    Promise.resolve().then(() => {
      this.dkgDataSource = new CustomDataSource(this.sampleData, this.dikeGrid.filter,
        this.dikeGrid.sorting, this.dikeGrid.pagination, this.dikeGrid.waitingIndicator);
    });
  }
  
  onDataDeliveryIdChange(id: string): void {
    console.log(`Data delivery id: ${id}`);
  } 
}

```

{% endtab %}

{% tab title="custom-datasource.ts" %}

```typescript
import { DataSource } from '@angular/cdk/collections';
import { PageEvent } from '@angular/material/paginator';

import { combineLatest, defer, merge, Observable, Subject, Subscription } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';

import { DikeColumnSortEvent, DikeFilterable, DikeGridFilter, DikeGridPagination,
    DikeGridSorting, DikeGridWaitingIndicator
} from '@dikesoft/angular-data-grid';

import { Employee } from 'app/mock-api/common/employees/data.model';
import { SampleData } from 'app/services/sample-data.service';

export class CustomDataSource extends DataSource<Employee> {

  private dataSetSubject: Subject<Employee[]>;
  private gridSubscription: Subscription = Subscription.EMPTY;

  constructor(
    private sampleData: SampleData,
    private gridFilter: DikeGridFilter<Employee>,
    private gridSorting: DikeGridSorting<Employee>,
    private gridPagination: DikeGridPagination,
    private gridIndicator: DikeGridWaitingIndicator) {

    super();

    this.dataSetSubject = new Subject<Employee[]>();
    this.setGridEventSubscription();
  }

  connect(): Observable<Employee[]> {
    return this.dataSetSubject.asObservable();
  }

  disconnect(): void {
    this.gridSubscription.unsubscribe();
  }

  private initialized(): Observable<void> {
    return defer(() => new Observable<void>((observer) => {
      observer.next();
      observer.complete();
    }));
  }

  private page(entries: Employee[]): Employee[] {
    // If paginator is not displayed, return the original entries:
    if (!this.gridPagination.matPaginator) {
      return entries;
    }

    const startIndex = this.gridPagination.matPaginator.pageIndex * this.gridPagination.matPaginator.pageSize;

    return entries.slice(startIndex, startIndex + this.gridPagination.matPaginator.pageSize);
  }

  private setGridEventSubscription(): void {
    // Prepare the filter observable:
    const gridFiltering$: Observable<DikeFilterable<Employee>[]> = merge(
      this.gridFilter.filterChange,
      this.gridFilter.clearFilterChange,
      this.initialized()

    ).pipe(
      map(() => {
        const validFilterables: DikeFilterable<Employee>[] = [ ];

        // Get all the valid filterables:
        this.gridFilter.filterables.forEach((filterable: DikeFilterable<Employee>) => {
          if (!!filterable.filter && filterable.filter.length > 0) {
            validFilterables.push({ ...filterable });
          }
        });

        return validFilterables;
      })
    );

    // Prepare the sorting observable:
    const gridSorting$: Observable<DikeColumnSortEvent<Employee>> = merge(this.gridSorting.sortChange, this.initialized()).pipe(
      map(() => this.gridSorting.getCurrentSortable())
    );

    /**
     * Prepare the pagination event:
     *
     * Since the paginator could be hidden at runtime, we listen the paginatorChange
     * observable.
     *
     * If the paginator is displayed, we listen to the pageChange observable. Otherwise,
     * we return a void observable.
     *
     */
    const gridPaging$: Observable<PageEvent | void> = merge(this.gridPagination.paginatorChange, this.initialized())
      .pipe(
        switchMap(paginator => !!paginator ? merge(paginator.page, this.initialized()) : this.initialized()
      )
    );

    // Execute the actual REST invocation:
    const gridServerData$: Observable<Employee[]> = combineLatest([gridFiltering$, gridSorting$]).pipe(
      tap(() => this.gridIndicator.loadingIndicator()),
      // Cancel the previous invocation and make a new one:
      switchMap(([filterables, sorting]) => this.sampleData.searchEmployees(filterables, sorting)),
    );

    // For the sake of brevity, pagination is done in the client side:
    const pagedData$ = combineLatest([gridServerData$, gridPaging$]).pipe(
      tap(([entries]) => {
        if (!!this.gridPagination.matPaginator && this.gridPagination.matPaginator.length !== entries.length) {
          this.gridPagination.matPaginator.length = entries.length;
          this.gridPagination.matPaginator.firstPage();
        }
      }),
      map(([entries]) => this.page(entries))
    );

    this.gridSubscription.unsubscribe();
    this.gridSubscription = pagedData$.pipe(
      // Once the data set arrives, we hide the loading indicator:
      tap(() => this.gridIndicator.hideWaitingIndicator())

    ).subscribe(entries => this.dataSetSubject.next(entries));
  }
}

```

{% endtab %}

{% tab title="sample-data.service.ts" %}

```typescript
import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';
import { delay } from 'rxjs/operators';

import { DikeFilterable, DikeColumnSortEvent } from '@dikesoft/angular-data-grid';
import { Employee } from 'app/mock-api/common/employees/data.model';

@Injectable({
  providedIn: 'root'
})
export class SampleData {

  constructor(
    private httpClient: HttpClient) { }

  getEmployees(sampleSize: number): Observable<Employee[]> {
    return this.httpClient.get<Employee[]>('api/common/employees', { params: { sampleSize } }).pipe(delay(350));
  }

  searchEmployees(filterables: DikeFilterable<Employee>[], sortedColumn: DikeColumnSortEvent<Employee>): Observable<Employee[]> {
    return this.httpClient.post<Employee[]>('api/common/employees', { filterables, sortedColumn }).pipe(delay(350));
  }
}

```

{% endtab %}
{% endtabs %}

1. After querying the DikeGrid instance from the component's view, we create a <mark style="color:green;">`CustomDataSource`</mark> object passing in all the dependencies to implement the logic for this data source.
2. We obtain the data from a **REST API**. This API is a mock service that runs in memory. That is why we delay every invocation **350** milliseconds.
3. The **REST API** receives the **filtering** and **sorting** event data to process them behind the REST API.
4. For the sake of brevity, we execute the pagination operation in the client after receiving the response from the **REST API**.

{% hint style="warning" %}
Be aware that the DikeGrid will **wrap** every response from the REST API assigning **new ids** for every row and a **unique id** for the whole data set. The DikeGrid also **resets** the **edition** and **selection** operations. It means that the DikeGrid removes the history of changes and deselects rows.
{% endhint %}

## DataSource decorators

Once you assign your custom data source to the DikeGrid instance, it connects to that data source.

See the following diagram:

![Custom DataSource decorators](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FukTrbxYAFx9NBKXHx4qi%2Fcustom-datasource-decorators.png?alt=media\&token=25f44bdd-ffb6-4a69-bdca-bc772e2e3cae)

{% hint style="success" %}
The <mark style="color:green;">`DikeGridDataSource`</mark> wraps every data entry into a row of the type <mark style="color:green;">`DikeGridDataRowEntry`</mark>.
{% endhint %}

If you have allowed the **edition** operation, the DikeGrid creates the following decorators:

|                                                                    |                                                                                                                                                                                           |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:green;">`DikeRefreshingDataSource`</mark>       | This decorator always wraps a <mark style="color:green;">`DikeGridDataSource`</mark> instance. It pushes the rows when the user has **updated** them.                                     |
| <mark style="color:green;">`DikeEditionStateDataSource`</mark>     | It splits the set of rows depending on their status, <mark style="color:red;">`Modified`</mark>, <mark style="color:red;">`Deleted`</mark>, or <mark style="color:red;">`Editing`</mark>. |
| <mark style="color:green;">`DikeEditionFilteringDataSource`</mark> | This decorator manages all the filtering operations over the edited rows.                                                                                                                 |

{% hint style="info" %}
The DikeGrid creates the edition decorators only if you allow edition because the DikeGrid manages all the edition operations in memory.
{% endhint %}

{% hint style="warning" %}
You can not assign **custom decorators** when you provide a **Custom DataSource**.
{% endhint %}

{% hint style="warning" %}
**Row grouping** operation is **not allowed** when you provide a **Custom DataSource**.
{% endhint %}

## Summary

You can provide a custom data source to the DikeGrid. For example, a custom data source could take remote data entries.&#x20;

The DikeGrid manages the edition operation by adding the corresponding decorators.

Be aware that you can not group rows or assign custom decorators.

### Complete code for this section

{% tabs %}
{% tab title="custom-data-source.component.html" %}

```markup
<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"
        (click)="onClearFilter()">Clear Filter
    </button>
</div>

<dike-grid id="grid-custom-datasource" height="600px" #grid="dkgGrid"
    [displayRowId]="gridProperties.displayRowId"
    [gridElevation]="gridProperties.matElevation"
    [gridElevationValue]="gridProperties.elevationValue"
    [striped]="gridProperties.stripeRows"
    [verticalRowLines]="gridProperties.verticalRowLines"
    
    (dataDeliveryIdChange)="onDataDeliveryIdChange($event)"
    
    [allowSelection]="gridProperties.allowSelection"
    [allowRowFiltering]="gridProperties.allowRowFilter"
    [allowSorting]="gridProperties.allowSorting"
    [allowPagination]="gridProperties.allowPagination"
    [pageSize]="gridProperties.pageSize"
    
    (filterChange)="onFilterChange($event)"
    (editionFilterChange)="onEditionFilterChange($event)"
    
    (selectionChange)="onSelectionChange($event)"
    (editionRowChange)="onEditionRowChange($event)"
    (updateRowChange)="onUpdateRowChange($event)"
    (cancelRowEditionChange)="onCancelRowEditionChange($event)"
    (removeRowChange)="onRemoveRowChange($event)"
    (restoreRowChange)="onRestoreRowChange($event)"
    
    allowEdition
    [editionMode]="gridProperties.editionMode"
    
    [datasource]="dkgDataSource">

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

    <dike-grid-column
        fieldName="country"
        headerText="Country"
        dataType="Text"
        width="250"
        sortable>
    </dike-grid-column>
    
    <dike-grid-column
        fieldName="completeNameGroup"
        headerText="Complete Name">

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

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

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

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

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

</dike-grid>
    
```

{% endtab %}

{% tab title="custom-data-source.component.ts" %}

```typescript
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subscription } from 'rxjs';

import { DikeFilterable, DikeGridComponent, DikeGridDataRowEntry, DikeGridDataSourceInput } 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 { CustomDataSource } from 'app/modules/admin/datasource/custom-data-source/custom-datasource';

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

@Component({
  selector: 'custom-data-source',
  templateUrl: './custom-data-source.component.html',
  styleUrls: ['./custom-data-source.component.scss'],

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

  dkgDataSource: DikeGridDataSourceInput<Employee>;
  gridProperties: DikeGridProperties;

  private changeGridPropertiesSubscription: Subscription = Subscription.EMPTY;

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

  ngOnInit(): void {
    // Listening to any config property change:
    this.setChangeGridPropertiesSubscription();
  }

  ngAfterViewInit(): void {
    // Once the DikeGrid intance is retrieved from the view, we create the DataSource instance:
    Promise.resolve().then(() => {
      this.dkgDataSource = new CustomDataSource(this.sampleData, this.dikeGrid.filter,
        this.dikeGrid.sorting, this.dikeGrid.pagination, this.dikeGrid.waitingIndicator);
    });
  }

  ngOnDestroy(): void {
    this.changeGridPropertiesSubscription.unsubscribe();
  }

  onDataDeliveryIdChange(id: string): void {
    console.log(`Data delivery id: ${id}`);
  }

  onFilterChange(filterable: DikeFilterable<Employee>): void {
    console.log('onFilterChange => ', filterable);
  }

  onEditionFilterChange(filterable: DikeFilterable<Employee>): void {
    console.log('onEditionFilterChange => ', filterable);
  }

  onSelectionChange(rows: DikeGridDataRowEntry<Employee>[]): void {
    console.log('Selection change: ', rows);
  }

  onEditionRowChange(row: DikeGridDataRowEntry<Employee>): void {
    console.log('Edition row change: ', row);
  }

  onUpdateRowChange(rows: DikeGridDataRowEntry<Employee> | DikeGridDataRowEntry<Employee>[]): void {
    console.log('Update row(s) change: ', rows);
  }

  onCancelRowEditionChange(rows: DikeGridDataRowEntry<Employee> | DikeGridDataRowEntry<Employee>[]): void {
    console.log('Cancel row(s) edition change: ', rows);
  }

  onRemoveRowChange(rows: DikeGridDataRowEntry<Employee> | DikeGridDataRowEntry<Employee>[]): void {
    console.log('Remove row(s) change: ', rows);
  }

  onRestoreRowChange(rows: DikeGridDataRowEntry<Employee> | DikeGridDataRowEntry<Employee>[]): void {
    console.log('Restore row(s) change: ', rows);
  }

  onClearFilter(): void {
    console.log('Clear Filter => ', this.dikeGrid.filter.clearFilter());
  }

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

```

{% endtab %}

{% tab title="custom-datasource.ts" %}

```typescript
import { DataSource } from '@angular/cdk/collections';
import { PageEvent } from '@angular/material/paginator';

import { combineLatest, defer, merge, Observable, Subject, Subscription } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';

import { DikeColumnSortEvent, DikeFilterable, DikeGridFilter, DikeGridPagination,
    DikeGridSorting, DikeGridWaitingIndicator
} from '@dikesoft/angular-data-grid';

import { Employee } from 'app/mock-api/common/employees/data.model';
import { SampleData } from 'app/services/sample-data.service';

export class CustomDataSource extends DataSource<Employee> {

  private dataSetSubject: Subject<Employee[]>;
  private gridSubscription: Subscription = Subscription.EMPTY;

  constructor(
    private sampleData: SampleData,
    private gridFilter: DikeGridFilter<Employee>,
    private gridSorting: DikeGridSorting<Employee>,
    private gridPagination: DikeGridPagination,
    private gridIndicator: DikeGridWaitingIndicator) {

    super();

    this.dataSetSubject = new Subject<Employee[]>();
    this.setGridEventSubscription();
  }

  connect(): Observable<Employee[]> {
    return this.dataSetSubject.asObservable();
  }

  disconnect(): void {
    this.gridSubscription.unsubscribe();
  }

  private initialized(): Observable<void> {
    return defer(() => new Observable<void>((observer) => {
      observer.next();
      observer.complete();
    }));
  }

  private page(entries: Employee[]): Employee[] {
    // If paginator is not displayed, return the original entries:
    if (!this.gridPagination.matPaginator) {
      return entries;
    }

    const startIndex = this.gridPagination.matPaginator.pageIndex * this.gridPagination.matPaginator.pageSize;

    return entries.slice(startIndex, startIndex + this.gridPagination.matPaginator.pageSize);
  }

  private setGridEventSubscription(): void {
    // Prepare the filter observable:
    const gridFiltering$: Observable<DikeFilterable<Employee>[]> = merge(
      this.gridFilter.filterChange,
      this.gridFilter.clearFilterChange,
      this.initialized()

    ).pipe(
      map(() => {
        const validFilterables: DikeFilterable<Employee>[] = [ ];

        // Get all the valid filterables:
        this.gridFilter.filterables.forEach((filterable: DikeFilterable<Employee>) => {
          if (!!filterable.filter && filterable.filter.length > 0) {
            validFilterables.push({ ...filterable });
          }
        });

        return validFilterables;
      })
    );

    // Prepare the sorting observable:
    const gridSorting$: Observable<DikeColumnSortEvent<Employee>> = merge(this.gridSorting.sortChange, this.initialized()).pipe(
      map(() => this.gridSorting.getCurrentSortable())
    );

    /**
     * Prepare the pagination event:
     *
     * Since the paginator could be hidden at runtime, we listen the paginatorChange
     * observable.
     *
     * If the paginator is displayed, we listen to the pageChange observable. Otherwise,
     * we return a void observable.
     *
     */
    const gridPaging$: Observable<PageEvent | void> = merge(this.gridPagination.paginatorChange, this.initialized())
      .pipe(
        switchMap(paginator => !!paginator ? merge(paginator.page, this.initialized()) : this.initialized()
      )
    );

    // Execute the actual REST invocation:
    const gridServerData$: Observable<Employee[]> = combineLatest([gridFiltering$, gridSorting$]).pipe(
      tap(() => this.gridIndicator.loadingIndicator()),
      // Cancel the previous invocation and make a new one:
      switchMap(([filterables, sorting]) => this.sampleData.searchEmployees(filterables, sorting)),
    );

    // For the sake of brevity, pagination is done in the client side:
    const pagedData$ = combineLatest([gridServerData$, gridPaging$]).pipe(
      tap(([entries]) => {
        if (!!this.gridPagination.matPaginator && this.gridPagination.matPaginator.length !== entries.length) {
          this.gridPagination.matPaginator.length = entries.length;
          this.gridPagination.matPaginator.firstPage();
        }
      }),
      map(([entries]) => this.page(entries))
    );

    this.gridSubscription.unsubscribe();
    this.gridSubscription = pagedData$.pipe(
      // Once the data set arrives, we hide the loading indicator:
      tap(() => this.gridIndicator.hideWaitingIndicator())

    ).subscribe(entries => this.dataSetSubject.next(entries));
  }
}

```

{% endtab %}

{% tab title="sample-data.service.ts" %}

```typescript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable } from 'rxjs';
import { delay } from 'rxjs/operators';

import { DikeFilterable, DikeColumnSortEvent } from '@dikesoft/angular-data-grid';
import { Employee } from 'app/mock-api/common/employees/data.model';

@Injectable({
  providedIn: 'root'
})
export class SampleData {

  constructor(
    private httpClient: HttpClient) { }

  getEmployees(sampleSize: number): Observable<Employee[]> {
    return this.httpClient.get<Employee[]>('api/common/employees', { params: { sampleSize } }).pipe(delay(350));
  }

  searchEmployees(filterables: DikeFilterable<Employee>[], sortedColumn: DikeColumnSortEvent<Employee>): Observable<Employee[]> {
    return this.httpClient.post<Employee[]>('api/common/employees', { filterables, sortedColumn }).pipe(delay(350));
  }
}

```

{% endtab %}
{% endtabs %}
