# 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](/files/8Z6E1RVVGdSeVtwJY1f2)

{% 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.dikesoft.com/fundamentals/datasource/custom-datasource.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
