Integrate TinyMCE with Angular
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:
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:
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:
- Include the
<editor>
configuration within theFormGroup
- 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:
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.
Updated at Tue May 4, 2021
Akshay Pandey 11/5/2023
I am sorry but TinyMCE is garbage. I have tried every approach but it keeps giving this stupid Can't bind to 'init' since it isn't a known property of 'editor' ERROR. It's useless.
Armin Zia 11/6/2023 ADMIN
Hi Akshay,
I've been using TinyMCE in production for years, it's a great solution and one of the best editors available. The error message you mentioned suggests you haven't imported the EditorModule correctly. Look at the docs, you should be able to set up your environment easily.
Chip Bourne 1/11/2024
Is this example dated now? I cannot find where EditorSettings is in my tinyMce angular distro r10.2.0??
Thanks
Armin Zia 1/17/2024 ADMIN
Hello Chip, thanks for reporting.
Yes, this is outdated. It's EditorOptions now.
Maya 2/27/2024
That sounds fantastic! Integrating TinyMCE into Angular projects can greatly enhance content editing capabilities. Looking forward to your future post on writing custom plugins for TinyMCE as well. Keep up the great work!