---
title: Runtime Toolbox Customization
product: Survey Creator
description: Enable your end users to customize the toolbox by adding and removing its elements, e.g., question and panel types. View a free demo for JavaScript.
framework: Angular
source: https://surveyjs.io/survey-creator/examples/customize-toolbox-at-runtime/angular
index: https://surveyjs.io/survey-creator/examples/overview.md
---

# Runtime Toolbox Customization (Angular)

Survey Creator users can change the collection of available toolbox items and add new ones at runtime. In this demo, questions on the design surface have a **Save to Toolbox** button. Clicking it saves the current question configuration as a new toolbox item. To manage toolbox items, click the **Customize Toolbox** button in the toolbar. This button opens a pop-up window where you can show and hide any toolbox items and delete unnecessary custom items. Please note that runtime toolbox customization is not supported out of the box and requires implementation by your development team, as shown in this example.

## Save Custom Items to the Toolbox

Perform the following steps to let users save custom question configurations to the Toolbox:

1. Create a toolbox item configuration object.          
A toolbox item configuration object should implement the [`IQuestionToolboxItem`](/survey-creator/documentation/api-reference/iquestiontoolboxitem) interface.

1. Add the custom item to the Toolbox.          
Access the [`QuestionToolbox`](/survey-creator/documentation/api-reference/questiontoolbox) instance using Survey Creator's [`toolbox`](/survey-creator/documentation/api-reference/survey-creator#toolbox) property and call the [`addItem(item, index)`](/survey-creator/documentation/api-reference/questiontoolbox#addItem) method on this instance.

1. Add a UI element that saves custom toolbox items.
You can use any UI element that suits your use case. This demo uses the [`onElementGetActions`](/survey-creator/documentation/api-reference/survey-creator#onElementGetActions) event to add a custom button ([adorner](/survey-creator/documentation/end-user-guide/user-interface#adorners)) to each question on the design surface. A click on this button call a `saveCustomItem` function that creates a `IQuestionToolboxItem` object and adds it to the Toolbox.

Refer to the Code tab for a code example. Code lines that implement the steps above are marked with comments.

## Manage Toolbox Items

If you want to let users add and remove items from the Toolbox, follow the instructions below:

1. Create a survey that displays the dialog content.        
A pop-up dialog is built upon a regular survey from [SurveyJS Form Library](/form-library/documentation/overview). The survey displays a list of predefined toolbox items using a [Checkboxes](/form-library/examples/create-checkboxes-question-in-javascript/) question and allows users to delete unnecessary custom toolbox items using a [Dynamic Matrix](/form-library/examples/dynamic-matrix-add-new-rows/) question. Implement a function that returns the survey JSON schema (view the `createToolboxSetupSurveyJson()` function in code listings).

1. Populate the survey with lists of predefined and custom toolbox items.       
Access an array of toolbox [`items`](/survey-creator/documentation/api-reference/questiontoolbox#items), use it to prepare data arrays for the Checkboxes and Dynamic Matrix questions, and assign these arrays to the questions' [`value`](/form-library/documentation/api-reference/question#value) property (view the `populateToolboxSetupSurvey()` function).

1. Add a custom button that opens the dialog window.           
Configure the button by defining an [`IAction`](https://surveyjs.io/form-library/documentation/api-reference/iaction) object and passing it the `Action` constructor. This button can be added to the Survey Creator toolbar.

1. Apply the changes made in the dialog window.         
When users click Apply in the dialog window, you need to clear the Toolbox, collect selected toolbox items, and add them to the Toolbox anew (view the `showToolboxSetupPopup()` function).

## Files

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

```css
/* You can define custom CSS rules here */
```

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

```html
<div style="position: fixed; top: 0; bottom: 0; right: 0; left: 0;">
    <survey-creator [model]="model"></survey-creator>
</div>
```

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

```ts
import { Component, OnInit } from "@angular/core";
import { SurveyCreatorModel } from "survey-creator-core";
import "survey-core/survey.i18n";
import "survey-creator-core/survey-creator-core.i18n";
import { Model, settings, Action, JsonObject, ComputedUpdater } from "survey-core";
import "survey-core/survey-core.css";
import "survey-creator-core/survey-creator-core.css";

import SurveyTheme from "survey-core/themes";
import { registerCreatorTheme } from "survey-creator-core";

registerCreatorTheme(SurveyTheme); // Add predefined Survey Creator UI themes

@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey-creator",
    templateUrl: "./creator.component.html",
    styleUrls: ["./creator.component.css"]
})
export class SurveyCreatorComponent implements OnInit {
    model: SurveyCreatorModel;
    ngOnInit() {
        const creator = new SurveyCreatorModel();
        const toolbox = creator.toolbox;
        toolbox.forceCompact = false;
        // #region Save Custom Items to the Toolbox 
        var customItems = {};
        const customCategoryName = "Custom Questions";
        
        function saveCustomItem (element) {
          const json = new JsonObject().toJsonObject(element);
          json.type = element.getType();
        
          if (!!customItems[element.name]) {
            creator.notify("A toolbox item with the same name already exists", "error");
            return;
          };
        
          // Step 1: Prepare a toolbox item configuration object
          const item = {
              name: element.name,
              iconName: "icon-" + element.getType(),
              title: element.title,
              json: json,
              category: customCategoryName
          };
          customItems[item.name] = item;
          // Step 2: Add the custom toolbox item to the top
          toolbox.addItem(item, 0);
        }
        
        // Step 3: Add a custom adorner that saves a question configuration as a toolbox item
        creator.onElementGetActions.add((_, options) => {
            if (options.element["isPage"]) return;
            const elemToAdd = options.element;
            options.actions.unshift({
                id: "save-to-toolbox",
                title: "Save to Toolbox",
                iconName: "icon-toolbox",        
                action: () => {
                    saveCustomItem(elemToAdd);
                }
            });
        });
        // #endregion
        
        // #region Manage Toolbox Items
        const predefinedItems: any = {};
        
        const defaultToolboxItems: any = [].concat(toolbox.items);
        
        // Step 1: Create a survey that displays the dialog content
        function createToolboxSetupSurveyJson () {
            const choices: Array<{ value: string; text: string }> = [];
            const value: string[] = [];
            defaultToolboxItems.forEach(({ name, title, iconName, json, tooltip, category }) => {
                choices.push({ value: name, text: title });
                value.push(name);
                predefinedItems[name] = {
                    name: name,
                    iconName: iconName,
                    json: json,
                    title: title,
                    tooltip: tooltip,
                    category: category
                };
            });
            return {
              "showNavigationButtons": false,
              "elements": [{
                "type": "checkbox",
                "name": "predefinedItems",
                "title": "Standard questions",
                "choices": choices,
                "defaultValue": value
              }, {
                "type": "matrixdynamic",
                "name": "customItems",
                "title": "Custom Questions",
                "showHeader": false,
                "columns": [{
                  "name": "enabled",
                  "cellType": "boolean",
                  "defaultValue": true,
                  "renderAs": "checkbox",
                  "width": "15px",
                  "minWidth": "15px",
                }, {
                  "name": "question-name",
                  "cellType": "dropdown",
                  "readOnly": true
                }],
                "rowCount": 0,
                "allowAddRows": false,
                "hideColumnsIfEmpty": true,
                "noRowsText": "There are no custom questions."
              }]
            };
        }
        
        function isItemUsed (name) {
          return !!toolbox.getItemByName(name);
        }
        
        // Step 2: Populate the survey with lists of predefined and custom toolbox items
        function populateToolboxSetupSurvey (survey) {
            const selectedPredefinedItems: string[] = [];
            toolbox.items.forEach((item) => {
                selectedPredefinedItems.push(item.name);
            });
            const qPredefinedItems = survey.getQuestionByName("predefinedItems");
            qPredefinedItems.value = selectedPredefinedItems;
        
            const availableCustomItems: Array<{ value: string; text: string }> = [];
            const selectedCustomItems: Array<{ enabled: boolean; 'question-name': string; }> = [];
            Object.keys(customItems).forEach((key) => {
                const item = customItems[key];
                availableCustomItems.push({ value: item.name, text: item.title });
                selectedCustomItems.push({
                    enabled: isItemUsed(item.name),
                    "question-name": item.name
                });
            });
            const qCustomItems = survey.getQuestionByName("customItems");
            qCustomItems.visible = availableCustomItems.length > 0;
            qCustomItems.choices = availableCustomItems;
            qCustomItems.value = selectedCustomItems;
            return [ qPredefinedItems, qCustomItems ];
        }
        
        // Step 3: Add a custom button that opens the dialog window with toolbox customization options
        const toolboxCustomizationAction = new Action({
            id: "toolbox-customization",
            iconName: "icon-toolbox",
            tooltip: "Customize Toolbox",
            visible: new ComputedUpdater(() => {
                return creator.activeTab === "designer";
            }),
            enabled: true,
            action: () => {
                showToolboxSetupPopup();
            }
        });
        // Add the custom button to the top toolbar
        creator.toolbarItems.push(toolboxCustomizationAction);
        // Add the custom button to the bottom toolbar (visible only on mobile devices)
        creator.footerToolbar.actions.push(toolboxCustomizationAction);
        
        function showToolboxSetupPopup () {
            const toolboxSetupSurvey = new Model(createToolboxSetupSurveyJson());
        
            const [ qPredefinedItems, qCustomItems ] = populateToolboxSetupSurvey(toolboxSetupSurvey);
        
            const popupOptions = {
                title: "Configure Toolbox Items",
                componentName: "survey",
                data: { model: toolboxSetupSurvey, survey: toolboxSetupSurvey },
                cssClass: "setup-toolbox-popup",
                // Step 4: Apply the changes made in the dialog window
                onApply: () => {
                    const items: any = [];
        
                    // Clear the Toolbox
                    toolbox.clearItems();
        
                    // Collect selected custom toolbox items
                    if (Array.isArray(qCustomItems.value)) {
                        const updatedCustomQuestions = {};
                        qCustomItems.value.forEach((obj) => {
                            const qName = obj["question-name"];
                            const customItem = customItems[qName];
                            updatedCustomQuestions[qName] = customItem;
                            if (obj.enabled) {
                                items.push(customItem);
                            }
                        });
                        customItems = updatedCustomQuestions; 
                    }
        
                    // Collect selected predefined toolbox items
                    for (let key in predefinedItems) {
                      if (Array.isArray(qPredefinedItems.value) && qPredefinedItems.value.indexOf(key) > -1) {
                          items.push(predefinedItems[key]);
                      }
                    }
        
                    // Add the selected items to the Toolbox
                    items.forEach((item) => {
                        toolbox.addItem(item);
                    });
                    return true;
                }
            };
            settings.showDialog(popupOptions);
        }
        // #endregion
        
        creator.JSON = {
          "elements": [{
            "type": "text",
            "name": "question1",
            "title": "Question 1"
          }]
        }
        this.model = creator;
    }
}
```

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

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

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

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

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html"
})
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 { SurveyCreatorModule } from "survey-creator-angular";
import { SurveyCreatorComponent } from "./components/creator.component";

@NgModule({
    declarations: [AppComponent, SurveyCreatorComponent],
    imports: [BrowserModule, SurveyCreatorModule],
    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
/* You can add global styles to this file and import other style files */
.sv_main .sv_custom_header {
    display: none;
}

.setup-toolbox-popup .sd-root-modern {
    min-width: 640px;
}
```

### `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-angular-ui": "latest",
    "survey-creator-core": "latest",
    "survey-core": "latest",
    "survey-creator-angular": "latest",
    "tslib": "1.13.0",
    "zone.js": "0.11.7"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~13.0.0",
    "@angular/cli": "~13.0.0",
    "@types/jasmine": "3.6.3",
    "@types/jasminewd2": "2.0.8",
    "@types/node": "14.14.28",
    "codelyzer": "6.0.1",
    "jasmine-core": "3.6.0",
    "jasmine-spec-reporter": "6.0.0",
    "karma": "6.1.1",
    "karma-chrome-launcher": "3.1.0",
    "karma-coverage-istanbul-reporter": "3.0.3",
    "karma-jasmine": "4.0.1",
    "karma-jasmine-html-reporter": "1.5.4",
    "protractor": "7.0.0",
    "ts-node": "9.1.1",
    "tslint": "~6.1.3",
    "typescript": "4.1.5"
  },
  "keywords": [ "angular", "surveyjs" ],
  "description": "SurveyJS-Angular example project"
}
```

## Other Frameworks

- [React](https://surveyjs.io/survey-creator/examples/customize-toolbox-at-runtime/reactjs.md)
- [Vue 3](https://surveyjs.io/survey-creator/examples/customize-toolbox-at-runtime/vue3js.md)
- [jQuery](https://surveyjs.io/survey-creator/examples/customize-toolbox-at-runtime/jquery.md)
- [Vanilla JS](https://surveyjs.io/survey-creator/examples/customize-toolbox-at-runtime/vanillajs.md)
