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:
<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:
<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:
<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:
<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.
<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:
<dx-list> <dxi-item> <div *dxTemplate> <dx-button text="I am a nested component"></dx-button> </div> </dxi-item> </dx-list>
Event Handling
<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:
<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
<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.
// ... 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>
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:
<dx-scroll-view> <div>Some scrollable content</div> </dx-scroll-view>
These UI components do not support dynamically or conditionally rendered content in their root element. For example, the following code does not work:
<dx-popup [(visible)]="showPopup"> {{ popupContent }} </dx-popup>
Wrap the content in a static element:
<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.
<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>
dxTemplate
attribute directive cannot be used on custom markup elements. Refer to the common Custom Templates article for more information.
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:
<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.
<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:
<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:
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:
<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:
<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.
<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...
<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:
<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:
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:
- The
getColumns()
function returns an array. - The array gets assigned to the
columns
property. This activates change detection. - When comparing the old and new
columns
values, the change detection mechanism callsgetColumns()
again and receives a different array. - 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.
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.
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).
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 []; } } }
If you have technical questions, please create a support ticket in the DevExpress Support Center.