DevExtreme Angular - Custom Sources

Access to a custom data source is configured using the CustomStore component. DevExtreme provides ASP.NET and PHP extensions that help configure it and implement server-side data processing. You can also use the third-party extension for MongoDB. If these extensions are not suitable for your data source, follow the instructions below to configure the CustomStore manually.

The CustomSource's configuration differs depending on whether data is processed on the client or server. In the former case, switch the CustomStore to the raw mode and load all data from the server using the load function as shown in the following example:

jQuery
JavaScript
$(function() {
    $("#lookupContainer").dxLookup({
        dataSource: new DevExpress.data.DataSource({
            store: new DevExpress.data.CustomStore({
                key: "ID",
                loadMode: "raw",   
                load: function () {
                    return $.getJSON('https://mydomain.com/MyDataService');
                }
            })
        })
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from "@angular/core";
import { HttpClient, HttpClientModule } from "@angular/common/http";
import { DxLookupModule } from "devextreme-angular";
import DataSource from "devextreme/data/data_source";
import CustomStore from "devextreme/data/custom_store";
import "rxjs/add/operator/toPromise";
// ...
export class AppComponent {
    lookupDataSource: any = {};
    constructor(@Inject(HttpClient) httpClient: HttpClient) {
        this.lookupDataSource = new DataSource({
            store: new CustomStore({
                key: "ID",
                loadMode: "raw",   
                load: () => {
                    return httpClient.get('http://mydomain.com/MyDataService')
                        .toPromise();
                }
            })
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxLookupModule,
        HttpClientModule
    ],
    // ...
})
<dx-lookup ...
    [dataSource]="lookupDataSource">
</dx-lookup>
Vue
<template>
    <dx-lookup ... 
        :data-source="dataSource" />
</template>
<script>
import { DxLookup } from "devextreme-vue/lookup";
import CustomStore from "devextreme/data/custom_store";
import DataSource from "devextreme/data/data_source";
// ...
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const lookupDataSource = new DataSource({
    store: new CustomStore({
        key: "ID",
        loadMode: "raw",   
        load: () => {
            return fetch("https://mydomain.com/MyDataService")
                    .then(handleErrors);
        }
    })
});
export default {
    // ...
    data() {
        return {
            dataSource: lookupDataSource
        };
    },
    components: {
        // ...
        DxLookup
    }
}
</script>
React
import React from "react";
import Lookup from "devextreme-react/lookup";
import CustomStore from "devextreme/data/custom_store";
import DataSource from "devextreme/data/data_source";
// ...
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const lookupDataSource = new DataSource({
    store: new CustomStore({
        key: "ID",
        loadMode: "raw",   
        load: () => {
            return fetch("https://mydomain.com/MyDataService")
                    .then(handleErrors);
        }
    })
});
class App extends React.Component {
    render() {
        return (
            <Lookup ...
                dataSource={lookupDataSource}>
            </Lookup>
        );
    }
}
export default App;
NOTE
We recommend not using this mode with large amounts of data because all data is loaded at once.

In the latter case, use the CustomStore's load function to send data processing settings to the server. These settings are passed as a parameter to the load function and depend on the operations (paging, filtering, sorting, etc.) that you have enabled in the DataSource. The following settings are relevant for the Lookup:

After receiving these settings, the server should apply them to data and send back an object with the following structure:

{
    data: [{
        key: "Group 1",
        items: [ ... ] // result data objects
    },
    ...
    ]
}

If the group setting is absent, the object structure is different:

{
    data: [ ... ] // result data objects
}

If you specify the Lookup's value beforehand, the CustomStore should implement the byKey operation as well. Below is a generalized CustomStore configuration for the Lookup widget.

jQuery
JavaScript
$(function() {
    $("#lookupContainer").dxLookup({
        dataSource: new DevExpress.data.DataSource({
            key: "ID",
            load: function(loadOptions) {
                var d = $.Deferred(),
                    params = {};
                [
                    "skip",     
                    "take",  
                    "sort", 
                    "filter", 
                    "searchExpr",
                    "searchOperation",
                    "searchValue",
                    "group"
                ].forEach(function(i) {
                    if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                        params[i] = JSON.stringify(loadOptions[i]);
                });
                $.getJSON("http://mydomain.com/MyDataService", params)
                    .done(function(result) {
                        // Here, you can perform operations unsupported by the server
                        d.resolve(result.data);
                    });
                return d.promise();
            },
            byKey: function(key) {
                var d = new $.Deferred();
                $.get('https://mydomain.com/MyDataService?id=' + key)
                    .done(function(result) {
                        d.resolve(result);
                    });
                return d.promise();
            }
        })
    });
});
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
Angular
TypeScript
HTML
import { ..., Inject } from "@angular/core";
import { HttpClient, HttpClientModule, HttpParams } from "@angular/common/http";
import { DxLookupModule } from "devextreme-angular";
import DataSource from "devextreme/data/data_source";
import CustomStore from "devextreme/data/custom_store";
import "rxjs/add/operator/toPromise";
// ...
export class AppComponent {
    lookupData: any = {};
    constructor(@Inject(HttpClient) httpClient: HttpClient) {
        _this = this;
        this.lookupData = new DataSource({
            store: new CustomStore({
                key: "ID",
                load: (loadOptions) => {
                    let params: HttpParams = new HttpParams();
                    [
                        "skip",     
                        "take",  
                        "sort", 
                        "filter", 
                        "searchExpr",
                        "searchOperation",
                        "searchValue",
                        "group"
                    ].forEach(function(i) {
                        if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                            params = params.set(i, JSON.stringify(loadOptions[i]));
                    });
                    return httpClient.get("http://mydomain.com/MyDataService", { params: params })
                        .toPromise()
                        .then(result => {
                            // Here, you can perform operations unsupported by the server
                            return result.data;
                        });
                },
                byKey: function(key) {
                    return httpClient.get('https://mydomain.com/MyDataService?id=' + key)
                        .toPromise();
                }
            })
        });
    }
    isNotEmpty(value: any): boolean {
        return value !== undefined && value !== null && value !== "";
    }
}
@NgModule({
     imports: [
         // ...
         DxLookupModule,
         HttpClientModule 
     ],
     // ...
 })
<dx-lookup
    [dataSource]="lookupData">
</dx-lookup>
Vue
<template>
    <dx-lookup ... 
        :data-source="dataSource" />
</template>
<script>
import DxLookup from "devextreme-vue/lookup";
import CustomStore from "devextreme/data/custom_store";
// ...
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const lookupDataSource = {
    store: new CustomStore({
        key: "ID",
        load: (loadOptions) => {
            let params = "?";
            [
               "skip",     
                "take",  
                "sort", 
                "filter", 
                "searchExpr",
                "searchOperation",
                "searchValue",
                "group"
            ].forEach(function(i) {
                if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                    params += `${i}=${JSON.stringify(loadOptions[i])}&`;
            });
            params = params.slice(0, -1);
            return fetch(`https://mydomain.com/MyDataService${params}`)
                .then(handleErrors)
                .then(response => response.json())
                .then((result) => {
                    return result.data;
                });
        },
        byKey: (key) => {
            return fetch(`https://mydomain.com/MyDataService?id=${key}`)
                    .then(handleErrors);
        }
    })
}
export default {
    // ...
    data() {
        return {
            dataSource: lookupDataSource
        };
    },
    components: {
        // ...
        DxLookup
    }
}
</script>
React
import React from "react";
import Lookup from "devextreme-react/lookup";
import CustomStore from "devextreme/data/custom_store";
// ...
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
function handleErrors(response) {
    if (!response.ok) 
        throw Error(response.statusText);
    return response;
}
const lookupDataSource = {
    store: new CustomStore({
        key: "ID",
        load: (loadOptions) => {
            let params = "?";
            [
               "skip",     
                "take",  
                "sort", 
                "filter", 
                "searchExpr",
                "searchOperation",
                "searchValue",
                "group"
            ].forEach(function(i) {
                if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                    params += `${i}=${JSON.stringify(loadOptions[i])}&`;
            });
            params = params.slice(0, -1);
            return fetch(`https://mydomain.com/MyDataService${params}`)
                .then(handleErrors)
                .then(response => response.json())
                .then((result) => {
                    return result.data;
                });
        },
        byKey: (key) => {
            return fetch(`https://mydomain.com/MyDataService?id=${key}`)
                    .then(handleErrors);
        }
    })
}
class App extends React.Component {
    render() {
        return (
            <Lookup ...
                dataSource={lookupDataSource}>
            </Lookup>
        );
    }
}
export default App;
See Also