martin
martin

Reputation: 96891

Angular and dynamically added CKEditors break

I'm having an issue with dynamically added CKEditors rendred via NgFor with [email protected].

Live demo is available here.

@Directive({
    selector: 'textarea'
})
class CKEditor {
    constructor(_elm: ElementRef) {
        CKEDITOR.replace(_elm.nativeElement);
    }
}

@Component({
    selector: 'my-app',
})
@View({
    directives: [NgFor, CKEditor],
    template: `
      <div *ng-for="#item of items">
        {{ item }}: <textarea>{{ item }}</textarea>
      </div>
      <button (click)="addItem()">Add</button>`
})
class AppComponent {
    items = ['default_0', 'default_1'];

    constructor() {
        this.addItem();
    }

    addItem() {
        var id = 'ckeditor_inst_' + this.items.length;
        this.items.push(id);
    }
}

You can see three CKEditors that work correctly. Then click "Add" button at the bottom and it breaks the last CKEditor in the container in a way that you can even write to it and if you press any toolbar button it throws:

Uncaught TypeError: Cannot read property 'getSelection' of undefined.

It's interesting that only the last CKEditor is broken, the other two work. It seems like Angular2 somehow manipulates with the last element which breaks the CKEditor.

I remember using the same way of adding new CKEditors in [email protected] and I think it worked there but maybe I just didn't notice. Version [email protected] is the same.

Upvotes: 2

Views: 1953

Answers (2)

martin
martin

Reputation: 96891

This issue has been fixed in beta-15, see:

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

Upvotes: 2

oleq
oleq

Reputation: 15895

Your integration uses Classic CKEditor because of wysiwygarea plugin, which enables editing in an <iframe> (i.e. to avoid CSS collision with the webpage).

The drawback of such implementation is that once you detach (and re–attach) such <iframe> from DOM (like Angular does each time you add a new item), its internal document gets "broken". By broken I mean that document.body is loaded from scratch, losing your content, CKEditor bootstrap code, JS references, etc. and eventually making the whole editor instance useless.

So the problem lies in the way this view is being rendered:

@View({
    directives: [NgFor, CKEditor],
    template: `
      <div *ng-for="#item of items">
        {{ item }}: <textarea>{{ item }}</textarea>
      </div>
      <button (click)="addItem()">Add</button>`
})

And I see three solutions to this problem:

  • Clean solution: Force Angular to not re–render the entire items collection when a new item is added.
  • Tricky soluction: Use Inline CKEditor, which works in <div contenteditable="true" /> div instead of <iframe>...<body contenteditable="true" /></iframe> and is immune to mutations in DOM.
  • Lazy and slow solution: Stick to current integration but destroy all CKEDITOR.instances (instance.destroy()) before a new item is added and then re–initialise them CKEDITOR.replace().

Upvotes: 3

Related Questions