<form action="your-action" (submit)="onFormSubmit($event)">
<dx-form
id="form"
(onOptionChanged)="onOptionChanged($event)"
[formData]="customer"
[readOnly]="false"
[showColonAfterLabel]="true"
[showValidationSummary]="true"
validationGroup="customerData"
>
<dxi-item itemType="group" caption="Credentials">
<dxi-item dataField="Email" [editorOptions]="emailEditorOptions">
<dxi-validation-rule type="required" message="Email is required">
</dxi-validation-rule>
<dxi-validation-rule type="email" message="Email is invalid">
</dxi-validation-rule>
<dxi-validation-rule
type="async"
message="Email is already registered"
[validationCallback]="asyncValidation"
>
</dxi-validation-rule>
</dxi-item>
<dxi-item dataField="Password" [editorOptions]="passwordEditorOptions">
<dxi-validation-rule type="required" message="Password is required">
</dxi-validation-rule>
</dxi-item>
<dxi-item
name="ConfirmPassword"
editorType="dxTextBox"
dataField="ConfirmPassword"
[editorOptions]="confirmPasswordEditorOptions"
>
<dxo-label text="Confirm Password"> </dxo-label>
<dxi-validation-rule
type="required"
message="Confirm Password is required"
>
</dxi-validation-rule>
<dxi-validation-rule
type="compare"
[comparisonTarget]="passwordComparison"
message="Password and Confirm Password do not match"
>
</dxi-validation-rule>
</dxi-item>
</dxi-item>
<dxi-item itemType="group" caption="Personal Data">
<dxi-item dataField="Name" [editorOptions]="nameEditorOptions">
<dxi-validation-rule type="required" message="Name is required">
</dxi-validation-rule>
<dxi-validation-rule
type="pattern"
[pattern]="namePattern"
message="Do not use digits in the Name"
>
</dxi-validation-rule>
</dxi-item>
<dxi-item
dataField="Date"
editorType="dxDateBox"
[editorOptions]="dateBoxOptions"
>
<dxo-label text="Date of birth"> </dxo-label>
<dxi-validation-rule
type="required"
message="Date of birth is required"
>
</dxi-validation-rule>
<dxi-validation-rule
type="range"
[max]="maxDate"
message="You must be at least 21 years old"
>
</dxi-validation-rule>
</dxi-item>
<dxi-item
dataField="VacationDates"
editorType="dxDateRangeBox"
[editorOptions]="dateRangeBoxOptions"
>
<dxo-label text="Vacation Dates"> </dxo-label>
<dxi-validation-rule
type="custom"
message="The vacation period must not exceed 25 days"
[validationCallback]="validateVacationDatesRange"
>
</dxi-validation-rule>
<dxi-validation-rule
type="custom"
message="Both start and end dates must be selected"
[validationCallback]="validateVacationDatesPresence"
>
</dxi-validation-rule>
</dxi-item>
</dxi-item>
<dxi-item itemType="group" caption="Billing address">
<dxi-item
dataField="Country"
editorType="dxSelectBox"
[editorOptions]="{ dataSource: countries }"
>
<dxi-validation-rule type="required" message="Country is required">
</dxi-validation-rule>
</dxi-item>
<dxi-item
dataField="City"
editorType="dxAutocomplete"
[editorOptions]="{ dataSource: cities, minSearchLength: 2 }"
>
<dxi-validation-rule
type="pattern"
[pattern]="cityPattern"
message="Do not use digits in the City name"
>
</dxi-validation-rule>
<dxi-validation-rule
type="stringLength"
[min]="2"
message="City must have at least 2 symbols"
>
</dxi-validation-rule>
<dxi-validation-rule type="required" message="City is required">
</dxi-validation-rule>
</dxi-item>
<dxi-item dataField="Address" [editorOptions]="addressEditorOptions">
<dxi-validation-rule type="required" message="Address is required">
</dxi-validation-rule>
</dxi-item>
<dxi-item
dataField="Phone"
helpText="Enter the phone number in USA phone format"
[editorOptions]="phoneEditorOptions"
>
<dxi-validation-rule
type="pattern"
[pattern]="phonePattern"
message="The phone must have a correct USA phone format"
>
</dxi-validation-rule>
</dxi-item>
</dxi-item>
<dxi-item
itemType="group"
cssClass="last-group"
[colCountByScreen]="colCountByScreen"
>
<dxi-item
itemType="simple"
dataField="Accepted"
editorType="dxCheckBox"
[editorOptions]="{
text: 'I agree to the Terms and Conditions',
width: 270,
value: false
}"
>
<dxo-label [visible]="false"> </dxo-label>
<dxi-validation-rule
type="compare"
[comparisonTarget]="checkComparison"
message="You must agree to the Terms and Conditions"
>
</dxi-validation-rule>
</dxi-item>
<dxi-item
itemType="group"
cssClass="buttons-group"
[colCountByScreen]="colCountByScreen"
>
<dxi-item
name="Reset"
itemType="button"
[buttonOptions]="resetButtonOptions"
>
</dxi-item>
<dxi-item itemType="button" [buttonOptions]="registerButtonOptions">
</dxi-item>
</dxi-item>
</dxi-item>
</dx-form>
</form>
import {
NgModule, Component, ViewChild, enableProdMode,
} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {
DxCheckBoxModule,
DxSelectBoxModule,
DxNumberBoxModule,
DxAutocompleteModule,
} from 'devextreme-angular';
import notify from 'devextreme/ui/notify';
import Validator from 'devextreme/ui/validator';
import { AsyncRule } from 'devextreme-angular/common';
import { DxFormModule, DxFormComponent, DxFormTypes } from 'devextreme-angular/ui/form';
import { DxTextBoxTypes } from 'devextreme-angular/ui/text-box';
import { DxDateBoxTypes } from 'devextreme-angular/ui/date-box';
import { DxDateRangeBoxTypes } from 'devextreme-angular/ui/date-range-box';
import { DxButtonModule, DxButtonTypes } from 'devextreme-angular/ui/button';
import { Customer, Service } from './app.service';
type EditorOptions = DxTextBoxTypes.Properties;
if (!/localhost/.test(document.location.host)) {
enableProdMode();
}
const sendRequest = function (value) {
const invalidEmail = 'test@dx-email.com';
return new Promise((resolve) => {
setTimeout(() => {
resolve(value !== invalidEmail);
}, 1000);
});
};
@Component({
selector: 'demo-app',
providers: [Service],
templateUrl: `app/app.component.html`,
styleUrls: [`app/app.component.css`],
})
export class AppComponent {
@ViewChild(DxFormComponent, { static: false }) form: DxFormComponent;
customer: Customer;
countries: string[];
cities: string[];
maxDate: Date = new Date();
cityPattern = '^[^0-9]+$';
namePattern = /^[^0-9]+$/;
phonePattern = /^[02-9]\d{9}$/;
passwordEditorOptions: EditorOptions = {
mode: 'password',
valueChangeEvent: 'keyup',
onValueChanged: () => {
let editor = this.form.instance.getEditor('ConfirmPassword');
if (editor.option('value')) {
let instance = Validator.getInstance(editor.element()) as Validator;
instance.validate();
}
},
buttons: [
{
name: 'password',
location: 'after',
options: {
stylingMode: 'text',
icon: 'eyeopen',
onClick: () => this.changePasswordMode('Password'),
},
},
],
};
emailEditorOptions: EditorOptions = {
valueChangeEvent: 'keyup',
};
nameEditorOptions: EditorOptions = {
valueChangeEvent: 'keyup',
};
addressEditorOptions: EditorOptions = {
valueChangeEvent: 'keyup',
};
phoneEditorOptions: EditorOptions = {
mask: '+1 (X00) 000-0000',
maskRules: {
X: /[02-9]/,
},
maskInvalidMessage: 'The phone must have a correct USA phone format',
valueChangeEvent: 'keyup',
};
confirmPasswordEditorOptions: EditorOptions = {
mode: 'password',
valueChangeEvent: 'keyup',
buttons: [
{
name: 'password',
location: 'after',
options: {
icon: 'eyeopen',
stylingMode: 'text',
onClick: () => this.changePasswordMode('ConfirmPassword'),
},
},
],
};
colCountByScreen: DxFormTypes.GroupItem['colCountByScreen'] = {
xs: 2,
sm: 2,
md: 2,
lg: 2,
};
dateBoxOptions: DxDateBoxTypes.Properties = {
placeholder: 'Birth Date',
acceptCustomValue: false,
openOnFieldClick: true,
};
dateRangeBoxOptions: DxDateRangeBoxTypes.Properties = {
startDatePlaceholder: 'Start Date',
endDatePlaceholder: 'End Date',
acceptCustomValue: false,
};
registerButtonOptions: DxButtonTypes.Properties = {
text: 'Register',
type: 'default',
width: '120px',
useSubmitBehavior: true,
};
resetButtonOptions: DxButtonTypes.Properties = {
text: 'Reset',
width: '120px',
disabled: true,
icon: 'refresh',
onClick: () => {
this.form.instance.reset();
},
};
constructor(service: Service) {
this.maxDate = new Date(this.maxDate.setFullYear(this.maxDate.getFullYear() - 21));
this.countries = service.getCountries();
this.cities = service.getCities();
this.customer = service.getCustomer();
}
passwordComparison = () => this.form.instance.option('formData').Password;
checkComparison() {
return true;
}
changePasswordMode = (name: string) => {
let editor = this.form.instance.getEditor(name);
editor.option(
'mode',
editor.option('mode') === 'text' ? 'password' : 'text',
);
};
validateVacationDatesRange({ value }) {
const [startDate, endDate] = value;
if (startDate === null || endDate === null) {
return true;
}
const millisecondsPerDay = 24 * 60 * 60 * 1000;
const daysDifference = Math.abs((endDate - startDate) / millisecondsPerDay);
return daysDifference < 25;
}
validateVacationDatesPresence({ value }) {
const [startDate, endDate] = value;
if (startDate === null && endDate === null) {
return true;
}
return startDate !== null && endDate !== null;
}
asyncValidation: AsyncRule['validationCallback'] = ({ value }) => sendRequest(value);
onFormSubmit = (e: SubmitEvent) => {
notify({
message: 'You have submitted the form',
position: {
my: 'center top',
at: 'center top',
},
}, 'success', 3000);
e.preventDefault();
};
onOptionChanged = (e: DxFormTypes.OptionChangedEvent) => {
if (e.name === 'isDirty') {
const resetButton = this.form.instance.getButton('Reset');
resetButton.option('disabled', !e.value);
}
};
}
@NgModule({
imports: [
BrowserModule,
DxCheckBoxModule,
DxSelectBoxModule,
DxNumberBoxModule,
DxButtonModule,
DxAutocompleteModule,
DxFormModule,
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
::ng-deep form {
margin: 10px 10px 15px;
}
::ng-deep .last-group {
margin-top: 30px;
margin-bottom: 10px;
}
::ng-deep .last-group .dx-item-content {
align-items: start;
justify-content: center;
}
::ng-deep .last-group .dx-field-item {
padding: 0 !important;
}
::ng-deep .buttons-group {
display: flex;
width: 100%;
justify-content: end;
}
::ng-deep .buttons-group .dx-item-content {
gap: 10px;
}
import { Injectable } from '@angular/core';
export class Customer {
Email: string;
Password: string;
Name: string;
Date: Date;
VacationDates: [Date, Date];
Country: string;
City: string;
Address: string;
Phone: string;
Accepted: boolean;
}
const customer : Customer = {
Email: '',
Password: '',
Name: 'Peter',
Date: null,
VacationDates: [null, null],
Country: '',
City: null,
Address: '',
Phone: '',
Accepted: false,
};
const countries: string[] = [
'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'The Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burma', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Democratic Republic of the Congo', 'Republic of the Congo', 'Costa Rica', 'Ivory Coast', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'East Timor', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'The Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Republic of Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'North Korea', 'South Korea', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Republic of Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Federated States of Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Namibia', 'Nauru', 'Nepal', 'Kingdom of the Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Norway', 'Oman', 'Pakistan', 'Palau', 'State of Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'São Tomé and Príncipe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Swaziland', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe'];
const cities: string[] = [
'New York', 'Los Angeles', 'Chicago', 'Houston', 'Philadelphia', 'Phoenix', 'San Antonio', 'San Diego', 'Dallas', 'San Jose', 'Austin', 'Indianapolis', 'Jacksonville', 'San Francisco', 'Columbus', 'Charlotte', 'Fort Worth', 'Detroit', 'El Paso', 'Memphis', 'Seattle', 'Denver', 'Washington', 'Boston', 'Nashville', 'Baltimore', 'Oklahoma City', 'Louisville', 'Portland', 'Las Vegas', 'Milwaukee', 'Albuquerque', 'Tucson', 'Fresno', 'Sacramento', 'Long Beach', 'Kansas City', 'Mesa', 'Virginia Beach', 'Atlanta', 'Colorado Springs', 'Omaha', 'Raleigh', 'Miami', 'Oakland', 'Minneapolis', 'Tulsa', 'Cleveland', 'Wichita', 'Arlington', 'New Orleans', 'Bakersfield', 'Tampa', 'Honolulu', 'Aurora', 'Anaheim', 'Santa Ana', 'St. Louis', 'Riverside', 'Corpus Christi', 'Lexington', 'Pittsburgh', 'Anchorage', 'Stockton', 'Cincinnati', 'Saint Paul', 'Toledo', 'Greensboro', 'Newark', 'Plano', 'Henderson', 'Lincoln', 'Buffalo', 'Jersey City', 'Chula Vista', 'Fort Wayne', 'Orlando', 'St. Petersburg', 'Chandler', 'Laredo', 'Norfolk', 'Durham', 'Madison', 'Lubbock', 'Irvine', 'Winston–Salem', 'Glendale', 'Garland', 'Hialeah', 'Reno', 'Chesapeake', 'Gilbert', 'Baton Rouge', 'Irving', 'Scottsdale', 'North Las Vegas', 'Fremont', 'Boise', 'Richmond'];
@Injectable()
export class Service {
getCustomer() : Customer {
return customer;
}
getCountries() {
return countries;
}
getCities() {
return cities;
}
}
// In real applications, you should not transpile code in the browser.
// You can see how to create your own application with Angular and DevExtreme here:
// https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/
const componentNames = [
'accordion',
'action-sheet',
'autocomplete',
'bar-gauge',
'box',
'bullet',
'button-group',
'button',
'calendar',
'chart',
'check-box',
'circular-gauge',
'color-box',
'context-menu',
'data-grid',
'date-box',
'date-range-box',
'defer-rendering',
'diagram',
'draggable',
'drawer',
'drop-down-box',
'drop-down-button',
'file-manager',
'file-uploader',
'filter-builder',
'form',
'funnel',
'gallery',
'gantt',
'html-editor',
'linear-gauge',
'list',
'load-indicator',
'load-panel',
'lookup',
'map',
'menu',
'multi-view',
'nested',
'number-box',
'pie-chart',
'pivot-grid-field-chooser',
'pivot-grid',
'polar-chart',
'popover',
'popup',
'progress-bar',
'radio-group',
'range-selector',
'range-slider',
'recurrence-editor',
'resizable',
'responsive-box',
'sankey',
'scheduler',
'scroll-view',
'select-box',
'slider',
'sortable',
'sparkline',
'speed-dial-action',
'splitter',
'switch',
'tab-panel',
'tabs',
'tag-box',
'text-area',
'text-box',
'tile-view',
'toast',
'toolbar',
'tooltip',
'tree-list',
'tree-map',
'tree-view',
'validation-group',
'validation-summary',
'validator',
'vector-map',
];
window.exports = window.exports || {};
window.config = {
transpiler: 'ts',
typescriptOptions: {
module: 'system',
emitDecoratorMetadata: true,
experimentalDecorators: true,
},
meta: {
'typescript': {
'exports': 'ts',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'@angular/platform-browser-dynamic': {
'esModule': true,
},
'@angular/platform-browser': {
'esModule': true,
},
'@angular/core': {
'esModule': true,
},
'@angular/common': {
'esModule': true,
},
'@angular/common/http': {
'esModule': true,
},
'@angular/compiler': {
'esModule': true,
},
'@angular/animations': {
'esModule': true,
},
'@angular/forms': {
'esModule': true,
},
},
paths: {
'npm:': 'https://unpkg.com/',
'bundles:': '../../../../bundles/',
},
map: {
'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js',
'typescript': 'npm:typescript@4.2.4/lib/typescript.js',
/* @angular */
'@angular/compiler': 'bundles:@angular/compiler.umd.js',
'@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js',
'@angular/core': 'bundles:@angular/core.umd.js',
'@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js',
'@angular/common': 'bundles:@angular/common.umd.js',
'@angular/common/http': 'bundles:@angular/common-http.umd.js',
'@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js',
'@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js',
'@angular/forms': 'bundles:@angular/forms.umd.js',
/* devextreme */
'devextreme': 'npm:devextreme@24.1.7/cjs',
'@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13',
'devextreme/bundles/dx.all': 'npm:devextreme@24.1.7/bundles/dx.all.js',
'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.12',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.56',
/* devextreme-angular umd maps */
'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js',
'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js',
'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js',
...componentNames.reduce((acc, name) => {
acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`;
return acc;
}, {}),
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'tslib': 'npm:tslib@2.6.1/tslib.js',
'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js',
'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign@1.1.0',
'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'app': {
main: './app.component.ts',
defaultExtension: 'ts',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
'rxjs': {
defaultExtension: 'js',
},
'rxjs/operators': {
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:@devextreme/runtime@3.0.13/inferno/package.json',
'npm:rxjs@7.5.3/package.json',
'npm:rxjs@7.5.3/operators/package.json',
'npm:devexpress-diagram@2.2.12/package.json',
'npm:devexpress-gantt@4.1.56/package.json',
],
};
System.config(window.config);
// System.import('@angular/compiler').catch(console.error.bind(console));
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.7/css/dx.light.css" />
<script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.13.3/bundles/zone.umd.min.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.13/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import("app").catch(console.error.bind(console));
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<demo-app>Loading...</demo-app>
</div>
</body>
</html>