All Components

Custom Values

By default, when the Enter key is pressed or after the component loses focus, values that do not appear in the supplied list of items are dismissed.

To configure the ComboBox to accept custom values, set the allowCustom property to true.

Primitive Values

If the component is bound to a primitive value, you only have to set the allowCustom property to true.

This scenario is valid when the component is either bound to a dataset of primitive values or to a dataset of objects, and the valuePrimitive property is set to true.

The following example demonstrates how to allow custom primitive values.

@Component({
  selector: 'my-app',
  template: `
    <p>Custom values are <strong>enabled</strong>. Type a custom value.</p>

    <p>primitive data</p>
    <div class="example-wrapper">
        <kendo-combobox
            [data]="sizes"
            [value]="selectedSize"
            [allowCustom]="true"
            (valueChange)="onSizeChange($event)"
        >
        </kendo-combobox>
    </div>
  `
})
class AppComponent {
    public sizes: Array<string> = [ "Small", "Medium", "Large" ];
    public selectedSize: string = "Medium";

    public onSizeChange(value) {
        this.selectedSize = value;
    }
}

Object Values

If the component is bound to objects, you have to set the allowCustom property to true and to provide a valueNormalizer function. Its purpose is to convert the user text input into an object that can be recognized as a valid ComboBox value.

The valueNormalizer function receives Observable<string> as an argument and is expected to return a single normalized value wrapped as Observable that will be further processed by the component. For more information check the comments in the examples below.

@Component({
  selector: 'my-app',
  template: `
    <p>Custom values are <strong>enabled</strong>. Type a custom value.</p>
    <p>ComboBox value: {{ size|json }}</p>

    <kendo-combobox
        [allowCustom]="true"
        [data]="listItems"
        [textField]="'text'"
        [valueField]="'value'"
        [valueNormalizer]="valueNormalizer"
        [(ngModel)]="size"
    >
    </kendo-combobox>
  `
})

class AppComponent {
    public listItems: Array<{ text: string, value: number }> = [
        { text: "Small", value: 1 },
        { text: "Medium", value: 2 },
        { text: "Large", value: 3 }
    ];

    public size: { text: string, value: number } = { text: "Medium", value: 2 };

    /*
        The component will emit custom text typed by the user and pass it to the `valueNormalizer` method.
        You can process the custom text, create a single custom object that represents the normalized value
        and pass it back wrapped as an Observable.

        This example uses the `map` operator to transform text into a normalized value object.

        For further information on the `map` operator, refer to
        http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-map
    */
    public valueNormalizer = (text: Observable<string>) => text.map((text: string) => {
        return {
            value: this.listItems[this.listItems.length - 1].value + 1,
            text: text
        };
    });
}

Remote Service

The following example demonstrates how to handle custom object values through a remote service.

import { Component, Inject } from '@angular/core';
import { Response, ResponseOptions, Http } from '@angular/http';
import { MockBackend } from '@angular/http/testing';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import { DataService } from './data.service';
import { Product } from '../common/product.model'

@Component({
  selector: 'my-app',
  template: `
            <p>Type custom value and press enter.</p>
            <p>Value: {{item | json }}</p>
            <kendo-combobox
                [data]="listItems"
                [textField]="'ProductName'"
                [valueField]="'ProductId'"
                [(ngModel)]="item"
                [valueNormalizer]="valueNormalizer"
                [allowCustom]="true"
            >
            </kendo-combobox>`
})
export class AppComponent {
    public listItems: Product[] = [];
    public item: Product = { ProductID: 1, ProductName: 'Chai' };

    constructor(
        public http: Http,
        private backend: MockBackend,
        @Inject(DataService) private dataService: DataService
    ) {
        /*
          Mocked backend service.
          For further details check:
          https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html

          *Do NOT use in production environment*
        */
        this.backend.connections.subscribe((c: any) => {
            let response = new Response(<ResponseOptions>{ status: 200 });

            if (c.request.url === "normalize/url") {
                c.mockDownload(response);
                setTimeout(() => {
                    c.mockRespond({
                        ProductId: Math.floor(Math.random() * 20) + 4 ,
                        ProductName: JSON.parse(c.request.getBody()).text
                    });
                }, 1500);
            }
        });
    }

    ngOnInit() {
        /*
            Fetch ComboBox data from remote service.
        */
        this.dataService.fetchData().subscribe(data => this.listItems = data)
    }

    /*
        Maps each custom text that the user types to the result of a function that performs http request to remove service.
        For further details about `switchMap` operator check:
        http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap
    */
    public valueNormalizer =  (text: Observable<string>): any => text.switchMap(this.service);

    /*
        Send the custom text to remote service that will process it and send back a generated data item with `ProductID` and `ProductName` fields.
        Note that the response should contain *single item* that represents the normalized value.
        The response value will be returned by the `valueNormalizer` function to the ComboBox.
        If you want to modify the server response before it is returned by the valueNormalizer to the ComboBox component, use the `map` operator:
        http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-map

        For further details how to perform a http request check:
        https://angular.io/docs/ts/latest/guide/server-communication.html

        *IMPORTANT* If the request fails due to some reason, the component will clear the custom text and will reset its value.
        If you want to notify the user for the error use catch operator.

        For further details how to handle http errors check:
        https://angular.io/docs/ts/latest/guide/server-communication.html#!#always-handle-errors
    */
    public service = (text: string): any => this.http.post('normalize/url', { text })
}
import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Http, HttpModule, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions, ResponseType, JsonpModule } from '@angular/http';
import { MockBackend } from '@angular/http/testing';
import { FormsModule } from '@angular/forms';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { AppComponent }   from './app.component';
import { DataService }   from './data.service';

@NgModule({
  imports:      [ BrowserModule, HttpModule, DropDownsModule, FormsModule, JsonpModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [BaseRequestOptions, DataService, MockBackend, {
        provide: Http,
        deps: [MockBackend, BaseRequestOptions],
        useFactory: (backend, options) => {
            return new Http(backend, options);
        }
  }]
})

export class AppModule { }

enableProdMode();

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
import { Injectable, Inject } from '@angular/core';
import { Jsonp, JsonpModule } from '@angular/http';
import { Product } from './product.model';
import { Observable } from 'rxjs/Rx';

@Injectable()

export class DataService {


  constructor(@Inject(Jsonp) private jsonp: Jsonp) { }

  fetchData(action: string = "", data?: Product): Observable<Product[]>{
    return this.jsonp.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}?callback=JSONP_CALLBACK${this.serializeModels(data)}`).map(response => response.json())
  }

  private serializeModels(data?: Product): string {
    return data ? `&models=${JSON.stringify([data])}` : '';
  }

}
export class Product {
    constructor(
        public ProductID?: number,
        public ProductName?: string,
        public UnitPrice?: number,
        public UnitsInStock?: number,
        public Discontinued?: boolean
    ) { }
}

Errors from Normalizing Custom Object Values

The following example demonstrates how to handle errors that occur while normalizing custom object values through a remote service.

import { Component, Inject } from '@angular/core';
import { Response, ResponseOptions, Http } from '@angular/http';
import { MockBackend } from '@angular/http/testing';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
import { DataService } from './data.service';
import { Product } from '../common/product.model'

@Component({
  selector: 'my-app',
  template: `
            <p>This example demonstrates how to handle server errors that occur in the normalization service.</p>
            <p>Type a custom value and press Enter.</p>
            <p>Value: {{item | json }}</p>
            <kendo-combobox
                [data]="listItems"
                [textField]="'ProductName'"
                [valueField]="'ProductId'"
                [(ngModel)]="item"
                [valueNormalizer]="valueNormalizer"
                [allowCustom]="true"
            >
            </kendo-combobox>
            <span *ngIf="hasError" style="color: red;">{{ errorMessage }}</span>
  `
})
export class AppComponent {
    public listItems: Product[] = [];
    public item: Product = { ProductID: 1, ProductName: 'Chai' };
    public hasError: boolean = false;
    public errorMessage: string = "";

    constructor(
        public http: Http,
        private backend: MockBackend,
        @Inject(DataService) private dataService: DataService
    ) {
        /*
          Mocked backend service.
          For further details, refer to
          https://angular.io/docs/ts/latest/api/http/testing/index/MockBackend-class.html.

          *Do NOT use in production environment!*
        */
        this.backend.connections.subscribe((c: any) => {
            let response = new Response(<ResponseOptions>{ status: 500 });

            if (c.request.url === "normalize/url") {
                c.mockDownload(response);
                setTimeout(() => {
                    c.mockError({ error: "Error occured" });
                }, 1500);
            }
        });
    }

    ngOnInit() {
        /*
            Fetches the ComboBox data from the remote service.
        */
        this.dataService.fetchData().subscribe(data => this.listItems = data)
    }

    /*
        Maps each custom text that the user types to the result of a function that performs an http request to remove a service.
        For further details on the `switchMap` operator, refer to
        http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap.
    */
    public valueNormalizer =  (text: Observable<string>): any => text.switchMap(this.service);

    /*
        Sends the custom text to the remote service that will process it and sends back a generated data item with the `ProductID` and `ProductName` fields.
        Note that the response should contain a *single item* that represents the normalized value.
        The response value will be returned by the `valueNormalizer` function to the ComboBox.
        If you want to modify the server response before it is returned by the valueNormalizer to the ComboBox component, use the `map` operator:
        http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-map.

        For further details on how to perform an http request, refer to
        https://angular.io/docs/ts/latest/guide/server-communication.html.

        *IMPORTANT* If the request fails due to some reason, the component will clear the custom text and reset its value.
        If you want to notify the user about the error, use the catch operator.

        For further details on how to handle http errors, refer to
        https://angular.io/docs/ts/latest/guide/server-communication.html#!#always-handle-errors
    */
    public service = (text: string): any => this.http
        .post('normalize/url', { text })
        .catch((response) => {
            this.hasError = true;
            this.errorMessage = response.error;
        });
}
import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Http, HttpModule, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions, ResponseType, JsonpModule } from '@angular/http';
import { MockBackend } from '@angular/http/testing';
import { FormsModule } from '@angular/forms';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { AppComponent }   from './app.component';
import { DataService }   from './data.service';

@NgModule({
  imports:      [ BrowserModule, HttpModule, DropDownsModule, FormsModule, JsonpModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [BaseRequestOptions, DataService, MockBackend, {
        provide: Http,
        deps: [MockBackend, BaseRequestOptions],
        useFactory: (backend, options) => {
            return new Http(backend, options);
        }
  }]
})

export class AppModule { }

enableProdMode();

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
import { Injectable, Inject } from '@angular/core';
import { Jsonp, JsonpModule } from '@angular/http';
import { Product } from './product.model';
import { Observable } from 'rxjs/Rx';

@Injectable()

export class DataService {


  constructor(@Inject(Jsonp) private jsonp: Jsonp) { }

  fetchData(action: string = "", data?: Product): Observable<Product[]>{
    return this.jsonp.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}?callback=JSONP_CALLBACK${this.serializeModels(data)}`).map(response => response.json())
  }

  private serializeModels(data?: Product): string {
    return data ? `&models=${JSON.stringify([data])}` : '';
  }

}
export class Product {
    constructor(
        public ProductID?: number,
        public ProductName?: string,
        public UnitPrice?: number,
        public UnitsInStock?: number,
        public Discontinued?: boolean
    ) { }
}
In this article