22

In Angular 2 how can I make two way data binding with a contenteditable div?

<div class="editable" contenteditable="true">
    <h1>Text Field</h1>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra felis in sem porta feugiat.</p>
 </div>
Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
Codrut Tapu
  • 231
  • 1
  • 2
  • 6
  • Possible duplicate of [How to use \[(ngModel)\] on div's contenteditable in angular2?](http://stackoverflow.com/questions/35378087/how-to-use-ngmodel-on-divs-contenteditable-in-angular2) – tobek Dec 21 '16 at 01:49

7 Answers7

16

I've adapted Isetty's answer to work with the release version of Angular 2.0, now it is available. Apart from working with the release version, I've also added a keyup event and used textContent rather than innerText, because that suites my application better. You may wish to change these things.

import {Directive, ElementRef, Input, Output, EventEmitter, OnChanges} from "@angular/core";

@Directive({
    selector: '[contenteditableModel]',
    host: {
        '(blur)': 'onEdit()',
        '(keyup)': 'onEdit()'
    }
})

export class ContentEditableDirective implements OnChanges {
    @Input('contenteditableModel') model: any;
    @Output('contenteditableModelChange') update = new EventEmitter();

    constructor(
        private elementRef: ElementRef
    ) {
        console.log('ContentEditableDirective.constructor');
    }

    ngOnChanges(changes) {
        console.log('ContentEditableDirective.ngOnChanges');
        console.log(changes);
        if (changes.model.isFirstChange())
            this.refreshView();
    }

    onEdit() {
        console.log('ContentEditableDirective.onEdit');
        var value = this.elementRef.nativeElement.innerText
        this.update.emit(value)
    }

    private refreshView() {
        console.log('ContentEditableDirective.refreshView');
        this.elementRef.nativeElement.textContent = this.model
    }
}
David
  • 1,179
  • 12
  • 15
  • David by any chance you got a plunker with a working version? – Chris Tarasovs Jun 04 '17 at 17:36
  • 2
    I tried to do a TSLint defaults compatible version of this but I don't have a solution for fixing no-input-rename and no-output-rename. See: https://gist.github.com/zamber/f2d15337c245285d498d9a6b94de3117 I've also added some nice things, like enter to accept, esc to cancel and emitting on full value change, not each keydown. – zamber Aug 16 '17 at 08:30
2

Angular doesn't have a built-in ControlValueAccessor for contenteditable case. I've written a tiny little library that properly implements it for Angular 4 and above, and it also works with Internet Explorer 11 using MutationObserver fallback:

https://www.npmjs.com/package/@tinkoff/angular-contenteditable-accessor

Here's the code that ControlValueAccessor has to have:

registerOnChange(onChange: (value: string) => void) {
  ...
}

registerOnTouched(onTouched: () => void) {
  ...
}

setDisabledState(disabled: boolean) {
  ...
}

writeValue(value: string | null) {
  ...
}

Keep in mind that you should also watch out for dangerous pasted content so it would be wise to sanitize it in (drop)/(paste) events

waterplea
  • 3,462
  • 5
  • 31
  • 47
1

Please refer this code. It will work you i think.

app.ts

@Component({
    selector: 'test-component'
})
@View({
    directives: [ContenteditableModel]
    template: `
        <h1 contenteditable="true" [(contenteditableModel)]="someObj.someProperty"></h1>
        {{someObj | json}}
    `
})
export class TestCmp {
    someObj = {someProperty: "startValue"}
}

contenteditableModel.ts:

import {Directive, ElementRef, Input, Output} from "angular2/core";
import {EventEmitter} from "angular2/src/facade/async";
import {OnChanges} from "angular2/core";
import {isPropertyUpdated} from "angular2/src/common/forms/directives/shared";

@Directive({
    selector: '[contenteditableModel]',
    host: {
        '(blur)': 'onBlur()'
    }
})
export class ContenteditableModel implements OnChanges {
    @Input('contenteditableModel') model: any;
    @Output('contenteditableModelChange') update = new EventEmitter();

    private lastViewModel: any;


    constructor(private elRef: ElementRef) {
    }

    ngOnChanges(changes) {
        if (isPropertyUpdated(changes, this.lastViewModel)) {
            this.lastViewModel = this.model
            this.refreshView()
        }
    }

    onBlur() {
        var value = this.elRef.nativeElement.innerText
        this.lastViewModel = value
        this.update.emit(value)
    }

    private refreshView() {
        this.elRef.nativeElement.innerText = this.model
    }
}

For the extra inputs i found a link for you. https://www.namekdev.net/2016/01/two-way-binding-to-contenteditable-element-in-angular-2/

Ravi Teja Kumar Isetty
  • 1,559
  • 4
  • 21
  • 39
  • Thank you! You're code works but i have two problems: -my someObj.someProperty contains a lot of html (10-15 tags). After i modify something the entire html becomes plain text -to edit the text i use a text editor - Summernote Here is a part of my json object: [ { "dim": 4, "id": 1, "moduleType": { "id": 1, "name": "text-module", "content": "

    Text Field 1

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra felis in sem porta feugiat.

    " } }, The moduleType.content it's what i want to edit
    – Codrut Tapu Aug 18 '16 at 17:46
1

in angular 2 [(ngModel)] used for two way data binding.

the answer of your question is already here How to use [(ngModel)] on div's contenteditable in angular2? check this out and let me know if it is working for you or not.

Community
  • 1
  • 1
Vinay Pandya
  • 3,020
  • 2
  • 26
  • 42
1

To work properly, it is necessary to implement ControlValueAccessor for the directive contenteditable. See my solution: ng-contenteditable.

ktretyak
  • 27,251
  • 11
  • 40
  • 63
0

Angular 4/2 (Typescript) with dynamic editable:

// Imports
import { Component} from '@angular/core';

@Component({
    selector: 'discussion',
    template: `                                 
    <div class="details">
        <p class="time">Wednesday 14 Nov, 2016 10.13PM</p>
        <p class="text" name="discussion" 
            [contentEditable]="editable" 
            [ngClass]="{ 'editable': editable }" 
            (blur)="uDiscussion()" 
            (click)="eDiscussion($event)" 
            (input)="discussion = $event.target.innerText" 
        >{{ discussion }}</p>
    </div>
    <div class="dropdown">
        <a href="#" data-toggle="dropdown" class="dropdown-toggle">
            <i class="fa fa-ellipsis-v"></i>
        </a>
        <ul class="dropdown-menu">
            <li><a  (click)="eDiscussion($event)" >Edit</a></li>
            <li><a  (click)="dDiscussion()" >Delete</a></li>
        </ul>
    </div>`,
    styles: [`.editable {
        white-space: pre-wrap;
        border: 1px solid coral;
        width: 200px;
        min-height: 20px;
    }`]
})

export class DiscussionComponent {

    constructor(){}

    public discussion: string = "Test string";
    public editable: boolean = false;

    dDiscussion(){
        console.log("delete");
    }

    eDiscussion(event: any){
        // on click this will set 'contentEditable' to true 
        // and add 'editable' class for styling.
        this.editable = true;
    }

    uDiscussion(event: any){
        // on blur set 'contentEditable' to false 
        // and remove class 'editable' and log new values
        this.editable = false;
        console.log("this.discussion");
        console.log(this.discussion);
    }

}
Kaleem Ullah
  • 6,799
  • 3
  • 42
  • 47
0

the blur event and innerHTML attribute.

In .ts:

getContent(innerText){
  this.content = innerText;
}

in .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>
Sahil Ralkar
  • 2,331
  • 23
  • 25