DevExtreme Angular - Component Configuration Syntax

Static String Property Value

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

Static Non-String Property Value

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

Properties of the Object Type

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

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

Particular properties of the object type are not implemented as nested components. These properties depend on the values of other properties and therefore cannot be typed (columns[].editorOptions in the DataGrid, item's editorOptions in the Form, items[].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 UI components prefixed with dxi- ("i" stands for "item"). The following example shows how to configure the DataGrid's columns property:

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 properties that accept either an object or a collection, use UI components prefixed with dxi- as well. The series and valueAxis properties in the Chart UI component 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- element, dxi-item, is designed to declare items in collection UI components. 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 UI component.

dxi-item also supports directives that control parts of item appearance, such as badge in the code below. They are described in the items section of each collection UI component.

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")
    }
}

In nested components, the () syntax cannot be used. Use the [] syntax instead:

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 Property 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 Property 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 UI components allow you to declare their content directly in the markup:

The following is an example with ScrollView:

app.component.html
<dx-scroll-view>
    <div>Some scrollable content</div>
</dx-scroll-view>
IMPORTANT

These UI components do not support dynamically or conditionally rendered content in their root element. For example, the following code does not work:

app.component.html
<dx-popup [(visible)]="showPopup">
    {{ popupContent }}
</dx-popup>

Wrap the content in a static element:

app.component.html
<dx-popup [(visible)]="showPopup">
    <div>
        {{ popupContent }}
    </div>
</dx-popup>

Templates

Templates allow you to customize UI component elements. In the following code, an itemTemplate called listItem and a groupTemplate called listGroup customize items and groups in the List. 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 UI component methods, you need its instance. To access it, use the @ViewChild or @ViewChildren decorator (depending on whether you are getting a single or multiple UI component 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:

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 UI component
    // @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 assign the UI component instance to a variable and use it to call the methods:

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 UI Component Instance

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

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

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

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 property change. This approach backfires when you try to pass functions to UI component properties that do not accept them. For example:

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

enum GridColumnsOption {
    PersonalDetails = 0,
    JobDetails = 1,
}

@Component({
    selector: 'app-component',
    template: `
        <dx-data-grid
            [columns]="getColumns(columnsOption)">
        </dx-data-grid>
    `,
})

export class AppComponent {
    columnsOption = GridColumnsOption.PersonalDetails;

    getColumns(option: GridColumnsOption): string[] {
        switch (option) {
            case GridColumnsOption.PersonalDetails:
                return [
                    'name',
                    'address',
                    'phone',
                ];
            case GridColumnsOption.JobDetails:
                return [
                    'name',
                    'position',
                    'salary',
                ];
            default:
                return [];
        }
    }
}

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 property. 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 property, and the cycle repeats infinitely.

To work around this behavior, implement one of the following techniques:

Use a component’s class field instead of a method.

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

enum GridColumnsOption {
    PersonalDetails = 0,
    JobDetails = 1,
}

@Component({
    selector: 'app-component',
    template: `
        <dx-data-grid
            [columns]="columns[columnsOption]">
        </dx-data-grid>
    `,
})

export class AppComponent {
    columnsOption = GridColumnsOption.PersonalDetails;

    columns = {
        [GridColumnsOption.PersonalDetails]: [
            'name',
            'address',
            'phone',
        ],
        [GridColumnsOption.JobDetails]: [
            'name',
            'position',
            'salary',
        ],
    };
}

Rewrite a component’s class method to a custom Angular pipe.

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

enum GridColumnsOption {
    PersonalDetails = 0,
    JobDetails = 1,
}

@Pipe({name: 'columnsPipe'})
export class ExponentialStrengthPipe implements PipeTransform {
    transform(columnsOption: GridColumnsOption): string[] {
        switch (option) {
            case GridColumnsOption.PersonalDetails:
                return [
                    'name',
                    'address',
                    'phone',
                ];
            case GridColumnsOption.JobDetails:
                return [
                    'name',
                    'position',
                    'salary',
                ];
            default:
                return [];
        }
    }
}

@Component({
    selector: 'app-component',
    template: `
        <dx-data-grid
            [columns]="columnsOption | columnsPipe">
        </dx-data-grid>
    `,
})
export class AppComponent {
    columnsOption = GridColumnsOption.PersonalDetails;
} 

Cache the return value of the component’s method (for example, you can use a universal apply pipe).

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

    enum GridColumnsOption {
        PersonalDetails = 0,
        JobDetails = 1,
    }

    @Pipe({ name: 'apply' })
    export class ApplyPipe<TArgs, TReturn> implements PipeTransform {
        transform(func: ((...args: TArgs[]) => TReturn), ...args: TArgs[]): TReturn { return func(...args); }
    }

    @Component({
        selector: 'app-component',
        template: `
            <dx-data-grid
                [columns]="getColumns | apply: columnsOption">
            </dx-data-grid>
        `,
    })
    export class AppComponent {
        columnsOption = GridColumnsOption.PersonalDetails;

        getColumns = (option: GridColumnsOption): string[] => {
            switch (option) {
                case GridColumnsOption.PersonalDetails:
                    return [
                        'name',
                        'address',
                        'phone',
                    ];
                case GridColumnsOption.JobDetails:
                    return [
                        'firstName',
                        'position',
                        'salary',
                    ];
                default:
                    return [];
            }
        }
    }
NOTE
We recommend that you use an arrow function with this technique.