<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <title>devextreme-angular playground</title>
    <link rel="stylesheet" href="https://unpkg.com/devextreme@17.1/dist/css/dx.common.css" />
    <link rel="stylesheet" href="https://unpkg.com/devextreme@17.1/dist/css/dx.light.css" />
    <link rel="stylesheet" href="style.css" />
    <script src="https://unpkg.com/zone.js@0.6.25/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js@0.6.25/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="https://unpkg.com/typescript@2.0.10/lib/typescript.js"></script>
    <script src="config.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
    <my-app>
    loading...
  </my-app>
  </body>

</html>
/* Styles go here */

### DevExtreme Angular simple plunker

https://github.com/DevExpress/devextreme-angular
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  map: {
    app: "./src",
    
    '@angular/core': 'npm:@angular/core@2.4.3/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common@2.4.3/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler@2.4.3/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser@2.4.3/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.4.3/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http@2.4.3/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router@3.4.3/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms@2.4.3/bundles/forms.umd.js',
    
    'rxjs': 'npm:rxjs@5.0.3',
    
    'devextreme': 'npm:devextreme@17.1',
    'jquery': 'npm:jquery@3.1.1/dist/jquery.min.js',
    'jszip': 'npm:jszip@3.1.3/dist/jszip.min.js',
    'devextreme-angular': 'npm:devextreme-angular@17.1'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    },
    'devextreme': {
      defaultExtension: 'js'
    },
    'devextreme-angular': {
      main: 'index.js',
      defaultExtension: 'js'
    }
  }
});
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {App} from './app';
import {DevExtremeModule} from 'devextreme-angular';
import {MultilangComponent} from './multilang.component';

@NgModule({
  imports: [
      BrowserModule,
      DevExtremeModule
  ],
  declarations: [
    App,
    MultilangComponent
  ],
  bootstrap: [ App ]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule)
import {Component} from '@angular/core';
import {QUESTIONS} from './question.data';

@Component({
  selector: 'my-app',
  templateUrl: './src/app.html'
})
export class App {
  questions: Question[] = [];
  nextQuestions: Question[] = [];
  choices: Choice[] = [];
  selectedIndex: number = -1;
  columns: object[] = [];
  editing: object = {};
  switchQuestion = false;
  timer = null;
  messages: object = {};

  constructor() { }

  ngOnInit() {
    this.loadQuestion();
    this.messages = {
      removeQuestion: 'removeQuestion',
      question: 'question',
      newTitle: 'newTitle',
      newQuestion: 'newQuestion'
    };
  }

  public onRowSelected(e) {
    this.switchQuestion = true;
    this.selectedIndex = e.itemIndex;

    this.setNextQuestion();
    this.loadChoice();
  }

  public onFormUpdated(item, e) {
    if (!this.switchQuestion) {
      if (this.timer) {
        clearTimeout(this.timer);
      }
      if (item !== 'form') {
        this.questions[this.selectedIndex][item] = e.value;
      }

      this.timer = setTimeout(() => {
        console.log('update by service');
      }, 1000);
    }
  }

  public onRemoveQuestion() {
    if (this.selectedIndex > -1) {
      confirm(this.messages['removeQuestion'], this.messages['question']).done((result) => {
        if (result) {
          console.log('remove by service');
        }
      });
    }
  }

  public onInitNewChoice(e) {
    e.data.isActive = true;
  }

  public onChoiceInserted(e) {
    this.choiceService.create(e.data).subscribe(info => {
      if (info.success) {
        this.loadChoice();
      }
    });
  }

  public onChoiceUpdated(e) {
    e.data.idSurveyChoice = e.key.idSurveyChoice;

    this.choiceService.update(e.data).subscribe();
  }

  public onChoiceRemoved(e) {
    this.choiceService.remove(e.key.idSurveyChoice).subscribe();
  }

  private setNextQuestion() {
    this.nextQuestions = [];

    this.questions.forEach((question) => {
      if (question.idSurveyQuestion !== this.questions[this.selectedIndex].idSurveyQuestion) {
        this.nextQuestions.push(question);
      }
    });
  }

  private loadChoice() {
      this.choices = [];
      this.switchQuestion = false;
  }

  private loadQuestion() {
    this.questions = QUESTIONS;
  }
}
import {Injectable} from '@angular/core';

export const QUESTIONS = [
  {"description": {"idLanguageContent": "8c8517bd-8bcd-4ea7-859d-efe25efa371f", "en": "Question 1", "fr": "Question 1"}, "id_survey_question_next": null, "sequence": 0, "createdOn": "2017/04/06 16:18:14", "idLanguageContentDescription": "8c8517bd-8bcd-4ea7-859d-efe25efa371f", "title": {"idLanguageContent": "333dcbf5-2c77-4b65-aff6-6b1990a67f6c", "en": "Section 1", "fr": "Section 1"}, "idSurveyQuestion": "64d876ae-1435-4b4c-9676-1bbefd422619", "questionType": "text", "idLanguageContentTitle": "333dcbf5-2c77-4b65-aff6-6b1990a67f6c", "idSurvey": "dc2aebde-82f9-40ac-b389-6c6116acc7c5", "isActive": true},
  {"description": {"idLanguageContent": "dc4ab409-ed9f-416c-bf29-19590cfaf215", "en": "Question 2", "fr": "Question 2"}, "id_survey_question_next": null, "sequence": 1, "createdOn": "2017/04/06 16:18:53", "idLanguageContentDescription": "dc4ab409-ed9f-416c-bf29-19590cfaf215", "title": {"idLanguageContent": "9a2a8426-fd10-47dd-9635-d28cd6c851f9", "en": "Section 1", "fr": "Section 1"}, "idSurveyQuestion": "903b1b44-eb03-4335-901d-04b7c3c576df", "questionType": "date", "idLanguageContentTitle": "9a2a8426-fd10-47dd-9635-d28cd6c851f9", "idSurvey": "dc2aebde-82f9-40ac-b389-6c6116acc7c5", "isActive": true},
  {"description": {"idLanguageContent": "4131ed7b-f756-4f52-8980-c50a5d1b2c9c", "en": "Question 3", "fr": "Question 3"}, "id_survey_question_next": null, "sequence": 2, "createdOn": "2017/04/06 16:19:10", "idLanguageContentDescription": "4131ed7b-f756-4f52-8980-c50a5d1b2c9c", "title": {"idLanguageContent": "5eb513bb-4ee4-4472-825b-dab7c2dc9f14", "en": "Section 2", "fr": "Section 2"}, "idSurveyQuestion": "f0ab84f0-e216-49c4-a714-c31420c68abf", "questionType": "choice", "idLanguageContentTitle": "5eb513bb-4ee4-4472-825b-dab7c2dc9f14", "idSurvey": "dc2aebde-82f9-40ac-b389-6c6116acc7c5", "isActive": true},
  {"description": {"idLanguageContent": "36a27daa-8b9e-485e-bd05-fe853ca67dc9", "en": "Question 4", "fr": "Question 4"}, "id_survey_question_next": null, "sequence": 3, "createdOn": "2017/06/13 10:17:07", "idLanguageContentDescription": "36a27daa-8b9e-485e-bd05-fe853ca67dc9", "title": {"idLanguageContent": "f5d9bea3-8ac8-47bf-b269-2e01cffb567e", "en": "Section 2", "fr": "Section 2"}, "idSurveyQuestion": "0526d79d-fcc7-4d1e-98e3-041eb00adf6d", "questionType": "text", "idLanguageContentTitle": "f5d9bea3-8ac8-47bf-b269-2e01cffb567e", "idSurvey": "dc2aebde-82f9-40ac-b389-6c6116acc7c5", "isActive": true}]}
];
<div fxLayout="row">
  <div fxFlex="35%">
    <div class="content">
      <dx-tree-view
              [dataSource]="questions"
              displayExpr="description.fr"
              keyExpr="idSurveyQuestion"
              selectionMode="single"
              [selectByClick]="true"
              (onItemSelectionChanged)="onRowSelected($event)">
      </dx-tree-view>
    </div>
  </div>
  <div fxFlex="65%">
    <div *ngIf="selectedIndex > -1">
      <dx-form [formData]="questions[selectedIndex]" (onFieldDataChanged)="onFormUpdated('form',$event)">
        <dxi-item [label]="{text: 'section'}" dataField="title">
          <app-multilang [value]="questions[selectedIndex].title" (onValueChanged)="onFormUpdated('title',$event);"></app-multilang>
        </dxi-item>
        <dxi-item [label]="{text: 'description'}" dataField="description">
          <app-multilang [value]="questions[selectedIndex].description" (onValueChanged)="onFormUpdated('description',$event);"></app-multilang></dxi-item>
        <dxi-item
                [label]="{text: 'questionType'}"
                dataField="questionType"
                editorType="dxSelectBox"
                [editorOptions]="{dataSource: [
                    {value: 'choice', text: 'choiceAnswer'},
                    {value: 'text', text: 'textAnswer'},
                    {value: 'date', text: 'dateAnswer'}
                  ], displayExpr: 'text', valueExpr: 'value'}">
        </dxi-item>
        <dxi-item
                [label]="{text: 'nextQuestion'}"
                dataField="idSurveyQuestionNext"
                editorType="dxSelectBox"
                [editorOptions]="{dataSource: nextQuestions, displayExpr: 'description.fr', valueExpr: 'idSurveyQuestion'}">
        </dxi-item>
      </dx-form>
      <label>{{'options'}}</label>
      <dx-data-grid
              [dataSource]="choices"
              [hoverStateEnabled]="true"
              (onInitNewRow)="onInitNewChoice($event)"
              (onRowInserted)="onChoiceInserted($event)"
              (onRowUpdated)="onChoiceUpdated($event)"
              (onRowRemoved)="onChoiceRemoved($event)">
        <dxi-column
                dataField="sequence"
                caption="{{'sequence'}}"
                width="15%"
                sortOrder="asc"
                sortIndex="0">
        </dxi-column>
        <dxi-column
                dataField="name"
                caption="{{'name'}}"
                [calculateCellValue]="onCalculateCellValue"
                editCellTemplate="editname">
        </dxi-column>
        <dxi-column
                dataField="idSurveyQuestionNext"
                caption="{{'surveyQuestionNext'}}">
          <dxo-lookup [dataSource]="nextQuestions" displayExpr="description.fr" valueExpr="idSurveyQuestion">
          </dxo-lookup>
        </dxi-column>
        <dxi-column dataField="isActive" caption="{{'isActive'}}" width="10%"></dxi-column>

        <dxo-editing mode="row" [allowUpdating]="true" [allowAdding]="true" [allowDeleting]="true">
        </dxo-editing>
        <div *dxTemplate="let rowname of 'editname'">
          <app-multilang [value]="rowname.row.data.name" (onValueChanged)="rowname.setValue($event.value);"></app-multilang>
        </div>
      </dx-data-grid>
    </div>
  </div>
</div>
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';

@Component({
  selector: 'app-multilang',
  templateUrl: './src/multilang.component.html'
})
export class MultilangComponent implements OnInit {
  @Input()
  get value(): object { return this._value; }
  set value(val: object) {
    ['fr', 'en'].forEach((lang) => {
      this._value[lang] = (val && val[lang] ? val[lang] : '');
    });

    this._value['idLanguageContent'] = (val && val['idLanguageContent'] ? val['idLanguageContent'] : '');
    this.selectedTabValue = this._value[this.selectedTab];
  }
  private _value: object = {
    'idLanguageContent': ''
  };

  public languages: string[] = [];
  public selectedTab: string;
  public selectedTabValue: string;

  @Output() onValueChanged = new EventEmitter();

  constructor() {
    const labels = {'fr': 'Francais', 'en': 'Anglais'};
    ['fr', 'en'].forEach((lang) => {
      this._value[lang] = '';
    });

    ['fr', 'en'].forEach((lang) => {
      this.languages.push(labels[lang]);
    });
  }

  ngOnInit() {
    this.selectedTab = ['fr', 'en'][0];
    this.selectedTabValue = this._value[this.selectedTab];
  }

  public onTabValueChanged(e) {
    const oldValue = Object.assign({}, this.value);

    this._value[this.selectedTab] = e.element.find('input').val();
    this.onValueChanged.emit({
      value: this.value,
      oldValue: oldValue
    });
  }

  public onTabChanged(e) {
    this.selectedTab = environment.languages[e.component.option('selectedIndex')];
    this.selectedTabValue = this._value[this.selectedTab];
  }
}
<dx-tab-panel
        [items]="tabs"
        [selectedIndex]="0"
        (onSelectionChanged)="onTabChanged($event)">
  <ng-container *ngFor="let lang of languages; let i = index">
    <dxi-item [title]="lang">
      <dx-text-box (onKeyUp)="onTabValueChanged($event)" [value]="selectedTabValue" [placeholder]="lang"></dx-text-box>
    </dxi-item>
  </ng-container>
</dx-tab-panel>