14-day Cloud trial
Start today. For free.

One editor. 50+ features. Zero constraints. After your trial, retain the advanced features.

Try Professional Plan for FREE
PricingContact Us
Log InGet Started Free

How to create Angular reactive forms with a rich text editor

May 30th, 2023

7 min read

The Angular log being investigated with a magnifying glass

Written by

Simon Fjeldså

Category

How-to Use TinyMCE

Angular is a framework that aims to cater to a developer’s every need, with the overall goal of becoming the go-to framework for large single-page applications. To reach that goal, it incorporates many features that simplify the development process, including Angular reactive forms.

This article looks at how to integrate rich text editing into an Angular reactive form using TinyMCE.

In addition to getting the Angular WYSIWYG editor going inside a form, it explains how to build in custom validation, as well as use skinning to match the style of the editor with the popular Angular Material component library.

If you just want to get started with the basic setup in Angular, check out our previous article on how to add the TinyMCE rich text editor to a simple Angular project.

Here’s a complete example on CodeSandbox:

Angular reactive form prerequisites

Since this post focuses on using the tinymce-angular component, some prior knowledge of Angular is assumed. Although knowledge of the Angular Material component isn't necessary, it could be helpful for setting up the Angular reactive form with effective styling.

To view the full source and the associated boilerplate code for this project, open up the embedded CodeSandbox example above.

Starting the Angular reactive form demo

To get started, you'll need to install the tinymce-angular component with your package manager of choice.

  1. Create a new Angular project in your development environment:

ng new --defaults --skip-git forms-angular-rte
  1. Change into the newly created project directory, and then install TinyMCE. You can make use of your package manager of choice for this. For example:

# If you use npm
cd forms-angular-rte
npm install @tinymce/tinymce-angular

# If you use Yarn
cd forms-angular-rte
yarn add @tinymce/tinymce-angular

The tinymce-angular component is a wrapper around the TinyMCE rich text editor and thus requires TinyMCE in order to work. By default, the component will load TinyMCE from Tiny Cloud, which is the simplest and quickest way to get going. 

The only thing you need for this to work is a free API key which anyone can get at tiny.cloud (including a 14 day trial of Premium plugins). The alternative is to self-host TinyMCE and make it available, together with the assets it requires. Refer to the documentation for more information on self-hosting.

  1. Open the /src/app/app.module.ts file, and make the tinymce-angular component available in the project’s module by importing EditorModule from the tinymce-angular package:

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 {}
  1. Open the app.component.html file, and introduce the editor component tag. Provide your Tiny Cloud API key, and insert the API key as the value of the apiKey option:

  apiKey="your-api-key"
  [init]="{ 
    base_url: '/tinymce',
    suffix: '.min',
    plugins: 'lists link image table code help wordcount',
    menubar: false,
    min_height: 150
   }"
></editor>

The init property accepts an object specifying the editor configuration. The base_url and the suffix options are included to make sure TinyMCE loads when the Angular application loads.

  1. Change back into the top level of your Angular demo, and open angular.json. Add TinyMCE to the assets property under the projects and test options:

            "assets": [
              "src/favicon.ico",
              "src/assets",
              { "glob": "**/*", "input": "node_modules/tinymce", "output": "/tinymce/" }
            ],
  1. Since the goal is to load TinyMCE when the Angular app loads, in the angular.json file, include a reference to TinyMCE under the script tag options as well:

                "scripts": [
                  "node_modules/tinymce/tinymce.min.js"
                ]
  2. Save the changes, and test out the Angular app by running the command to start it up:

ng serve --open

In this example, the WordCount plugin has been added. In addition to displaying stats for word and character usage to the user in the status bar, this plugin also exposes some very useful APIs that you’ll use later, for checking the number of characters in the text area.

Once the Angular app has started running on the local host address with the ng serve –open command, you can make changes to the configuration, and see changes to the Angular reactive form right away.

Constructing the form

The first step in creating the form is making use of some of Angular’s design features to separate out the form. The material components are designed for accessibility, and are useful to include in your Angular application. The demo makes use of these material components:

  1. Install and configure the Angular Material package with the following command:

ng add @angular/material

The add command activates a series of questions: choosing a prebuilt theme, setting global Angular typography styles, and including Angular animations. While you can select whichever options work best for your form, the demo makes use of the following decisions:

? Choose a prebuilt theme name, or "custom" for a custom theme: Deep Purple/Amber  [ Preview: <a href="https://material.angular.io?theme=deeppurple-amber">https://material.angular.io?theme=deeppurple-amber</a> ]
? Set up global Angular Material typography styles? No
? Include the Angular animations module? Include and enable animations

UPDATE package.json (1158 bytes)
✔ Packages installed successfully.
UPDATE src/app/app.module.ts (497 bytes)
UPDATE angular.json (3219 bytes)
UPDATE src/index.html (561 bytes)
UPDATE src/styles.css (181 bytes)
  1. In the app.module.ts file, include the card module as an import:

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { EditorModule } from "@tinymce/tinymce-angular";
import { AppComponent } from "./app.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { MatCardModule } from "@angular/material/card";

And in the imports list:

@NgModule({
  declarations: [
    AppComponent
  ],

  imports: [
    BrowserModule,
    EditorModule,
    BrowserAnimationsModule,
    MatCardModule,
  ], 
  1. In the app.component.html file, wrap the editor component in the following material elements:

<mat-card class="blog mat-elevation-z3">  <mat-card-title>TinyMCE & Angular</mat-card-title>  
<mat-card-content>

  <editor
    apiKey="no-api-key"
    [init]="{ 
    base_url: '/tinymce',
    suffix: '.min',
      plugins: 'lists link image table code help wordcount',
      menubar: false,
      min_height: 150
    }"
  ></editor>
  </mat-card-content>
</mat-card>
  1. Check on the new appearance of the editor. The Angular Material theming helps shape the Angular reactive form with TinyMCE in the following sections.

The angular form starting to work using the material card style in Angular

  1. A form needs a submit button, and you can set this up by including the button element from the Angular Material component. Import the MatButtonModule into the app.module.ts file:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EditorModule } from '@tinymce/tinymce-angular';
import { AppComponent } from './app.component';
import { MatCardModule } from "@angular/material/card";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatCardModule } from "@angular/material/card";
import { MatButtonModule } from "@angular/material/button";

…

@NgModule({
  declarations: [
    AppComponent,
  ],

  imports: [
    BrowserModule,
    EditorModule,
    MatCardModule,
    MatButtonModule,
],
  1. Include the button element in the app.component.html file just after the editor element:

<mat-card-actions align="end">
      <button mat-raised-button>      Submit     </button> {" "}
</mat-card-actions>;
  1. Save the change, and there is now a submit button below the editor for the reactive form:

The Angular form gaining the button

Constructing the Angular reactive form

The first steps to take for the form is importing some specific Angular modules, and then modifying the app.component.ts file:

  1. Import the following modules into the app.module.ts file:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EditorModule } from '@tinymce/tinymce-angular';
import { MatCardModule } from "@angular/material/card";
import { MatButtonModule } from "@angular/material/button";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MatInputModule } from "@angular/material/input";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";

…

  imports: [
    BrowserModule,
    EditorModule,
    MatCardModule,
    MatButtonModule,
    FormsModule,
    ReactiveFormsModule,
    MatInputModule,
    BrowserAnimationsModule,
  ],
  1.  In the app.component.ts file, add in the following components to start building the form as a reactive form, where all fields are bound to a model:

import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { AsyncSubject, Subject } from "rxjs";
  1. Configure the form controls by adding the following FormComponent class. This class content replaces everything after the component (remove the AppComponent export class). The FormGroupDirective associates the form with a FormGroup instance (something like the “root” of the model):

class FormComponent {
  private editorSubject: Subject<any> = new AsyncSubject();
  public myForm = new FormGroup({
    title: new FormControl("", Validators.required),
    body: new FormControl("", Validators.required, maxLength(this.editorSubject, 10))
});

  handleEditorInit(e: any) {
    this.editorSubject.next(e.editor);
    this.editorSubject.complete();
  }

  public onSubmit() {
    console.log("Submitted!");
  }
}

export { FormComponent }

The model here, configured with the public myForm option, could be named anything. The important part to be aware of here is the FormControlDirective. This directive makes sure that each field of our form associates with the model. It allows each field in the form to work with FormControl instances (subcomponents of the model).

Building the maximum length validation

The body value in the FormComponent class has a maxLength option, and the next step is to set up the validation with the max length option. Besides tracking values between the view and model, reactive forms also provide a simple way to track the validity of the form and its fields. 

Validation is useful for controlling how the user interface works, and to prevent excess data entering the database (one way to set up and ensure security with sanitization). As an example, notice how it uses the “Validators.required” validator provided by Angular. This is simply a function that marks the form as invalid until a user has entered some text into each field, at which point the form enters a valid state.

The function takes a controller and emits either errors or null (representing no error). It makes use of the WordCount plugin API to retrieve a character count of the editor, which gives an accurate result for the number of characters in the editor. 

You can read more about different validators provided by Angular forms documentation.

  1. In the same directory as the app.component.ts file, create a new TypeScript file called maxlengths.validator.ts:

touch maxlength.validator.ts
  1. Open the new file, and include the following content:

import {
  AbstractControl,
  AsyncValidatorFn,
  ValidationErrors,
} from "@angular/forms";
import { Observable, Subject } from "rxjs";
import { map } from "rxjs/operators";

const maxLength = (
  editorSubject: Subject<any>,
  characterLimit: number
): AsyncValidatorFn => {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    return editorSubject.pipe(
      map((editor) => {
        const characterCount = editor.plugins.wordcount.body.getCharacterCount();
        return characterCount <= characterLimit
          ? null
          : {
              maxlength: {
                requiredLength: characterLimit,
                actualLength: characterCount,
              },
            };
      })
    );
  };
};

export { maxLength };
  1. Save the changes, and then return to the app.components.ts file to import the new Maximum length validator:

import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { AsyncSubject, Subject } from "rxjs";
import { maxLength } from "./maxlength.validators";

Completing the Angular reactive form

There are a few more modules to set up before the demo is complete. The HTML of the form demo also needs to be built up.

  1. Check on the app.module.ts file, and update the file to import the new FormComponent in place of the AppComponent:

import { FormComponent } from './app.component'; //replace AppComponent with FormComponent

@NgModule({
    declarations: [
        FormComponent,
],

  imports: [
    BrowserModule,
    EditorModule,
    MatCardModule,
    MatButtonModule,
    FormsModule,
    ReactiveFormsModule,
    MatInputModule,
    BrowserAnimationsModule,
  ],

  providers: [],
    bootstrap: [FormComponent]
})
  1. Change to the app.component.html and include the following elements to complete the form setup and configuration:

<mat-card class="blog mat-elevation-z3">
  <mat-card-title>TinyMCE & Angular</mat-card-title>
  <mat-card-content>
    <form (ngSubmit)="onSubmit()" [formGroup]="myForm">
      <mat-form-field class="title">
        <input
          matInput
          [formControl]="myForm.controls.title"
          placeholder="Title"
          type="text"/>

  <mat-error *ngIf="myForm.controls.title.hasError('required')">
        Title is required
      </mat-error>
</mat-form-field>

  <div class="mat-form-field-wrapper">
<editor><!-- Editor content omitted, changed in next step --></editor>
<div class="mat-form-field-subscript-wrapper">

<mat-error *ngIf="myForm.controls.body.hasError('maxlength')">
      Your post exceeds exceeds the character limit
      {{ myForm.controls.body.getError('maxlength').actualLength }} / 
      {{ myForm.controls.body.getError('maxlength').requiredLength }}
    </mat-error>
    <mat-error *ngIf="myForm.controls.body.touched && myForm.controls.body.hasError('required')">
      This form is required
    </mat-error>
  </div>
  </div>
</form>
</mat-card-content>

  <mat-card-actions align="end">
    <button mat-raised-button (click)="onSubmit()" [disabled]="!myForm.valid">
      Submit
    </button>
  </mat-card-actions>
</mat-card>
  1. Modify the editor component with the following configuration:

<editor
    apiKey="your-api-key"
    [formControl]="myForm.controls.body"
    class="mat-elevation-z1"
    (onInit)="handleEditorInit($event)"
    [init]="{ 
      base_url: '/tinymce',
      suffix: '.min',
      toolbar: false,
      plugins: 'lists link image table code help wordcount',
      menubar: false,
      min_height: 150,
      placeholder: 'Form body content'
    }"
></editor>
  1. Save the changes, and then check on the change in the demo in the browser:

The complete form running with TinyMCE in a reactive form

Improving the Angular reactive form design

The current design of the form could be improved with some adjustments to the angular JSON content, and the Angular app’s CSS. 

The Material package also has themes that could be optimized to show how you can get the TinyMCE rich text editor to look and feel as if it were from the Angular Material component library. (That’s another example of how easy it is to implement solutions with TinyMCE.)

  1. Open the app.component.css file, and include the following CSS content. This will change the style of the title element and adjust the width of the form:

.title {
    width: 100%;
    margin-bottom: 15px;
  }

  .blog {
    width: 70%;
    max-width: 600px;
    margin: auto;
  }
  1. For the Material package, set up a new .scss file alongside the other component files:

touch styles.scss
  1. Include the following content, and save the change:

// src/styles.scss

@use "@angular/material" as mat;

@include mat.core();

$my-app-primary: mat.define-palette(mat.$indigo-palette);
$my-app-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$my-app-warn: mat.define-palette(mat.$red-palette);

$my-app-theme: mat.define-light-theme(

  (
    color: (
      primary: $my-app-primary,
      accent: $my-app-accent,
      warn: $my-app-warn,
    ),
  )
);

@include mat.all-component-themes($my-app-theme);

html,

body {
  height: 100%;
}

body {
  margin: 0;
  font-family: Roboto, "Helvetica Neue", sans-serif;
}
  1. Introduce the new .scss file into the @Component class: 

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css', '/styles.scss']
})
  1. Save the change, and check on the new style for TinyMCE in a reactive form with Angular:

The TinyMCE Angular Reactive form with an improved design included.

Wrapping up

You now have an Angular reactive form with powerful editing capabilities provided by the TinyMCE rich text editor, together with a sleek Material look and custom input validation.

From here, the possibilities are endless. What are you doing with TinyMCE in Angular? Connect with us on Twitter and let us know what you’re up to.

Also remember to check out the full range of TinyMCE rich text editor plugins and how they can help you provide the most productive editing experience for your users.

AngularTinyMCEConfiguration
bySimon Fjeldså

Simon is an Engineer at Tiny, working on an array of features such as plugins and framework integrations for TinyMCE. Powered by coffee.

Related Articles

  • How-to Use TinyMCEApr 16th, 2024

    How to enable a Bootstrap WYSIWYG editor: a step-by-step guide

Join 100,000+ developers who get regular tips & updates from the Tiny team.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Tiny logo

Stay Connected

SOC2 compliance badge

Products

TinyMCEDriveMoxieManager
© Copyright 2024 Tiny Technologies Inc.

TinyMCE® and Tiny® are registered trademarks of Tiny Technologies, Inc.