---
title: Integrate a Third-Party Rich Content Editor
product: Survey Creator
description: Learn how to transform text entry fields within the Property Grid into powerful rich text editors for easy and precise text formatting. View this free demo to explore the capabilities of a WYSIWYG visual editor and access a code example for JavaScript to discover how to seamlessly integrate Quill into your SurveyJS form-building tool.
framework: Angular
source: https://surveyjs.io/survey-creator/examples/form-builder-with-integrated-rich-text-editor/angular
index: https://surveyjs.io/survey-creator/examples/overview.md
---

# Integrate a Third-Party Rich Content Editor (Angular)

In addition to multiple configuration options available by default, SurveyJS Creator supports various customizations. For instance, you can replace text entry fields within the Property Grid (the input fields you use to enter a title or form description) with dedicated rich text editors. A rich text editor is a WYSIWYG visual editor that provides an input area for editing your form's content and a toolbar equipped with all the necessary formatting tools. Survey creators can apply bold, italic, or underline font style to text, add bullet points or numbered lists, insert hyperlinks, adjust text alignment, and more. This demo illustrates how to integrate a third-party <a href="https://quilljs.com/" target="_blank">Quill</a> text editor into Survey Creator.

For a step-by step Quill integration tutorial, refer to the following documentation articles. Although they describe integration of a color picker, the same instructions apply to any other third-party component, including Quill. 

- [Integrate Third-Party React Components](https://surveyjs.io/form-library/documentation/customize-question-types/third-party-component-integration-react)
- [Integrate Third-Party Angular Components](https://surveyjs.io/form-library/documentation/customize-question-types/third-party-component-integration-angular)
- [Integrate Third-Party Vue 3 Components](/form-library/documentation/customize-question-types/third-party-component-integration-vue)

## Files

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

```css
.ql-toolbar.ql-snow {
    white-space: normal;
}
.sv-string-editor--html {
    white-space: normal !important;
}
.quill .ql-container {
    max-height: calc(100% - 42px);
}
```

### `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/quill.component.html`

```html
<quill-editor
    [readOnly]="model.isReadOnly"
    [(ngModel)]="content"
    [styles]="{ height: model.height }">
</quill-editor>
```

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

```ts
import { Component } from "@angular/core";
import { AngularComponentFactory, QuestionAngular } from "survey-angular-ui";
import { ElementFactory, Question, Serializer } from "survey-core";
import { PropertyGridEditorCollection } from "survey-creator-core";

// Must use lowercase
const CUSTOM_TYPE = "quill";

// Create a question model
export class QuestionQuillModel extends Question {
    getType() {
        return CUSTOM_TYPE;
    }
    public get height(): string {
        return this.getPropertyValue("height");
    }
    public set height(val: string) {
        this.setPropertyValue("height", val);
    }
}

// Register the model in `ElementFactory`
ElementFactory.Instance.registerElement(CUSTOM_TYPE, (name) => {
    return new QuestionQuillModel(name);
});

// Add question type metadata for further serialization into JSON
Serializer.addClass(
    CUSTOM_TYPE,
    [{ name: "height", default: "200px", category: "layout" }],
    function () {
        return new QuestionQuillModel("");
    },
    "question"
);

// Create a component that renders Quill
@Component({
    selector: "quill",
    templateUrl: "./quill.component.html"
})
export class QuillComponent extends QuestionAngular<QuestionQuillModel> {
    public get content() {
        return this.model.value;
    }
    public set content(val: string) {
        this.model.value = val;
    }
}
// Register the component in `AngularComponentFactory`
AngularComponentFactory.Instance.registerComponent(
    CUSTOM_TYPE + "-question",
    QuillComponent
);

// Register `quill` as an editor for properties of the `text` and `html` types in the Survey Creator's Property Grid
PropertyGridEditorCollection.register({
    fit: function (prop) {
        return prop.type == "text" || prop.type == "html";
    },
    getJSON: function () {
        return { type: "quill" };
    }
});
```

### `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 { QuillComponent } from "./quill.component";
import { getLocaleStrings } from "survey-creator-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

function applyHtml(_, options) {
    let str = options.text;
    if (str.indexOf("<p>") === 0) {
        // Remove root paragraphs <p></p>
        str = str.substring(3);
        str = str.substring(0, str.length - 4);
    }
    // Set HTML markup to render
    options.html = str;
}

// Specify a title for the toolbox category that will contain the Quill question type  
const translations = getLocaleStrings("en");
translations.toolboxCategories["custom"] = "Custom Questions";
@Component({
    // tslint:disable-next-line:component-selector
    selector: "component-survey-creator",
    templateUrl: "./creator.component.html",
    styleUrls: ["./creator.component.css"]
})
export class SurveyCreatorComponent implements OnInit {
    // Ensure that the component won't be removed from the production build by tree shaking
    static declaration = [QuillComponent];
    model: SurveyCreatorModel;
    ngOnInit() {
        const creator = new SurveyCreatorModel();
        // Move the Quill question type to the first position in the Toolbox
        const toolboxItems = creator.toolbox.items;
        const quillIndex = toolboxItems.findIndex((item) => item.name === "quill");
        const quillItem = toolboxItems.splice(quillIndex, 1)[0];
        quillItem.title = "Rich Text Editor";
        quillItem.iconName = "icon-comment";
        toolboxItems.unshift(quillItem);
        creator.toolbox.changeCategory("quill", "custom");
        
        // Apply HTML markup to survey contents
        creator.survey.onTextMarkdown.add(applyHtml);
        creator.onSurveyInstanceSetupHandlers.add((_, options) => {    
            options.survey.onTextMarkdown.add(applyHtml);    
        });
        
        creator.JSON = {
            "title": "Quill Integration with SurveyJS",
            "description": "<ul><li><strong>Bold</strong></li><li><em>Italics</em></li><li><s>Strikethrough</s></li></ul>",
            "pages": [{
                "name": "page1",
                "elements": [{
                    "type": "quill",
                    "name": "rich-text-editor",
                    "title": "Rich Text Editor",
                    "defaultValue": "Hello world!"
                }]
            }]
        }
        
        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";
import { FormsModule } from "@angular/forms";
import { QuillModule } from "ngx-quill";
import { QuillComponent } from "./components/quill.component";

@NgModule({
    declarations: [AppComponent, SurveyCreatorComponent, QuillComponent],
    imports: [BrowserModule, SurveyCreatorModule, FormsModule, QuillModule.forRoot({
        suppressGlobalRegisterWarning: true,
        modules: {
            toolbar: [[{ "header": [1, 2, 3, false] }], ["bold", "italic", "underline", "link"], [{ "list": "ordered" }, { "list": "bullet" }], ["clean"]]
        }
    })],
    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 */
```

### `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" ,
      "./node_modules/quill/dist/quill.core.css",
      "./node_modules/quill/dist/quill.snow.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",
    "ngx-quill": "16.0.0",
    "quill": "^1.3.7",
    "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/form-builder-with-integrated-rich-text-editor/reactjs.md)
- [Vue 3](https://surveyjs.io/survey-creator/examples/form-builder-with-integrated-rich-text-editor/vue3js.md)
- [jQuery](https://surveyjs.io/survey-creator/examples/form-builder-with-integrated-rich-text-editor/jquery.md)
