---
title: Single- and Multi-Select Questions with Custom Item Template
product: Form Library
description: Make your single- or multi-select question stand out with custom choice options. Implement a custom component that renders item markup to style question items. Refer to this free demo for JavaScript to learn more.
framework: Angular
source: https://surveyjs.io/form-library/examples/add-custom-items-to-single-and-multi-select-questions/angular
index: https://surveyjs.io/form-library/examples/overview.md
---

# Single- and Multi-Select Questions with Custom Item Template (Angular)

SurveyJS Form Library supports the usage of custom templates to change the appearance of choice options in Checkboxes, Radio Button Group, and other single- or multi-select question types. This demo shows how to customize choice options using a custom template component. 

To create and apply a custom option template, follow the steps below:

1. Implement a component that renders option markup.          
    The component accepts the option configuration object as a prop. You can use its `value` and `text` properties to access the option's value to be saved in survey results and display text. 

2. Register the component so that it can be accessed by name.         
    In HTML/CSS/JavaScript projects, register the component in `ReactElementFactory` as shown in the `index.js` file.           
    In React, register the component in `ReactElementFactory` as shown in the `SurveyComponent.jsx` file.          
    In Angular, register the component in `AngularComponentFactory` as shown in the `custom-choice-item.component.ts` file.          
    In Vue.js, use [techniques native to this framework](https://vuejs.org/guide/components/registration).

3. Assign the component name to the question's [`itemComponent`](https://surveyjs.io/form-library/documentation/api-reference/questionselectbase#itemComponent) property in the survey JSON schema.

## Files

### `src/app/components/json.ts`

```ts
export const json = {
  "elements": [
    {
      "type": "radiogroup",
      "name": "singleselect",
      "title": "Single-select question with a custom item template",
      "itemComponent": "custom-radio-item",
      "showOtherItem": true,
      "choices": [ "Item 1", "Item 2", "Item 3" ]
    },
    {
      "type": "checkbox",
      "name": "multiselect",
      "title": "Multi-select question with a custom item template",
      "itemComponent": "custom-checkbox-item",
      "showOtherItem": true,
      "showSelectAllItem": true,
      "choices": [ "Item 1", "Item 2", "Item 3" ]
    }
  ]
};
```

### `src/app/components/survey.component.css`

```css
.item-input {
    display: none;
}

.item-root {
    margin-bottom: 8px;
}

.item-svg {
    padding: 4px;
    width: 32px;
    height: 32px;
}

.item-image {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.item-text {
    height: 24px;
    font-family: 'Open Sans';
    font-style: normal;
    font-weight: 400;
    font-size: 16px;
    line-height: 24px;
    color: #161616;
    user-select: none;
}

.item-label {
    box-sizing: border-box;
    display: flex;
    flex-direction: row;
    align-items: center;
    padding: 4px 32px 4px 16px;
    gap: 12px;
    height: 48px;
    width: fit-content;
    background: rgba(255, 152, 20, 0.1);
    border: 1px solid #ff9814;
    border-radius: 100px;
    margin-bottom: 12px;
}

.item-label.item-label--selected {
    background: rgba(25, 179, 148, 0.1);
    border: 1px solid #19b394;
    border-radius: 100px;
}

.question-note {
    white-space: initial;
}
```

### `src/app/components/survey.component.html`

```html
<survey [model]="model" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; height:100%"></survey>
```

### `src/app/components/custom-checkbox-item.component.html`

```html
<div role="presentation" class="item-root">
    <label [class]="getItemLabelClassName()" (mousedown)="question.onMouseDown()">
        <input role="option"
               [type]="getOptionType()"
               class="item-input"
               [name]="question.name"
               [id]="question.getItemId(model)"
               (change)="onChange($event)"
               [checked]="question.isItemSelected(model)"
               [value]="model.value" />
        <span class="item-image">
            <svg class="item-svg">
                <use [attr.xlink:href]="getIconId()"></use>
            </svg>
        </span>
        <span [class]="question.getControlLabelClass(model)">
            <sv-ng-string [model]="model.locText"></sv-ng-string>
        </span>
    </label>

    <sv-ng-panel *ngIf="model.isPanelShowing"
                 [model]="model.panel">
    </sv-ng-panel>

    <div *ngIf="model.isCommentShowing"
         [class]="question.getCommentAreaCss(true)"
         [question]="question"
         [item]="model"
         sv-ng-comment-choice>
    </div>
</div>
```

### `src/app/components/custom-checkbox-item.component.ts`

```ts
import { Component, Input } from "@angular/core";
import { AngularComponentFactory } from "survey-angular-ui";

@Component({
    selector: "sv-ng-custom-checkbox-item",
    templateUrl: "./custom-checkbox-item.component.html"
})
export class CustomCheckboxItemComponent {
    @Input() question: any;
    @Input() model: any;
    constructor() { }

    public get item() {
        return this.model;
    }

    public getItemLabelClassName() {
        return ("item-label" + (this.question.isItemSelected(this.item) ? " item-label--selected" : ""));
    }

    public getIconId() {
        return this.question.isItemSelected(this.item) ? "#icon-plus" : "#icon-minus";
    }

    public getOptionType() {
        return "checkbox";
    }
    
    onChange(event: any) {
        this.question.clickItemHandler(this.item, event.currentTarget.checked);
    }
}

AngularComponentFactory.Instance.registerComponent("custom-checkbox-item", CustomCheckboxItemComponent);
```

### `src/app/components/custom-radio-item.component.html`

```html
<div role="presentation" class="item-root">
    <label [class]="getItemLabelClassName()" (mousedown)="question.onMouseDown()">
        <input role="option"
               [type]="getOptionType()"
               class="item-input"
               [name]="question.name"
               [id]="question.getItemId(model)"
               (change)="onChange($event)"
               [checked]="question.isItemSelected(model)"
               [value]="model.value" />
        <span class="item-image">
            <svg class="item-svg">
                <use [attr.xlink:href]="getIconId()"></use>
            </svg>
        </span>
        <span [class]="question.getControlLabelClass(model)">
            <sv-ng-string [model]="model.locText"></sv-ng-string>
        </span>
    </label>

    <sv-ng-panel *ngIf="model.isPanelShowing"
                 [model]="model.panel">
    </sv-ng-panel>

    <div *ngIf="model.isCommentShowing"
         [class]="question.getCommentAreaCss(true)"
         [question]="question"
         [item]="model"
         sv-ng-comment-choice>
    </div>
</div>
```

### `src/app/components/custom-radio-item.component.ts`

```ts
import { Component, Input } from "@angular/core";
import { AngularComponentFactory } from "survey-angular-ui";

@Component({
    selector: "sv-ng-custom-radio-item",
    templateUrl: "./custom-radio-item.component.html"
})
export class CustomRadioItemComponent {
    @Input() question: any;
    @Input() model: any;
    constructor() { }

    public get item() {
        return this.model;
    }

    public getItemLabelClassName() {
        return ("item-label" + (this.question.isItemSelected(this.item) ? " item-label--selected" : ""));
    }

    public getIconId() {
        return this.question.isItemSelected(this.item) ? "#icon-plus" : "#icon-minus";
    }

    public getOptionType() {
        return "radio";
    }
    
    onChange(event: any) {
        this.question.clickItemHandler(this.item, event.currentTarget.checked);
    }
}

AngularComponentFactory.Instance.registerComponent("custom-radio-item", CustomRadioItemComponent);
```

### `src/app/components/survey.component.ts`

```ts
import { Component, OnInit } from "@angular/core";
import { Model } from "survey-core";
import { json } from "./json";
import "./survey.component.css";
import "survey-core/survey-core.min.css";
import { SvgRegistry } from "survey-core";

const svgPlus = '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M30 16H17V3H16V16H3V17H16V30H17V17H30V16Z" fill="#19B394"/></svg>';
const svgMinus = '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M30 16H3V17H30V16Z" fill="#FF9814"/></svg>';
SvgRegistry.registerIcon("icon-plus", svgPlus);
SvgRegistry.registerIcon("icon-minus", svgMinus);
@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey",
    templateUrl: "./survey.component.html",
    styleUrls: ["./survey.component.css"]
})
export class SurveyComponent implements OnInit {
    model: Model;
    ngOnInit() {
        const survey = new Model(json);
        survey.onComplete.add((sender, options) => {
            console.log(JSON.stringify(sender.data, null, 3));
        });
        this.model = survey;
    }
}
```

### `src/app/app.component.css`

```css

```

### `src/app/app.component.html`

```html
<component-survey></component-survey>
```

### `src/app/app.component.ts`

```ts
import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"]
})
export class AppComponent {
    title = "CodeSandbox";
}
```

### `src/app/app.module.ts`

```ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppComponent } from "./app.component";
import { SurveyModule } from "survey-angular-ui";
import { SurveyComponent } from "./components/survey.component";
import { CustomRadioItemComponent } from "./components/custom-radio-item.component";
import { CustomCheckboxItemComponent } from "./components/custom-checkbox-item.component";

@NgModule({
    declarations: [AppComponent, SurveyComponent, CustomRadioItemComponent, CustomCheckboxItemComponent],
    imports: [BrowserModule, SurveyModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

### `src/environments/environment.prod.ts`

```ts
export const environment = {
    production: true
};
```

### `src/environments/environment.ts`

```ts
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
    production: false
};
```

### `src/index.html`

```html
<app-root></app-root>
```

### `src/main.ts`

```ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.log(err));
```

### `src/polyfills.ts`

```ts
/**
 * This file includes polyfills needed by Angular and is loaded before the app.
 * You can add your own extra polyfills to this file.
 *
 * This file is divided into 2 sections:
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
 *      file.
 *
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
 * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
 * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
 *
 * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 */

/***************************************************************************************************
 * BROWSER POLYFILLS
 */

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js';  // Run `npm install classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import "core-js/proposals/reflect-metadata";

/**
 * Required to support Web Animations `@angular/platform-browser/animations`.
 * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
 **/
// import 'web-animations-js';  // Run `npm install web-animations-js`.

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import "zone.js/dist/zone"; // Included with Angular CLI.

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
```

### `src/styles.css`

```css
.item-input {
    display: none;
}

.item-root {
    margin-bottom: 8px;
}

.item-svg {
    padding: 4px;
    width: 32px;
    height: 32px;
}

.item-image {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.item-text {
    height: 24px;
    font-family: 'Open Sans';
    font-style: normal;
    font-weight: 400;
    font-size: 16px;
    line-height: 24px;
    color: #161616;
    user-select: none;
}

.item-label {
    box-sizing: border-box;
    display: flex;
    flex-direction: row;
    align-items: center;
    padding: 4px 32px 4px 16px;
    gap: 12px;
    height: 48px;
    width: fit-content;
    background: rgba(255, 152, 20, 0.1);
    border: 1px solid #ff9814;
    border-radius: 100px;
    margin-bottom: 12px;
}

.item-label.item-label--selected {
    background: rgba(25, 179, 148, 0.1);
    border: 1px solid #19b394;
    border-radius: 100px;
}

.question-note {
    white-space: initial;
}
```

### `src/typings.d.ts`

```ts
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
    id: string;
}
```

### `.angular-cli.json`

```json
{
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [ "assets", "favicon.ico" ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "prefix": "app",
      "styles": [ "styles.css" ],
      "scripts": [ ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ]
}
```

### `_tsconfig.json`

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "resolveJsonModule": true,
    "target": "es2015",
    "module": "es2020",
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
```

### `package.json`

```json
{
  "name": "surveyjs-angular",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "14.1.1",
    "@angular/cdk": "14.1.1",
    "@angular/common": "14.1.1",
    "@angular/compiler": "14.1.1",
    "@angular/core": "14.1.1",
    "@angular/forms": "14.1.1",
    "@angular/platform-browser": "14.1.1",
    "@angular/platform-browser-dynamic": "14.1.1",
    "@angular/router": "14.1.1",
    "core-js": "3.6.4",
    "rxjs": "6.5.4",
    "survey-core": "latest",
    "survey-angular-ui": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular/cli": "1.6.6",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/core-js": "0.9.46",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "3.9.7"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/form-library/examples/add-custom-items-to-single-and-multi-select-questions/reactjs.md)
- [Vue 3](https://surveyjs.io/form-library/examples/add-custom-items-to-single-and-multi-select-questions/vue3js.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/add-custom-items-to-single-and-multi-select-questions/vanillajs.md)
