How to Add Rich Text Editor to an Angular App with Localization

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.

Subscribe to my email list now at http://jauyeung.net/subscribe/ . Follow me on Twitter at https://twitter.com/AuMayeung

Image for post
Image for post

Written by

Web developer. Subscribe to my email list now at http://jauyeung.net/subscribe/. Email me at hohanga@gmail.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store