Integrate TinyMCE with Angular

Tue Apr 13, 2021

5 comments 4018 reads

Angular / Tutorials / TinyMCE

armin-zia

Content editors are an essential part of the web. In this post, I'll show you how to integrate TinyMCE into Angular projects. In a future post, I'll show you how to write custom plugins for TinyMCE too.

As a web developer, you'll find yourself in need of a rich text editor at some point. You have two options: Markdown or HTML. Markdown has become increasingly popular over the years because of its simplicity, ease of use, and reliability. Many applications including websites and content management systems prefer Markdown and many non-technical people like writers and doctors have adopted it too. You can easily learn Markdown and get started with it in minutes. But this post is about HTML editors, I'll write about Markdown editors and processors in future posts.

There are many WYSIWYG (What You See Is What You Get) editors for both Markdown and HTML out there, including but not limited to:

They're all great and there's no such thing as the best editor, it really depends on your project requirements and what you want to achieve. I've been using TinyMCE for years and it has served me very well. Setting up TinyMCE is super easy, check out the Quickstart guide to get up and running. TinyMCE integrates with many frameworks, which means you don't need to develop custom wrappers. Check out the integrations guide for more information, you can integrate TinyMCE with:

  • AngularJS
  • Angular 5+
  • Bootstrap
  • Django
  • jQuery
  • Node.js + Express
  • Rails
  • React
  • Swing
  • Vue
  • Web Components
  • WordPress

Getting Started

I'm assuming you have Node.js, NPM, and Angular CLI installed.

npm install -g @angular/cli

You should be able to follow along with Angular 5+ but I'm using these versions:

  • node v14.6.1
  • npm v6.14.12
  • Angular CLI v11.2.11

Let's create an Angular project first:

ng new tinymce-example --routing=false --style=scss

This will create a new project named tinymce-example, with SCSS for styling and no routing, to keep things simple. I'm going to use Bootstrap for UI components, let's install that too:

npm install --save bootstrap

Open styles.scss and import Bootstrap:

@import "~bootstrap/scss/bootstrap";

I prefer importing the SCSS file but alternatively, you could open angular.json and import the CSS distribution instead:

"styles": [
  "node_modules/bootstrap/dist/css/bootstrap.min.css"
]

Finally, if you want to use Tiny Cloud you're going to need an API key. Sign up for a free account and grab your key from the dashboard. add your Tiny Cloud API key to the environment.ts file:

export const environment = {
  production: false,
  tinyMceApiKey: 'YOUT_API_KEY'
};

Don't forget to do the same in environment.prod.ts. If you want to use the self-hosted version, you don't need an API key.

Installing TinyMCE

You have two options for importing TinyMCE: Tiny Cloud, or TinyMCE Self-hosted. Using Tiny Cloud is great because if you wanted to include TinyMCE within the application it's a pretty hefty package. But you might prefer to use a self-hosted version. Let's look at both options.

Tiny Cloud

To use Tiny Cloud, all you need is the Angular integration package:

npm install --save @tinymce/tinymce-angular

Open app.module.ts and import the EditorModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EditorModule } from '@tinymce/tinymce-angular';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, EditorModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Now you can use the editor component by including your API key:

<editor apiKey="your-api-key" [init]={ /* your other settings */ } ></editor>

TinyMCE Self-hosted

To use a self-hosted version you need the tinymce package too:

npm install --save tinymce

In the AppModule, you also need to provide the TINYMCE_SCRIPT_SRC for injecting the script:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, EditorModule],
  providers: [
    { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

The tinymce.min.js is going to be loaded from the node_modules directory. Lastly, you need to import the TinyMCE assets in angular.json:

"assets": [
  "src/favicon.ico",
  "src/assets",
  {
    "glob": "**/*",
    "input": "node_modules/tinymce",
    "output": "/tinymce/"
  }
]

We're including everything from the nodue_modules/tinymce directory (the glob pattern). Now you can use the editor component like this:

<editor
  [init]="{
    base_url: '/tinymce',
    suffix: '.min',
    height: 500,
    menubar: false,
    plugins: [
      'advlist autolink lists link image charmap print preview anchor',
      'searchreplace visualblocks code fullscreen',
      'insertdatetime media table paste code help wordcount'
    ],
    toolbar:
      'undo redo | formatselect | bold italic backcolor | \
      alignleft aligncenter alignright alignjustify | \
      bullist numlist outdent indent | removeformat | help'
  }">
</editor>

Notice the base_url property. It's not needed when using Tiny Cloud, but if you want to include TinyMCE within your application, it is required so that TinyMCE knows where to look for the assets.

Creating an editor

Let's start with the basics, I'm going to use the self-hosted approach. Open app.component.html and add the following markup:

<editor
  [init]="{
    base_url: '/tinymce',
    suffix: '.min',
    height: 500,
    menubar: false,
    plugins: [
      'advlist autolink lists link image charmap print preview anchor',
      'searchreplace visualblocks code fullscreen',
      'insertdatetime media table paste code help wordcount'
    ],
    toolbar:
      'undo redo | formatselect | bold italic backcolor | \
      alignleft aligncenter alignright alignjustify | \
      bullist numlist outdent indent | removeformat | help'
  }">
</editor>

This is the most basic setup, the editor component comes from the EditorModule. We pass a configuration object to the init property and that's it. Now if you start the app using npm start you should see something like this:

TinyMCE Editor

Fabulous 🦄. The configuration object is self-explanatory, check out the Basic setup guide for more information. You can customize the editor, the toolbar, add plugins, etc. Now let's see how we can configure the editor in TypeScript and improve the user experience.

Configuring the editor in code

The tinymce package includes TypeScript definitions which is great. Let's extract the configuration to the component class. Open app.component.ts and add the following:

import { Component, OnInit } from '@angular/core';
import { Editor, EditorSettings } from 'tinymce';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  editor: Editor;
  settings: EditorSettings;

  ngOnInit() {
    this.setupEditor();
  }

  setupEditor() {
    this.settings = {
      base_url: '/tinymce',
      suffix: '.min',
      height: 500,
      menubar: false,
      toolbar: 'undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help',
      plugins: 'advlist autolink lists link image charmap print preview anchor searchreplace visualblocks code fullscreen insertdatetime media table paste code help wordcount',
      external_plugins: {},
      setup: (editor: Editor) => {
        this.editor = editor;
      }
    };
  }
}

The AppComponent implements the OnInit interface, and in the ngOnInit event we call the setupEditor method where we configure the editor. Notice the setup callback, you can use it to grab the editor instance and work with it in TypeScript code. Open app.component.html and change the markup:

<editor [init]="settings"></editor>

Great, now we're configuring the editor programmatically.

Displaying a loading indicator

Whether you're using Tiny Cloud or a self-hosted version, it's nice to show an indicator while the editor is loading. How do we do that? TinyMCE provides many events that you can use. Browser events, editor events, and plugin events. Check out the documentation to learn more. There's an onInit event on the editor that we can use to display a loading indicator. Open app.component.ts and add the following:

// other imports
import { AsyncSubject, Subject } from 'rxjs';

export class AppComponent implements OnInit {
  // other properties
  editorSubject: Subject<any> = new AsyncSubject();

  onEditorInit(event: any) {
    this.editorSubject.next(event.editor);
    this.editorSubject.complete();
  }
}

Open app.component.html and change the markup too:

<p *ngIf="(editorSubject | async) === null">Loading the editor...</p>
<editor [init]="settings" (onInit)="onEditorInit($event)"></editor>

We're using an AsyncSubject to handle the onInit event. In the onEditorInit method, we pass the editor instance to the subject and complete it. In the template, we display the loading text if the async subject is null. So while the editor is loading we see the loading indicator, and as soon as the editor instance is available, the subject is completed and the loading indicator is removed. Run the app again and you should see something like the following:

TinyMCE Loading Indicator

Accessing the editor content

You can get the editor content in different formats: raw, text, html, and tree. Let's say you need the content in text format, add the following method to app.component.ts:

getText() {
  const text = this.editor.getContent({ format: 'text' });
  console.info(text);
}

Remember that the editor property is of type Editor, defined by the tinymce package.

Integrating with Reactive Forms

To use TinyMCE with Reactive Forms, all you need is to:

  1. Include the <editor> configuration within the FormGroup
  2. Add the formControlName directive to the editor configuration. For example:
<editor [formControlName]="summary" [init]="{ /* editor config */ }"></editor>

That's it, now you have TinyMCE in your Angular project. TinyMCE has many premium and open-source plugins, make sure to check them out. You can easily write your custom plugins too.

Content Styling

How do we customize the CSS used in the editor? There are two handy properties for that: content_css, and content_style. The first one is used to inject stylesheets into the editor, and the other is used to supply custom CSS. Say you're using Bootstrap and you want the UI components to be available in the editor, and you also want to add padding to the editable area. You can configure the editor like this:

setupEditor() {
  this.settings = {
    baseUrl: "/tinymce",
    suffix: ".min",
    height: 300,
    menubar: false,
    content_css: "https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css",
    content_style: "body { padding: 15px; }",
    // other settings
  };
}

Syntax Highlighting

TinyMCE has a built-in plugin called Code Sample. This plugin gives you a modal dialog where you choose a programming language from a dropdown, and enter a code block. By default, TinyMCE uses Prism.js for syntax highlighting. Make sure to check out the official documentation to learn more. First, you want to add the codesample plugin to your configuration:

setupEditor() {
  this.settings = {
    // other configurations
    plugins: 'codesample',
    toolbar: 'codesample'
  }
}

You need to add prism.js and prism.css to your page in order to get the syntax-highlighted code created by the Code Sample plugin. You can install Prism.js locally, or use a CDN. Update index.html with the following:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- other tags -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/prism.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism.min.css" rel="stylesheet">
  </head>
  <body>
    <!-- body content -->
  </body>
</html>

Here's a screenshot of the Code Sample plugin in action:

TinyMCE Code Sample Plugin

The Code Sample plugin has 2 options with which you can configure the list of supported languages and whether to use a global Prism.js version or not. Using a global version is preferable if you don't want to use the one bundled inside the codesample plugin. This allows for a custom version of Prism.js, including additional languages, to be used.

setupEditor() {
  this.settings = {
    // other properties
    codesample_global_prismjs: true,
    codesample_languages: [
      { text: "HTML/XML", value: "markup" },
      { text: "Markdown", value: "markdown,md" },
      { text: "Javascript", value: "javascript,js" },
      { text: "TypeScript", value: "typescript,ts" },
      { text: "JSON", value: "json,webmanifest" },
      { text: "CSS", value: "css" },
      { text: "Sass (Scss)", value: "scss" },
      { text: "C#", value: "csharp" },
      { text: "ASP.NET (C#)", value: "aspnet" },
      { text: "SQL", value: "sql" },
      { text: "Dart", value: "dart" },
      { text: "PowerShell", value: "powershell" },
      { text: "GraphQL", value: "graphql" },
      { text: "YAML", value: "yaml" },
      { text: "Git", value: "git" },
      { text: ".ignore", value: "ignore,gitignore,hgignore,npmignore" },
    ],
  };
}

That should be a good starting point for integrating TinyMCE into your Angular projects. In future posts, I'll show you how to write custom plugins, and how to do file uploads with the Image plugin.

Source Code

You can find the sample project on Github for a quick test run. Happy coding.

Posted in Angular

Tagged Tutorials , TinyMCE