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 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, which will be further processed by the component. For more information, refer to the following example.

@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 more 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, Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { DataService } from './data.service';
import { Product } from '../common/product.model'
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpProgressEvent, HttpEventType, HttpClient } from '@angular/common/http';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/delay';
import 'rxjs/add/observable/concat';

@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: HttpClient,
        @Inject(DataService) private dataService: DataService
    ) {
    }

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

@Injectable()
export class ComboBoxInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        /*
          Mocked backend service.
          *Do NOT use in production environment*
        */
        if (req.url === "normalize/url") {
            const download = Observable.of(<HttpProgressEvent>{
                type: HttpEventType.DownloadProgress,
                loaded: 50,
                total: 100
            }).delay(1000);

            const success = Observable.of(new HttpResponse({
                body: {
                    ProductId: Math.floor(Math.random() * 20) + 4 ,
                    ProductName: req.body.text
                },
                status: 200
            }));

            return Observable.concat(download, success);
        }

        return next.handle(req);
    }
}
import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule, HTTP_INTERCEPTORS, HttpClientJsonpModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { AppComponent, ComboBoxInterceptor }   from './app.component';
import { DataService }   from './data.service';

@NgModule({
  imports:      [ BrowserModule, HttpClientModule, HttpClientJsonpModule, DropDownsModule, FormsModule, BrowserAnimationsModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [
    DataService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ComboBoxInterceptor,
      multi: true
    }
  ]
})

export class AppModule { }

enableProdMode();

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

@Injectable()

export class DataService {
  constructor(private http: HttpClient) { }

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

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

Handling Errors

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

import { Component, Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpProgressEvent, HttpEventType, HttpClient, HttpErrorResponse } from '@angular/common/http';
import { DataService } from './data.service';
import { Product } from '../common/product.model'
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';
import 'rxjs/add/observable/concat';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Observer } from 'rxjs/Observer';

@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: HttpClient,
        @Inject(DataService) private dataService: DataService
    ) {
    }

    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: any, caught: Observable<object>) => {
            this.hasError = true;
            this.errorMessage = response.error;

            return Observable.throw(response.error);
        });
}

@Injectable()
export class ComboBoxInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        /*
          Mocked backend service.
          *Do NOT use in production environment*
        */
        if (req.url === "normalize/url") {
            const download = Observable.of(<HttpProgressEvent>{
                type: HttpEventType.DownloadProgress,
                loaded: 50,
                total: 100
            }).delay(1000);

            const error = new Observable<HttpEvent<any>>((observer: Observer<HttpEvent<any>>) => {
                observer.error(new HttpErrorResponse({ error: "Error occured", status: 500 }));
            });

            return Observable.concat(download, error);
        }

        return next.handle(req);
    }
}
import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule, HTTP_INTERCEPTORS, HttpClientJsonpModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { AppComponent, ComboBoxInterceptor }   from './app.component';
import { DataService }   from './data.service';

@NgModule({
  imports:      [ BrowserModule, HttpClientModule, HttpClientJsonpModule, DropDownsModule, FormsModule, BrowserAnimationsModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [
    DataService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ComboBoxInterceptor,
      multi: true
    }
  ]
})

export class AppModule { }

enableProdMode();

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

@Injectable()

export class DataService {
  constructor(private http: HttpClient) { }

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

  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