3

I'm trying to embed tinymce into my website which is built using Angular2. Following is my component:

export class myComponent implements OnInit {
    //some code

    constructor(private af: AngularFire) {
    // some code
    }

    ngOnInit():any {
    tinymce.init(
        {
            selector: ".tinymce",
        });
    }
}

And in side my html, there is:

<textarea class="tinymce" rows="15"></textarea>

But there is error saying "Cannot find name 'tinymce'" but I have already include

<script src='//cdn.tinymce.com/4/tinymce.min.js'></script>

inside the head of html. Did I do something wrong? Is my initialization incorrect?

1
  • are you using a module loader like webpack or systemJS? Commented Sep 10, 2016 at 14:35

3 Answers 3

5

Here's what I've used for myself, on RC6. First I'll show the usage:

<h1>The Editor</h1>
<textarea htmlEditor [(ngModel)]="txt"></textarea>
<h1>The HTML Source</h1>
<textarea [(ngModel)]="txt"></textarea>
<h1>The Rendered HTML</h1>
<div [innerHTML]="txt"></div>

So the usage is pretty simple and straightforward, the HTML result of the editor is moved to the value of the textarea (I trigger the update on blur event)

And here's the directive definition (Typescript):

import {
    Directive,
    OnDestroy,
    AfterViewInit,
    Provider,
    forwardRef,
    HostBinding
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { DomSanitizer } from '@angular/platform-browser';

declare var tinymce: any;

export const TinyMceValueAccessor: Provider = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => TinyMceDirective2),
    multi: true
};

// Tinymce directive
@Directive({
    selector: '[htmlEditor]',
    providers: [TinyMceValueAccessor]
})

export class TinyMceDirective2 implements OnDestroy, AfterViewInit, ControlValueAccessor {
    static nextUniqueId = 0;
    @HostBinding('attr.data-tinymce-uniqueid') uniqueId;

    onTouchedCallback: () => void = () => { };
    onChangeCallback: (_: any) => void = () => { };
    innerValue;
    init = false;

    constructor(private sanitizer: DomSanitizer) {
        this.uniqueId = `tinymce-host-${TinyMceDirective2.nextUniqueId++}`;
    }

    //get accessor
    get value(): any {
        return this.innerValue;
    };

    //set accessor including call the onchange callback
    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    ngAfterViewInit(): void {
        console.log('tinymce');
        tinymce.init({
            selector: `[data-tinymce-uniqueid=${this.uniqueId}]`,
            schema: 'html5',
            setup: ed => {
                ed.on('init', ed2 => {
                    if (this.innerValue) ed2.target.setContent(this.innerValue);
                    this.init = true;
                });
            }
        });

        // I chose to send an update on blur, you may choose otherwise
        tinymce.activeEditor.on('blur', () => this.updateValue());
    }

    updateValue() {
        const content = tinymce.activeEditor.getContent();
        this.value = this.sanitizer.bypassSecurityTrustHtml(content);
    }

    writeValue(value): void {
        if (value !== this.innerValue) {
            this.innerValue = value;
            if (this.init && value) tinymce.activeEditor.setContent(value);
        }
    }

    registerOnChange(fn): void {
        this.onChangeCallback = fn;
    }

    registerOnTouched(fn): void {
        this.onTouchedCallback = fn;
    }

    ngOnDestroy(): void {
        if (this.init) tinymce.remove(`[data-tinymce-uniqueid=${this.uniqueId}]`);
    }
}

Some highlights:

  • I'm using NG_VALUE_ACCESSOR to provide for two way binding using ngModel
  • I'm assigning a unique id to a custom attribute on the host element so that tinymce only initializes that specific element and no others.
  • I'm sending value updates only on blur events, but you may use a different strategy, for example using debounce time.
  • I'm using DomSanitizer to bypass sanitization, since tinymce sometimes outputs html which triggers Angular 2 sanitization.
Sign up to request clarification or add additional context in comments.

1 Comment

Been trying this one out. I'll get an error on page reload saying inline template:25:0 caused by: tinymce is not defined - any idea as to why?
3

I have a nice working plunker using Angular2 and TinyMCE using

<script src="//cdn.tinymce.com/4/tinymce.min.js"></script>

http://plnkr.co/edit/E5Yzk9KT9nSWlPU6i1ZK?p=preview

Comments

2

I'm on Angular v4 final. Here's how I implemented the TinyMCE editor:

tiny-editor.component.ts

imports...

declare var tinymce: any;

const contentAccessor = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TinyEditorComponent),
  multi: true
};

@Component({
  selector: 'app-tiny-editor',
  styleUrls: ['./tiny-editor.component.scss'],
  providers: [contentAccessor],
  template: `
      <textarea id="{{elementId}}"></textarea>
  `
})
export class TinyEditorComponent implements AfterViewInit, ControlValueAccessor {
  private onTouch: Function;
  private onModelChange: Function;

  registerOnTouched(fn) {
    this.onTouch = fn;
  }
  registerOnChange(fn) {
    this.onModelChange = fn;
  }

  writeValue(value) {
    this.editorContent = value;
  }

  @Input() elementId: String;
  @Output() onEditorContentChange = new EventEmitter();

  constructor() { }

  editor;
  editorContent: string = null;

  ngAfterViewInit() {
    tinymce.init({
      selector: `#${this.elementId}`,
      plugins: ['link', 'table'],
      skin_url: '../assets/skins/lightgray',
      schema: 'html5',
      setup: editor => {
        this.editor = editor;
        editor.on('keyup change', () => {
          const tinyContent = editor.getContent();
          this.editorContent = tinyContent;
          this.onEditorContentChange.emit(tinyContent);
          this.onModelChange(tinyContent);
          this.onTouch();
          console.log(tinyContent);
        });
      }
    });
  }
}

create-article.component.html

<form [formGroup]="form" (ngSubmit)="onSubmit()">
<app-tiny-editor
   formControlName="content"
   [elementId]="'my-editor'">
</app-tiny-editor>

This seems to be working, although when rendering out the form.value there's some delay in the content showing.

3 Comments

As of about May, I've switched over to the Editor from PrimeNG which uses Quill.js as its editor. The angular integration seems to be much better.
Thanks for the update :) I was going to use TinyMCE but seemed a huge bother to get the value out again. Good call on PrimeNG I am liking it thanks.
This throw a error: text-editor.component.ts:59 Uncaught TypeError: _this.onModelChange is not a function on this.onModelChange(content);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.