How to Add Rich Text Editor to an Angular App with Localization
Tags: javascript, angular, webdev, tutorial
Published: 2020-01-08 03:14:27

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

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](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:

```bash
npm i ng2-ckeditor
```

Then we include the following in the `imports` array of your module:

```typescript
import { CKEditorModule } from 'ng2-ckeditor';  
import { FormsModule } from '@angular/forms';  
   
@NgModule({  
  // ...  
  imports: [CKEditorModule, FormsModule],  
  // ...  
})  
export class AppModule {}
```

In `index.html` , we add:

```html
<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:

```typescript
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](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](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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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.