After a long development period with a lot of twists and turns, Angular 2 was finally released as a non-beta version in September 2016. What better way to celebrate this than to integrate your favorite Angular WYSIWYG editor with it?
In this blog post, we will go through how to get started using TinyMCE with Angular 2. It will not be a fully functional “production ready” implementation, but more of a simple guide on how to get started and something to continue working on.
Let’s do this!
For TinyMCE 5: See how to add TinyMCE 5 to a simple Angular project.
Prerequisites
This won’t be an especially technically complicated blog post, but I do expect you to have a working understanding of:
- JavaScript and Angular 2
- How to use the terminal/command line
I also take for granted that you have node and NPM installed on your computer. We will be using TypeScript (as almost all Angular 2 applications are written in) so while I think any JavaScript developer should be alright getting through the minimalistic code samples, I don’t want you to be scared if you see some unfamiliar syntax. Don’t start hyperventilating because of the types, it’s ok, they won’t bite.
Setting up
To spend as little time as possible on setup and configuration we will use the marvelous angular-cli tool for this project. You don’t have to use this, but it is a really nice way to quickly get started doing the fun stuff and spend almost no time on boring bootstrapping (it’s kind of like flying business class, just walk past the line and put on your complimentary slippers).
If you have an Angular 2 project already set up in some other way, you should be able to follow along, although you will have to create some files by yourself and keep track of some more declarations.
Anyway, let’s get going by simply installing the angular-cli package globally with NPM by running the command:
npm install @angular/cli@1.0.0-beta.32.3 --global
The version number - the @1.0.0-beta.32.3 part of the command - simply locks the install to the version of the cli that was used when writing this blog post. You can omit the version number to use the latest version of angular-cli if you want to, but be aware that some things might have changed making this post harder to follow along with.
Now that we have angular-cli installed, let’s start this!
Creating the project
We will use angular-cli to generate a new project using the following command:
ng new NgTiny
NgTiny is simply the name of the project, you can exchange this to whatever you feel like. After angular-cli has finished creating a bunch of files, initialized a git repository and installed tooling packages with NPM we can CD into our newly created project:
cd NgTiny
Installing TinyMCE
The next step is to install TinyMCE into our project, which we simply do with npm
and the following command:
npm install tinymce@4.5.3 --save
Just like the angular-cli install above the version is optional but recommended if you are going to follow along with the blog post. After that has finished installing we are ready to start writing some code, so open up the project directory in your IDE/Editor of choice.
Getting the skin
For the TinyMCE editor to work it needs a skin, which simply consists of some font and CSS files used by the editor. The easiest way to get this working in an angular-cli project is to simply copy the skins
folder from the TinyMCE directory in node_modules
to the src/assets
directory, either by manually copying over the files in the finder/file explorer, or by using the terminal with a command something like this:
For Macos and Linux, use:
cp -r node_modules/tinymce/skins src/assets/skins
For Windows, use:
xcopy /I /E node_modules/tinymce/skins src/assets/skins
Then, when initializing a TinyMCE instance, just add the skin_url setting with the correct url like this:
tinymce.init({
// other settings...
skin_url: "assets/skins/lightgray", // ...more settings
});
Angular-cli will then be smart enough to copy along the files in the assets folder both while you are developing with the dev server using the ng serve command
, but also when you build your project with ng build
. Very handy indeed.
Creating a naive TinyEditorComponent
The angular-cli can also help us here, by generating a new component. Run this command:
ng generate component TinyEditor
TinyEditor is, just like NgTiny
before, the name of the component and can be changed to something else if you want to.
Angular-cli will create four files for us:
- A TypeScript file with your component code,
tiny-editor.component.ts
- An HTML file with the components template,
tiny-editor.component.html
- A CSS file for any styling for the component you want to add,
tiny-editor.component.css
- A test file,
tiny-editor.component.spec.ts
We will only be using the TypeScript component file during this blogpost, so you can delete the other three files. You might be asking yourself why we even used the angular-cli tool to generate the component if we just delete three out of four files directly. While we could have just created the component file ourselves, the angular-cli tool also takes care of importing the component into out app.module.ts
file for us – something that I always forget to do otherwise (I spent too much time with the Angular 2 beta, I think).
Add typing
As Angular2 is mostly meant to be written in TypeScript (at least that is what you get when using angular-cli) we should get started by adding the following line to the tiny-editor.component.ts file that we generated in the previous step.
declare var tinymce: any;
By doing this we are declaring that we are going to use something called tinymce and that it doesn’t matter what type it has, we’re basically just telling TypeScript to chill out and not stress so much about tinymce. It’s ok, we’ve got this.
The code
I’ll start by adding the code of the TinyComponent all in one go and then thoroughly go through it underneath.
tiny-editor.component.ts
import {
Component,
AfterViewInit,
EventEmitter,
OnDestroy,
Input,
Output,
} from "@angular/core";
import "tinymce";
import "tinymce/themes/modern";
import "tinymce/plugins/table";
import "tinymce/plugins/link";
declare var tinymce: any;
@Component({
selector: "app-tiny-editor",
template: `<textarea id="{{elementId}}"></textarea>`,
})
export class TinyEditorComponent implements AfterViewInit, OnDestroy {
@Input() elementId: String;
@Output() onEditorContentChange = new EventEmitter();
editor;
ngAfterViewInit() {
tinymce.init({
selector: "#" + this.elementId,
plugins: ["link", "table"],
skin_url: "assets/skins/lightgray",
setup: (editor) => {
this.editor = editor;
editor.on("keyup change", () => {
const content = editor.getContent();
this.onEditorContentChange.emit(content);
});
},
});
}
ngOnDestroy() {
tinymce.remove(this.editor);
}
}
We start out with the usual dependency imports:
import {
Component,
AfterViewInit,
EventEmitter,
OnDestroy,
Input,
Output,
} from "@angular/core";
import "tinymce";
import "tinymce/themes/modern";
import "tinymce/plugins/table";
import "tinymce/plugins/link";
declare var tinymce: any;
First, we have the pretty boring @angular/core packages - the usual Component decorator, Input, Output, EventEmitter and the AfterViewInit and OnDestroy lifetime hooks.
Under these we first import tinymce, followed by importing the modern theme that comes bundled with the tinymce npm package. The modern theme is the standard theme used by TinyMCE and will be used by default if you do not configure it to use some other theme (like the Inlite theme). If you do set it up to use some other theme you would of course also have to change this import as well.
After that, we import the table and link plugins. All plugins that you use has to be imported in the same way.
Lastly in this section we have the tinymce type declaration we talked about earlier, just added in to keep TypeScript calm when we call on the tinymce global later on in the code.
After the import we have the component decorator:
@Component({
selector: 'app-tiny-editor',
template: `<textarea id="{{elementId}}"></textarea>`
})
This should be pretty obvious stuff, the selector is created by angular-cli from the name we gave our component and the template is just inlined (and not linked in from the html file, because we deleted that, remember?). If we wanted to be able to use TinyMCE with the inline mode we would have to have some kind of logic in the template where it would be a div (or some other element) instead of a textarea if some state is set, but as we are keeping it simple here we’ll just use a textarea.
Following is the TinyEditorComponent class itself:
export class TinyEditorComponent implements AfterViewInit, OnDestroy {
@Input() elementId: String;
@Output() onEditorContentChange = new EventEmitter();
editor;
ngAfterViewInit() {
tinymce.init({
selector: "#" + this.elementId,
plugins: ["link", "table"],
skin_url: "assets/skins/lightgray",
setup: (editor) => {
this.editor = editor;
editor.on("keyup change", () => {
const content = editor.getContent();
this.onEditorContentChange.emit(content);
});
},
});
}
ngOnDestroy() {
tinymce.remove(this.editor);
}
}
Let's start with the @Input and @Output at the beginning.
@Input() elementId: String;
declares the elementId
property that will be added to the textarea in the template, and then used in the init function of TinyMCE to initialize the editor on that textarea. We also define that this is a String, because when in TypeScript land use the types.
@Output() onEditorContentChange = new EventEmitter();
declares the output event that is emitted on the keyup- and change events (more on this later). This also declares that the type of the data the EventEmitter will send out will be a String.
With this input and output, the tiny-editor component will be used in a parent component like this:
<app-tiny-editor
[elementId]="'my-editor'"
(onEditorContentChange)="keyupHandler($event)"
>
</app-tiny-editor>
The elementId
is sent in as a string and a handler is registered to take care of the emitted output from onEditorContentChange
.
Beneath the input and output the editor
variable is just a variable in which we will save a reference to the TinyMCE editor instance, used in cleanup in the ngOnDestroy lifetime hook.
Now for the meat of the sandwich:
ngAfterViewInit() {
tinymce.init({
selector: '#' + this.elementId,
plugins: ['link', 'table'],
skin_url: 'assets/skins/lightgray',
setup: editor => {
this.editor = editor;
editor.on('keyup change', () => {
const content = editor.getContent();
this.onEditorContentChange.emit(content);
});
}
});
}
We initialize our TinyMCE instance in the ngAfterViewInit
lifetime hook to make sure the textarea we are going to select as a target for the editor has already been rendered out on the page. In the init we first set our selector to the input variable this.elementId
, then we declare which plugins we are going to use.
We then define the path to our skins catalog – which should be under the assets/skins directory if you have followed this blog post and is using angular-cli.
In the setup function, we first save the reference to our editor in the this.editor variable, then register an event handler on the keyup and change events of the editor. These could be exchanged for other events, see our documentation for more information. In the event handler, we then get the content from the editor and emit it on our onEditorContentChange
output.
Lastly, we have the ngOnDestroy
lifetime hook that just removes the TinyMCE instance from the page whenever the component is destroyed:
ngOnDestroy() {
tinymce.remove(this.editor);
}
Wrapping up
I hope you have found some kind of value in reading this blog post and that you feel empowered to start coding on your own Angular 2 application using TinyMCE for your content creating needs.
Happy hacking, good luck!