Integrating CKEditor 5 with Angular from npm
CKEditor 5 has an official Angular component that you can use to add a rich text editor to your application, whether you use standalone or NGModule components. It works with multiple editor types, including classic, inline, and decoupled (document), and integrates with Angular forms through ngModel. This guide will help you install and configure it using the npm distribution of CKEditor 5.
This guide assumes you already have an Angular project. To create such a project, you can use Angular CLI. Refer to the Angular documentation to learn more.
First, install the CKEditor 5 packages:
ckeditor5– package with open-source plugins and features.ckeditor5-premium-features– package with premium plugins and features.
Depending on your configuration and chosen plugins, you may need to install the first or both packages.
npm install ckeditor5 ckeditor5-premium-featuresCopy codeThen, install the CKEditor 5 WYSIWYG editor component for Angular:
npm install @ckeditor/ckeditor5-angularCopy codeThe following setup differs depending on the type of components you use.
Standalone components provide a simplified way to build Angular applications. They are enabled in Angular 17 by default. Standalone components aim to simplify the setup and reduce the need for NGModules. That is why you do not need such a module in this case.
Instead, add the CKEditorModule to the imports in your app component. The component needs the standalone option set to true. The example below shows how to use the component with open-source and premium plugins.
Starting from version 44.0.0, the licenseKey property is required to use the editor. If you use a self-hosted editor from npm:
- You must either comply with the GPL or
- Obtain a license for self-hosting distribution.
You can set up a free trial to test the editor and evaluate the self-hosting.
// app.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { ClassicEditor, Bold, Essentials, Italic, Paragraph } from 'ckeditor5';
import { FormatPainter } from 'ckeditor5-premium-features';
@Component( {
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.None,
imports: [ CKEditorModule ],
standalone: true
} )
export class AppComponent {
title = 'angular';
public Editor = ClassicEditor;
public config = {
licenseKey: '<YOUR_LICENSE_KEY>',
plugins: [ Essentials, Paragraph, Bold, Italic, FormatPainter ],
toolbar: [ 'undo', 'redo', '|', 'bold', 'italic', '|', 'formatPainter' ]
}
}Copy codeDepending on the plugins used (open source only or premium too), you may need to import the first or both CSS files. Angular, by default, scopes styles to a particular component. Because of that, the editor may not detect attached styles. You must set the encapsulation option to ViewEncapsulation.None to turn this scoping off.
/* app.component.css */
@import 'ckeditor5/ckeditor5.css';
@import 'ckeditor5-premium-features/ckeditor5-premium-features.css';Copy codeThen, use the <ckeditor> tag in the template to run the rich text editor:
<!-- app.component.html -->
<ckeditor [editor]="Editor" [config]="config" data="<p>Hello, world!</p>"></ckeditor>Copy codeIf you want to use NGModule components, add the CKEditorModule to the imports array. It will make the CKEditor 5 component available in your Angular application.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { AppComponent } from './app.component';
@NgModule( {
declarations: [ AppComponent ],
imports: [ BrowserModule, CKEditorModule ],
providers: [],
bootstrap: [ AppComponent ]
} )
export class AppModule { }Copy codeThen, import the editor into your Angular component and assign it to a public property to make it accessible from the template. The example below shows how to use the component with open-source and premium plugins.
Starting from version 44.0.0, the licenseKey property is required to use the editor. If you use a self-hosted editor from npm:
- You must either comply with the GPL or
- Obtain a license for self-hosting distribution.
You can set up a free trial to test the editor and evaluate the self-hosting.
// app.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { ClassicEditor, Essentials, Paragraph, Bold, Italic } from 'ckeditor5';
import { FormatPainter } from 'ckeditor5-premium-features';
@Component( {
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
encapsulation: ViewEncapsulation.None
} )
export class AppComponent {
title = 'angular';
public Editor = ClassicEditor;
public config = {
licenseKey: '<YOUR_LICENSE_KEY>',
plugins: [ Essentials, Paragraph, Bold, Italic, FormatPainter ],
toolbar: [ 'undo', 'redo', '|', 'bold', 'italic', '|', 'formatPainter' ]
}
}Copy codeDepending on the plugins you used, you may need to import the first or both CSS files. Angular, by default, scopes styles to a particular component. That’s why the editor may not detect attached styles. You must set the encapsulation option to ViewEncapsulation.None to turn this scoping off.
/* app.component.css */
@import 'ckeditor5/ckeditor5.css';
@import 'ckeditor5-premium-features/ckeditor5-premium-features.css';Copy codeFinally, use the <ckeditor> tag in the template to run the rich text editor:
<!-- app.component.html -->
<ckeditor [editor]="Editor" [config]="config" data="<p>Hello, world!</p>"></ckeditor>Copy codeThe following @Input properties are supported by the CKEditor 5 rich text editor component for Angular:
The Editor which provides the static create() method to create an instance of the editor:
<ckeditor [editor]="Editor"></ckeditor>Copy codeThe configuration of the editor:
<ckeditor [config]="{ toolbar: [ 'heading', '|', 'bold', 'italic' ] }"></ckeditor>Copy codeThe initial data of the editor. It can be a static value:
<ckeditor data="<p>Hello, world!</p>"></ckeditor>Copy codeor a shared parent component’s property
@Component( {
// ...
} )
export class MyComponent {
public editorData = '<p>Hello, world!</p>';
// ...
}Copy code<ckeditor [data]="editorData"></ckeditor>Copy codeThe tagName input is deprecated in favor of config.root.element (or config.roots.main.element). The new configuration option lets you customize the tag name, classes, inline styles, and HTML attributes of the editable element. See the Using an inline editor section below for details.
The tag name of the HTML element on which the rich text editor will be created.
The default tag is <div>.
<ckeditor tagName="textarea"></ckeditor>Copy codeControls the editor’s read–only state:
@Component( {
// ...
} )
export class MyComponent {
public isDisabled = false;
// ...
toggleDisabled() {
this.isDisabled = !this.isDisabled
}
}Copy code<ckeditor [disabled]="isDisabled"></ckeditor>
<button (click)="toggleDisabled()">
{{ isDisabled ? 'Enable editor' : 'Disable editor' }}
</button>Copy codeAllows disabling the two-way data binding mechanism. The default value is false.
We introduced this option to address performance issues in large documents. By default, while using the ngModel directive, whenever the editor’s data is changed, the component must synchronize the data between the editor instance and the connected property. This results in calling the editor.getData() function, which causes a massive slowdown while typing in large documents.
This option allows the integrator to disable the default behavior and only call the editor.getData() method on demand, which prevents the slowdowns. You can read more in the relevant issue.
The following @Output properties are supported by the CKEditor 5 rich text editor component for Angular:
Fired when the editor is ready. It corresponds with the editor#ready event.
It is fired with the editor instance.
Fired when the content of the editor has changed. It corresponds with the editor.model.document#change:data event.
It is fired with an object containing the editor and the CKEditor 5 change:data event object.
<ckeditor [editor]="Editor" (change)="onChange($event)"></ckeditor>Copy codeimport { ClassicEditor } from 'ckeditor5';
import { ChangeEvent } from '@ckeditor/ckeditor5-angular/ckeditor.component';
@Component( {
// ...
} )
export class MyComponent {
public Editor = ClassicEditor;
public onChange( { editor }: ChangeEvent ) {
const data = editor.getData();
console.log( data );
}
// ...
}Copy codeFired when the editing view of the editor is blurred. It corresponds with the editor.editing.view.document#blur event.
It is fired with an object containing the editor and the CKEditor 5 blur event data.
Fired when the editing view of the editor is focused. It corresponds with the editor.editing.view.document#focus event.
It is fired with an object containing the editor and the CKEditor 5 focus event data.
Fired when an error is reported for the editor, either during the initialization or at runtime.
Prior to ckeditor5-angular v7.0.1, this event was not fired for errors during the editor initialization.
A reported error does not stop the editor. Its state may no longer be consistent, so do not leave the error unhandled. Nothing is restarted and no data is restored for you, so what happens next is your application’s decision.
The error handling guide covers the options: telling the user and switching the editor to read-only, recreating it, and recovering its content. If you are moving off the Watchdog, the migrating from the Watchdog guide shows how to recreate the editor and when to do it. In Angular that check differs from the other integrations because the error output carries no phase.
The component implements the ControlValueAccessor interface and works with the ngModel. Here is how to use it:
Create some model in your component to share with the editor:
@Component( {
// ...
} )
export class MyComponent {
public model = {
editorData: '<p>Hello, world!</p>'
};
// ...
}Copy codeUse the model in the template to enable a two–way data binding:
<ckeditor [(ngModel)]="model.editorData" [editor]="Editor"></ckeditor>Copy codeThe CKEditor 5 rich text editor component for Angular can be styled using the component style sheet or using a global style sheet. See how to set the CKEditor 5 component’s height using these two approaches.
First, create a (S)CSS file in the parent component’s directory and style the given editor’s part preceded by the :host and ::ng-deep pseudo selectors:
/* src/app/app.component.css */
:host ::ng-deep .ck-editor__editable_inline {
min-height: 500px;
}Copy codeThen, in the parent component, add the relative path to the above style sheet:
/* src/app/app.component.ts */
@Component( {
// ...
styleUrls: [ './app.component.css' ]
} )Copy codeTo style the component using a global style sheet, first, create it:
/* src/styles.css */
.ck-editor__editable_inline {
min-height: 500px;
}Copy codeThen, add it to the angular.json configuration file:
"architect": {
"build": {
"options": {
"styles": [
{ "input": "src/styles.css" }
]
}
}
}Copy codeTo display the placeholder in the main editable element, set the root.placeholder field in the CKEditor 5 rich text editor component configuration:
@Component( {
// ...
} )
export class MyComponent {
public config = {
root: {
placeholder: 'Type the content here!'
}
}
}Copy codeThe CKEditor 5 rich text editor component provides all the functionality needed for most use cases. When access to the full CKEditor 5 API is needed you can get the editor instance with an additional step.
To do this, create a template reference variable #editor pointing to the <ckeditor> component:
<ckeditor #editor [editor]="Editor"></ckeditor>Copy codeThen get the <ckeditor> component using a property decorated by @ViewChild( 'editor' ) and access the editor instance when needed:
@Component()
export class MyComponent {
@ViewChild( 'editor' ) editorComponent: CKEditorComponent;
public getEditor() {
// Warning: This may return "undefined" if the editor is hidden behind the `*ngIf` directive or
// if the editor is not fully initialised yet.
return this.editorComponent.editorInstance;
}
}Copy codeThe editor creation is asynchronous so the editorInstance will not be available until the editor is created. If you want to make changes to an editor that has just been created, a better option would be getting the CKEditor 5 instance on the ready event.
If you want to use the document (decoupled) editor, you need to add the toolbar to the DOM manually:
// app.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { DecoupledEditor, Essentials, Italic, Paragraph, Bold } from 'ckeditor5';
@Component( {
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
encapsulation: ViewEncapsulation.None,
imports: [ CKEditorModule ],
standalone: true
} )
export class AppComponent {
title = 'angular';
public Editor = DecoupledEditor;
public config = {
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
plugins: [ Bold, Essentials, Italic, Paragraph ],
toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
}
public onReady( editor: DecoupledEditor ): void {
const element = editor.ui.getEditableElement()!;
const parent = element.parentElement!;
parent.insertBefore(
editor.ui.view.toolbar.element!,
element
);
}
}Copy codeImport the needed CSS style sheet:
/* app.component.css */
@import 'ckeditor5/ckeditor5.css';Copy codeAnd then, link the method in the template:
<!-- app.component.html -->
<ckeditor [editor]="Editor" data="<p>Hello, world!</p>" (ready)="onReady($event)"></ckeditor>Copy codeSingle-root editors such as InlineEditor, BalloonEditor, and DecoupledEditor can be configured as inline editors that accept only inline content (text, bold, italic, links) instead of blocks. This is useful for short fields such as titles, captions, or single-line inputs.
Set root.modelElement to '$inlineRoot' to restrict the root to inline content. Optionally, provide a custom root.element to render the editable host as a specific tag (for example, <h1> for a title) instead of the default <div>.
// app.component.ts
import { Component } from '@angular/core';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { BalloonEditor, Essentials, Bold, Italic } from 'ckeditor5';
@Component( {
selector: 'app-root',
templateUrl: './app.component.html',
imports: [ CKEditorModule ],
standalone: true
} )
export class AppComponent {
public Editor = BalloonEditor;
public config = {
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
plugins: [ Essentials, Bold, Italic ],
toolbar: [ 'bold', 'italic' ],
root: {
element: 'h1',
modelElement: '$inlineRoot',
initialData: 'Document title',
placeholder: 'Enter title...'
}
};
}Copy code<!-- app.component.html -->
<ckeditor [editor]="Editor" [config]="config"></ckeditor>Copy codeThe root.element property accepts:
- A tag name string, for example
'h1'or'section'. - A descriptor object with
name,classes,styles, andattributesfields.
Without modelElement: '$inlineRoot', only the host tag changes – the schema still permits blocks inside the root.
The <ckeditor> component always renders a <div> host for ClassicEditor, regardless of root.element. Classic editor wraps its toolbar and editable inside its own structure. Use InlineEditor, BalloonEditor, or DecoupledEditor to control the host element.
Rendering the editor inside a shadow root isolates it from the styles of the host page. Set ViewEncapsulation.ShadowDom on the component and Angular attaches the root for you, moving the styles of that component into it. Use a component that wraps the editor rather than the root component of the application, or everything the application renders ends up inside the root. The whole editor UI, including the body collection that holds balloons and dropdown panels, stays inside the shadow root, so the scoped styles cover all of it. This happens because config.ui.overlayContainer is not set, so the editor falls back to the root it is in and logs the ui-overlay-container-not-configured warning. For a more robust setup, set your own overlay container and load the same style sheets into it, as described in the Where the floating user interface mounts section of the Shadow DOM guide.
Rules from the global styles of the application do not match elements inside a shadow root, so import the editor style sheet in the styles of the component instead of in angular.json:
// editor.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { ClassicEditor, Essentials, Paragraph, Bold, Italic } from 'ckeditor5';
@Component( {
selector: 'app-editor',
templateUrl: './editor.component.html',
styleUrls: [ './editor.component.css' ],
imports: [ CKEditorModule ],
standalone: true,
encapsulation: ViewEncapsulation.ShadowDom
} )
export class EditorComponent {
public Editor = ClassicEditor;
public config = {
licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
plugins: [ Essentials, Paragraph, Bold, Italic ],
toolbar: [ 'bold', 'italic' ]
};
}Copy code/* editor.component.css */
@import 'ckeditor5/ckeditor5.css';Copy code<!-- editor.component.html -->
<ckeditor [editor]="Editor" [config]="config" data="<p>Hello, world!</p>"></ckeditor>Copy codeAn override of a --ck-* variable on :root has no effect on an editor inside a shadow root, so put it on the shadow host instead. The Shadow DOM guide explains why, and covers the known limitations.
We provide ready-to-use integration featuring collaborative editing in an Angular application:
It is not mandatory to build an application on top of the above samples, however, it should help you get started.
To share one Context between editors, create it yourself and pass it in the editor configuration:
this.context = await MyEditor.Context.create( {
// The context configuration.
} );
this.config = { context: this.context };Copy codeThe context is yours, so destroy it when you are done with it.
Hold the editors back until the context is ready, with *ngIf="config" on the <ckeditor> element. The component reads its configuration only once, when it creates the editor. If it renders before Context.create() resolves, the editor gets no context, and setting the configuration later changes nothing. Errors attributed to the context rather than to one of its editors are not emitted by the component; register a callback for those with MyEditor.Context.onEditorError().
CKEditor 5 supports multiple UI languages, and so does the official Angular component. Follow the instructions below to translate CKEditor 5 in your Angular application.
Similarly to CSS style sheets, both packages have separate translations. Import them as shown in the example below. Then, pass them to the translations array of the config property.
// app.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
import { ClassicEditor } from 'ckeditor5';
// More imports...
import coreTranslations from 'ckeditor5/translations/es.js';
import premiumFeaturesTranslations from 'ckeditor5-premium-features/translations/es.js';
@Component( {
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
encapsulation: ViewEncapsulation.None,
imports: [ CKEditorModule ],
standalone: true
} )
export class AppComponent {
title = 'angular';
public Editor = ClassicEditor;
public config = {
// ... Other configuration options ...
translations: [ coreTranslations, premiumFeaturesTranslations ]
}
}Copy codeFor advanced usage see the Setting the UI language guide.
There is a known issue related to the localization in Angular 17. Read more in the known issues section below.
The moduleResolution option of the TypeScript configuration determines the algorithm for finding and resolving modules from node_modules. In Angular 17, the option is set to node by default. This option prevents type declaration for editor translations from being correctly loaded. To fix it, you have several options:
- You can set the
moduleResolutionoption tobundler. It is the recommended setting in TypeScript 5.0+ for applications that use a bundler. And it is a recommended way of fixing this problem. You can check other solutions below for lower TypeScript versions. - You can tell the TypeScript compiler to suppress the problem using the
// @ts-expect-errorcomment above the imported translations. - You can update Angular to version 18, where the
moduleResolutionoption is set tobundlerby default. - You can import translations directly from our CDN, like:
import ‘https://cdn.ckeditor.com/ckeditor5/49.0.0/translations/es.umd.js’;. This way, the editor will load the translations automatically, so you do not need to pass them manually into the configuration.
You can use Jest as a test runner in Angular apps. Unfortunately, Jest does not use a real browser. Instead, it runs tests in Node.js that uses JSDOM. JSDOM is not a complete DOM implementation, and while it is sufficient for standard apps, it cannot polyfill all the DOM APIs that CKEditor 5 requires.
For testing CKEditor 5, it is recommended to use testing frameworks that utilize a real browser and provide a complete DOM implementation. Some popular options include:
These frameworks offer better support for testing CKEditor 5 and provide a more accurate representation of how the editor behaves in a real browser environment.
If this is not possible and you still want to use Jest, you can mock some of the required APIs. Below is an example of how to mock some of the APIs used by CKEditor 5:
import { TextEncoder } from 'util';
beforeAll( () => {
window.TextEncoder = TextEncoder;
window.scrollTo = jest.fn();
window.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
for ( const key of [ 'InputEvent', 'KeyboardEvent' ] ) {
window[ key ].prototype.getTargetRanges = () => {
const range = new StaticRange( {
startContainer: document.body.querySelector( '.ck-editor__editable p' ),
startOffset: 0,
endContainer: document.body.querySelector( '.ck-editor__editable p' ),
endOffset: 0
} );
return [ range ];
};
}
const getClientRects = () => ({
item: () => null,
length: 0,
[Symbol.iterator]: function* () {}
});
Range.prototype.getClientRects = getClientRects;
Element.prototype.getClientRects = getClientRects;
if ( !Document.prototype.createElementNS ) {
Document.prototype.createElementNS = ( namespace, name ) => {
const element = document.createElement( name );
element.namespaceURI = namespace;
return element;
};
}
} );Copy codeThese mocks should be placed before the tests that use CKEditor 5. They are imperfect and may not cover all the cases, but they should be sufficient for basic initialization and rendering the editor. Keep in mind that they are not a replacement for proper browser testing.
Because of the breaking changes in the Angular library output format, the @ckeditor/ckeditor5-angular package is released in the following versions to support various Angular ecosystems:
| CKEditor 5 Angular component version | Angular version | Details |
|---|---|---|
| Actively supported versions | ||
^11 |
19+ |
Requires CKEditor 5 in version 47 or higher. |
| Past releases (no longer maintained) | ||
^10 |
16+ |
Requires CKEditor 5 in version 46 or higher. |
^9 |
16+ |
Migration to TypeScript 5. Declaration files are not backward compatible. Requires CKEditor 5 in version 43 or higher. |
^8 |
13+ |
Requires CKEditor 5 in version 42 or higher. |
^7 |
13+ |
Changes in peer dependencies (issue). Requires CKEditor 5 in version 37 or higher. |
^6 |
13+ |
Requires CKEditor 5 in version 37 or higher. |
^5 |
13+ |
Requires Angular in version 13+ or higher. |
^4 |
9.1+ |
Requires CKEditor 5 in version 34 or higher. |
^3 |
9.1+ |
Requires Node.js in version 14 or higher. |
^2 |
9.1+ |
Migration to TypeScript 4. Declaration files are not backward compatible. |
^1 |
5.x - 8.x |
Angular versions no longer maintained. |
All available Angular versions are listed on npm, where they can be pulled from.
The source code of the CKEditor 5 rich text editor component for Angular is available on GitHub at https://github.com/ckeditor/ckeditor5-angular.
- See how to manipulate the editor’s data in the Getting and setting data guide.
- Refer to further guides in the setup section to see how to customize your editor further.
- Check the features category to learn more about individual features.