Adding a rich text editor to your Angular app is easy since there are libraries to do so already. It supports lots of features like changing fonts, adding or removing underline, adding lists, bolding text, changing default fonts, add pictures, adding placeholders, etc. Almost anything you can think of can be added to an editor. CKEditor is the rich text editor with the most comprehensive options available.
To use CKEditor in our Angular app, we use an Angular version of the editor available at https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/angular.html, available as a Node package. This allows us to bind our input content to our model without writing code to do that ourselves.
We install the package by running:
npm i ng2-ckeditor
Then we include the following in the imports
array of your module:
import { CKEditorModule } from 'ng2-ckeditor';
import { FormsModule } from '@angular/forms';
@NgModule({
// ...
imports: [CKEditorModule, FormsModule],
// ...
})
export class AppModule {}
In index.html
, we add:
<script src="https://cdn.ckeditor.com/4.5.11/full-all/ckeditor.js"></script>
This includes all the plugins for CKEditor.
Then we add the following to our component, we add:
import { Component } from '@angular/core';
@Component({
selector: 'sample',
template: `
<ckeditor
[(ngModel)]="content"
[config]="config"
[readonly]="false"
(change)="onChange($event)"
(editorChange)="onEditorChange($event)"
(ready)="onReady($event)"
(focus)="onFocus($event)"
(blur)="onBlur($event)"
(contentDom)="onContentDom($event)"
(fileUploadRequest)="onFileUploadRequest($event)"
(fileUploadResponse)="onFileUploadResponse($event)"
(paste)="onPaste($event)"
(drop)="onDrop($event)"
debounce="500">
</ckeditor>
`,
})
export class Editor{
content: string = '<p>Some html</p>';
config: any = {
allowedContent: true,
toolbar: [['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', 'Link', '-', 'CreatePlaceholder']],
removePlugins: 'elementspath',
resize_enabled: false,
extraPlugins: 'font,divarea,placeholder',
contentsCss: ["body {font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;}"],
autoParagraph: false,
enterMode: 2
};
}
In the config
, we define the options that are displayed in the editor. We can define the buttons shown in the toolbar with the toolbar
property. We put all the options in the array. Full list of controls that you can display in the toolbar is at https://ckeditor.com/latest/samples/old/toolbar/toolbar.html. We have one array per toolbar row. We can also remove plugins that we don’t need with the removePlugins
option, and add more plugins with the extraPlugins
options. Plugins have to be installed separately if the full installation is not used like I did above.
It can handle model changes with the change
handler. You can run code when the editor is loaded with the ready
event. paste
handler allows you to run code after something is pasted.
Now to change the language of the editor, we can add the language
option to the config
object. Use the standard language codes listed at https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes. For example, to set the language to Spanish, we put the following in the config
object:
language: 'es',
It is very simple.
Another thing you might want to do with your editor is to customize the content of some of the elements. To do this, we can make an Angular directive and add some event handlers to the CKEDITOR
object to set the content you want. For example, if you want to set labels of the inputs of the Placeholder plugin that we have above, we run:
ng g directive changeEditorOptions
Then we get:
import { Directive } from '@angular/core';
@Directive({
selector: '[appChangeEditorOptions]'
})
export class ChangeEditorOptionsDirective {
constructor() {}
}
Next, we reference the CKEDITOR
global object we got from putting <script src=”https://cdn.ckeditor.com/4.5.11/full-all/ckeditor.js"></script>
in index.html
and hook some event handlers to the CKEDITOR
object and put that into the constructor
. After that we get the following:
import { Directive } from '@angular/core';
declare let CKEDITOR: any;
declare let $: any;
@Directive({
selector: '[appChangeEditorOptions]'
})
export class ChangeEditorOptionsDirective{
constructor() {
CKEDITOR.on(
'instanceReady',
(ev) => {
}
);
CKEDITOR.on('dialogDefinition', (event) => {
if ('placeholder' === event.data.name) {
}
});
}}
instanceReady
event is fired when the editor is loaded, and dialogDefinition
is loaded when the user opens a dialog box from the editor.
To add a drop down to our Placeholders dialog box and change the placeholders available in the placeholder plugin we can add it into the dialogDefinition
event handler. We can make our changes by adding the following:
if ('placeholder' === event.data.name) {
const input = event.data.definition.getContents('info').get('name');
const dialog = event.data.definition;
input.type = 'select';
input.items = [
['First Name', 'first_name'],
['Last Name', 'last_name'],
['Link', 'link'],
];}
The code works like this: after opening the dialog, we get the name
element in the info
section by calling event.data.definition.getContents(‘info’).get(‘name’);
Then we change the input to select
by setting that to input.type
and we populate the input by assigning the array to input.items
. The first item in the array is what is displayed and the second is the value.
At the end, we have:
import { Directive } from '@angular/core';
declare let CKEDITOR: any;
declare let $: any;
@Directive({
selector: '[appChangeEditorOptions]'
})
export class ChangeEditorOptionsDirective{
constructor() {
CKEDITOR.on(
'instanceReady',
(ev) => {
}
);
CKEDITOR.on('dialogDefinition', (event) => {
if ('placeholder' === event.data.name) {
const input = event.data.definition.getContents('info').get('name');
const dialog = event.data.definition;
input.type = 'select';
input.items = [
['First Name', 'first_name'],
['Last Name', 'last_name'],
['Link', 'link'],
];
}
}); }
}
To run script with the editor loads, we put the following in the instanceReady
handler:
const $script = document.createElement('script'),
$editor_instance = CKEDITOR.instances[ev.editor.name];
$script.src = '//path/to/your/script';
$script.onload = () => {
//run code after load
}};
$editor_instance.document.getHead().$.appendChild($script);
Now we have:
import { Directive } from '@angular/core';
declare const CKEDITOR: any;
@Directive({
selector: '[appChangeEditorOptions]'
})
export class ChangeEditorOptionsDirective{
constructor() {
CKEDITOR.on(
'instanceReady',
(ev) => {
const $script = document.createElement('script'),
$editor_instance = CKEDITOR.instances[ev.editor.name];
$script.src = '//path/to/your/script';
$script.onload = () => {
//run code after load
}}; $editor_instance.document.getHead().$.appendChild($script);
}
);
CKEDITOR.on('dialogDefinition', (event) => {
if ('placeholder' === event.data.name) {
const input = event.data.definition.getContents('info').get('name');
const dialog = event.data.definition;
input.type = 'select';
input.items = [
['First Name', 'first_name'],
['Last Name', 'last_name'],
['Link', 'link'],
];
}
}); }
}
Since CKEDITOR
is global, we need to put declare
keyword before it so that the compiler won’t think it’s undefined.
Apply this directive to your editor by adding:
import { Component } from '@angular/core';
@Component({
selector: 'sample',
template: `
<div appChangeEditorOptions>
<ckeditor
[(ngModel)]="content"
[config]="config"
[readonly]="false"
(change)="onChange($event)"
(editorChange)="onEditorChange($event)"
(ready)="onReady($event)"
(focus)="onFocus($event)"
(blur)="onBlur($event)"
(fileUploadRequest)="onFileUploadRequest($event)"
(fileUploadResponse)="onFileUploadResponse($event)"
(paste)="onPaste($event)"
(drop)="onDrop($event)"
debounce="500">
</ckeditor>
</div>
`,
})
export class Editor {
content: string = '<p>Some html</p>';
config: any = {
allowedContent: true,
toolbar: [['Bold', 'Italic', 'Underline', '-', 'NumberedList', 'BulletedList', 'Link', '-', 'CreatePlaceholder']],
removePlugins: 'elementspath',
resize_enabled: false,
extraPlugins: 'font,divarea,placeholder',
contentsCss: ["body {font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;}"],
autoParagraph: false,
enterMode: 2
};
}
Also include, ChangeEditorOptionsDirective
in the app.module.ts
into the declaration
section of the module if it’s not there already.