DevExtreme Angular - Component Configuration Syntax

Static String Option Value

HTML
<dx-button text="Simple button"></dx-button>

Static Non-String Option Value

HTML
<dx-button
    [disabled]="false"
    [width]="50">
</dx-button>

Options of the Object Type

Use components prefixed with dxo- ("o" stands for "object"). In the following example, we configure the TreeMap widget's tooltip option:

HTML
<dx-tree-map>
    <dxo-tooltip
        [enabled]="true"
        format="thousands">
    </dxo-tooltip>
</dx-tree-map>

Particular options of the object type are not implemented as nested components. These options depend on the values of other options and therefore cannot be typed (editorOptions in the DataGrid, editorOptions in the Form, widget options in the Toolbar). Specify them with an object:

HTML
<dx-data-grid>
    <dxi-column
        [editorOptions]="{ width: 100 }">
    </dxi-column>
</dx-data-grid>

Collections

Use components prefixed with dxi- ("i" stands for "item"). The following example shows how to configure the DataGrid widget's columns option:

HTML
<dx-data-grid>
    <dxi-column dataField="firstName" caption="First Name"></dxi-column>
    <dxi-column dataField="lastName" caption="Last Name" [visible]="false"></dxi-column>
</dx-data-grid>

For options that accept either an object or a collection, use components prefixed with dxi- as well. The series and valueAxis options in the Chart widget exemplify this case:

HTML
<dx-chart>
    <dxi-series valueField="value" argumentField="argument"></dxi-series>
    <dxi-value-axis>
        <dxo-label format="millions"></dxo-label>
    </dxi-value-axis>
</dx-chart>

A special dxi- component, dxi-item, is designed to declare items in collection widgets. It supports structural directives provided by Angular, for instance, ngFor. The following code shows how to use dxi-item to declare items in the List widget.

dxi-item also supports directives that control parts of item appearance, such as badge in the code below. They are described in the Default Item Template section of each collection widget.

app.component.html
app.component.ts
<dx-list>
    <dxi-item>
        <h1>Available items</h1>
    </dxi-item>
    <dxi-item *ngFor="let item of listItems" [badge]="item.badge">
        <h2>{{item.text}}</h2>
    </dxi-item>
</dx-list>
// ...
export class AppComponent {
    listItems = [{
        text: 'Cars',
        badge: '12'
    }, {
        text: 'Bikes',
        badge: '5'
    }];
}

If a dxi-item contains nested components, wrap the content in an element with the dxTemplate directive without parameters:

HTML
<dx-list>
    <dxi-item>
        <div *dxTemplate>
            <dx-button text="I am a nested component"></dx-button>
        </div>
    </dxi-item>
</dx-list>

Event Handling

app.component.html
app.component.ts
<dx-button
    text="OK"
    (onClick)="okClicked($event)">
</dx-button>
import notify from "devextreme/ui/notify";
// ...
export class AppComponent {
    okClicked (e) {
        notify("The OK button was clicked")
    }
}

To handle events in a nested component, use a different syntax:

app.component.html
app.component.ts
<dx-data-grid>
    <dxi-column type="buttons">
        <dxi-button
            [onClick]="okClicked">
        </dxi-button>
    </dxi-column>
</dx-data-grid>
import notify from "devextreme/ui/notify";
// ...
export class AppComponent {
    okClicked (e) {
        notify("The OK button was clicked")
    }
}

Callback Functions

app.component.html
app.component.ts
<dx-vector-map>
    <dxi-layer
        [customize]="customizeLayers">
    </dxi-layer>
</dx-vector-map>
// ...
export class AppComponent {
    customizeLayers(elements) {
        // ...
    }
}

Callback functions are executed outside the component's context. If the context is important, explicitly bind the callback function to it in the constructor.

app.component.ts
app.component.html
// ...
export class AppComponent {
    myCountry: string = "USA"; // we need to access this context variable in the callback function

    constructor() {
        this.customizeLayers = this.customizeLayers.bind(this);
    }

    customizeLayers(elements) {
        let country = this.myCountry;
        // ...
    }
}
<dx-vector-map>
    <dxi-layer
        [customize]="customizeLayers">
    </dxi-layer>
</dx-vector-map>

One-Way Option Binding

Changes in the bindingProperty are propagated to the TextBox's value, but not vice versa:

app.component.html
app.component.ts
<dx-text-box [value]="bindingProperty"></dx-text-box>
// ...
export class AppComponent {
    bindingProperty: string = "Some value";
}

Two-Way Option Binding

Changes in the bindingProperty are propagated to the TextBox's value and vice versa:

app.component.html
app.component.ts
<dx-text-box [(value)]="bindingProperty"></dx-text-box>
// ...
export class AppComponent {
    bindingProperty: string = "Some value";
}

Declare Content in the Markup

The following widgets allow you to declare their content directly in the markup:

The following is an example with ScrollView:

HTML
<dx-scroll-view>
    <div>Some scrollable content</div>
</dx-scroll-view>

Templates

Templates allow you to customize widget elements. In the following code, an itemTemplate called listItem and a groupTemplate called listGroup customize items and groups in the List widget. Inside the templates, the itemData and groupData variables expose item and group data objects; the itemIndex variable gives access to the item index.

HTML
<dx-list
    [items]="groupedItems"
    [grouped]="true"
    itemTemplate="listItem"
    groupTemplate="listGroup">
    <div *dxTemplate="let itemData of 'listItem'; let itemIndex = index">
        {{itemIndex}} - {{itemData.itemProperty}}
    </div>
    <div *dxTemplate="let groupData of 'listGroup'">
        {{groupData.groupProperty}}
    </div>
</dx-list>
NOTE
The dxTemplate attribute directive cannot be used on custom markup elements.

Refer to the common Custom Templates article for more information.

NOTE

Angular has a built-in template directive. This causes an error when you try to specify an eponymous property on a configuration component (for instance, on dxo-master-detail). In this case, use the following syntax:

HTML
<dxo-master-detail [template]="'masterDetail'"></dxo-master-detail>

External Templates

External templates are created using the ng-template element. The following code replicates the example above, but here the itemTemplate is the external template. The groupTemplate is omitted.

The ngTemplateOutlet directive uses a template reference variable to reference the external template. The ngTemplateOutletContext directive specifies variables that are accessible in the template.

custom-list.component.html
<dx-list [items]="items" itemTemplate="listItem">
    <div *dxTemplate="let itemData of 'listItem'; let itemIndex = index">
        <ng-template 
            [ngTemplateOutlet]="customItemTemplate" 
            [ngTemplateOutletContext]="{ itemData: itemData, itemIndex: itemIndex }">
        </ng-template>
    </div>
</dx-list>

<ng-template #customItemTemplate let-data="itemData" let-index="itemIndex">
    {{index}} - {{data.itemProperty}}
</ng-template>

In the previous code, the external template is used in the same component in which it is declared. The following code illustrates the case when the external template is declared in another component. The ngTemplateOutlet directive should be set to an input property in this case:

parent.component.html
custom-list.component.html
custom-list.component.ts
<ng-template #customItemTemplate let-data="itemData" let-index="itemIndex">
    {{index}} - {{data.itemProperty}}
</ng-template>

<custom-list
    [externalItemTemplate]="customItemTemplate"> 
</custom-list>
<dx-list ...
    itemTemplate="listItem">
    <div *dxTemplate="let itemData of 'listItem'; let itemIndex = index">
        <ng-template 
            [ngTemplateOutlet]="externalItemTemplate" 
            [ngTemplateOutletContext]="{ itemData: itemData, itemIndex: itemIndex }">
        </ng-template>
    </div>
</dx-list>
import { Component, Input, TemplateRef } from '@angular/core';
@Component({
    selector: 'custom-list',
    templateUrl: './custom-list.component.html'
})
export class CustomListComponent {
    @Input() externalItemTemplate: TemplateRef<any>
    // ...
}

Call Methods

To call widget methods, you need the widget instance. To access it, use the @ViewChild or @ViewChildren decorator (depending on whether you are getting a single or multiple widget instances) and the component's instance property. These decorators accept a component name or a template reference variable. The following code illustrates this approach by the example of the DataGrid widget:

app.component.ts
app.component.html
import { Component, ViewChild } from "@angular/core";
import { DxDataGridComponent } from "devextreme-angular";
// ...
export class AppComponent {
    @ViewChild(DxDataGridComponent, { static: false }) dataGrid: DxDataGridComponent
    // ===== or using a template reference variable =====
    @ViewChild("targetDataGrid", { static: false }) dataGrid: DxDataGridComponent

    // Prior to Angular 8
    // @ViewChild(DxDataGridComponent) dataGrid: DxDataGridComponent
    // @ViewChild("targetDataGrid") dataGrid: DxDataGridComponent
    refresh() {
        this.dataGrid.instance.refresh();
    }

    // Getting multiple instances of one widget
    // @ViewChildren(DxDataGridComponent) dataGrids: QueryList<DxDataGridComponent>
}
<dx-data-grid #targetDataGrid [dataSource]="dataSource"></dx-data-grid>
<dx-button text="Refresh data" (onClick)="refresh()"></dx-button>

Alternatively, you can save the widget instance in a component property once the widget is initialized:

app.component.html
app.component.ts
<dx-data-grid
    [dataSource]="dataSource"
    (onInitialized)="saveGridInstance($event)">
</dx-data-grid>
<dx-button text="Refresh data" (onClick)="refresh()"></dx-button>
import { Component } from "@angular/core";
import DataGrid from "devextreme/ui/data_grid";
// ...
export class AppComponent {
    dataGridInstance: DataGrid;
    saveGridInstance (e) {
        this.dataGridInstance = e.component;
    }
    refresh() {
        this.dataGridInstance.refresh();
    }
}

Get a Widget Instance

You can access a widget instance in the component as described in Call Methods.

You can also use template reference variables to access a widget instance in the markup:

HTML
<dx-select-box #targetSelectBox [items]="items"></dx-select-box>
{{targetSelectBox.value}}

Dispose of a Widget

Call dispose() to free up the allocated resources and remove the DOM node associated with the widget:

HTML
<dx-data-grid #dataGridVar id="myDataGrid"></dx-data-grid>
TypeScript
import { Component, ViewChild } from '@angular/core';
import { DxDataGridModule, DxDataGridComponent } from 'devextreme-angular';
// ...
export class AppComponent {
    @ViewChild('dataGridVar', { static: false }) dataGrid: DxDataGridComponent;
    // Prior to Angular 8
    // @ViewChild('dataGridVar') dataGrid: DxDataGridComponent;

    removeDataGrid (e) {
        this.dataGrid.instance.dispose();
        document.getElementById('myDataGrid').remove();
    }
}

Data Layer

DevExtreme Data Layer is a set of components for working with data. Refer to the Data Layer API reference for code examples.

DevExtreme Validation Features

In the following example, two textboxes are placed in a validation group that is validated on a button click. Each textbox has a set of validation rules. The validation result is displayed under the textboxes in a validation summary.

app.component.html
app.component.ts
app.module.ts
<dx-validation-group>
    <dx-text-box [(value)]="email">
        <dx-validator>
            <dxi-validation-rule type="required" message="Email is required"></dxi-validation-rule>
            <dxi-validation-rule type="email" message="Email is invalid"></dxi-validation-rule>
        </dx-validator>
    </dx-text-box>

    <dx-text-box [(value)]="password" mode="password">
        <dx-validator>
            <dxi-validation-rule type="required" message="Password is required"></dxi-validation-rule>
        </dx-validator>
    </dx-text-box>

    <dx-validation-summary></dx-validation-summary>

    <dx-button (onClick)="validate($event)" text="Submit"></dx-button>
</dx-validation-group>
// ...
export class AppComponent {
    email: string;
    password: string;
    validate(params) {
        let result = params.validationGroup.validate();
        if (result.isValid) {
            // the values are valid
            // submit and reset them
            // params.validationGroup.reset();
        }
    }
}
// ...
import {
    DxTextBoxModule,
    DxValidatorModule,
    DxValidationSummaryModule,
    DxValidationGroupModule,
    DxButtonModule
} from 'devextreme-angular';

@NgModule({
    // ...
    imports: [
        // ...
        DxTextBoxModule,
        DxValidatorModule,
        DxValidationSummaryModule,
        DxValidationGroupModule,
        DxButtonModule
    ]
})
export class AppModule { }

Refer to the Data Validation article for more information.

Angular Forms Support

DevExtreme editors support the formControlName directive needed for features of reactive forms...

app.component.html
app.component.ts
app.module.ts
<form [formGroup]="myForm">
    <dx-text-box
        name="email"
        formControlName="email"
        [isValid]="emailControl.valid || emailControl.pristine"
        [validationError]="{ message: 'Email is invalid'}">
    </dx-text-box>
</form>
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, AbstractControl, Validators } from '@angular/forms';
// ...
export class AppComponent implements OnInit {
    email: string;
    emailControl: AbstractControl;
    myForm: FormGroup;
    ngOnInit() {
        this.myForm = new FormGroup({
            email: new FormControl('', Validators.compose([Validators.required, Validators.email]))
        });
        this.emailControl = this.myForm.controls['email'];
    }
}
// ...
import { ReactiveFormsModule } from '@angular/forms';
import { DxTextBoxModule } from 'devextreme-angular';

@NgModule({
    // ...
    imports: [
        // ...
        DxTextBoxModule,
        ReactiveFormsModule
    ]
})
export class AppModule { }

... and ngModel binding necessary to use the editors in template-driven forms:

app.component.html
app.component.ts
app.module.ts
<form>
    <dx-text-box
        name="email"
        email required
        #emailControl="ngModel"
        [(ngModel)]="email"
        [isValid]="emailControl.valid || emailControl.pristine"
        [validationError]="{ message: 'Email is invalid'}">
    </dx-text-box>
</form>
// ...
export class AppComponent {
    email: string;
}
// ...
import { FormsModule } from '@angular/forms';
import { DxTextBoxModule } from 'devextreme-angular';

@NgModule({
    // ...
    imports: [
        // ...
        DxTextBoxModule,
        FormsModule
    ]
})
export class AppModule { }

Angular Change Detection Specifics

Angular activates change detection on each option change. This approach backfires when you try to pass functions to widget options that do not accept them. For example:

app.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    template: `
        <dx-data-grid
            [columns]="getColumns()">
        </dx-data-grid>
    `,
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    getColumns() {
        return [
            { dataField: "firstName" },
            { dataField: "lastName" }
        ];
    }
}

In the code above, the getColumns() function returns an array of objects, but this array is created from scratch each time the function is called. This is what happens when you run this code:

  1. The getColumns() function returns an array.
  2. The array gets assigned to the columns option. This activates change detection.
  3. When comparing the old and new columns values, the change detection mechanism calls getColumns() again and receives a different array.
  4. The array gets assigned to the columns option, and the cycle repeats infinitely.

To workaround this behavior, do one of the following:

  • Return an object reference, not a new object, from the function:

    app.component.ts
    import { Component } from '@angular/core';
    
    @Component({
        selector: 'app-root',
        template: `
            <dx-data-grid
                [columns]="getColumns()">
            </dx-data-grid>
        `,
        styleUrls: ['./app.component.css']
    })
    export class AppComponent {
        _columns: [
            { dataField: "firstName" },
            { dataField: "lastName" }
        ];
        getColumns() {
            return this._columns;
        }
    }
  • Switch change detection to the OnPush mode:

    app.component.ts
    import { Component, ChangeDetectionStrategy } from '@angular/core';
    
    @Component({
        // ...
        changeDetection: ChangeDetectionStrategy.OnPush
    })
    // ...