¿Cómo usar[(ngModel)] en div''s contenteditable en angular2?
ionic2 (6)
Aquí hay otra versión , basada en la respuesta de @ tobek, que también admite html y pegar:
import {
Directive, ElementRef, Input, Output, EventEmitter, SimpleChanges, OnChanges,
HostListener, Sanitizer, SecurityContext
} from ''@angular/core'';
@Directive({
selector: ''[contenteditableModel]''
})
export class ContenteditableDirective implements OnChanges {
/** Model */
@Input() contenteditableModel: string;
@Output() contenteditableModelChange?= new EventEmitter();
/** Allow (sanitized) html */
@Input() contenteditableHtml?: boolean = false;
constructor(
private elRef: ElementRef,
private sanitizer: Sanitizer
) { }
ngOnChanges(changes: SimpleChanges) {
if (changes[''contenteditableModel'']) {
// On init: if contenteditableModel is empty, read from DOM in case the element has content
if (changes[''contenteditableModel''].isFirstChange() && !this.contenteditableModel) {
this.onInput(true);
}
this.refreshView();
}
}
@HostListener(''input'') // input event would be sufficient, but isn''t supported by IE
@HostListener(''blur'') // additional fallback
@HostListener(''keyup'') onInput(trim = false) {
let value = this.elRef.nativeElement[this.getProperty()];
if (trim) {
value = value.replace(/^[/n/s]+/, '''');
value = value.replace(/[/n/s]+$/, '''');
}
this.contenteditableModelChange.emit(value);
}
@HostListener(''paste'') onPaste() {
this.onInput();
if (!this.contenteditableHtml) {
// For text-only contenteditable, remove pasted HTML.
// 1 tick wait is required for DOM update
setTimeout(() => {
if (this.elRef.nativeElement.innerHTML !== this.elRef.nativeElement.innerText) {
this.elRef.nativeElement.innerHTML = this.elRef.nativeElement.innerText;
}
});
}
}
private refreshView() {
const newContent = this.sanitize(this.contenteditableModel);
// Only refresh if content changed to avoid cursor loss
// (as ngOnChanges can be triggered an additional time by onInput())
if (newContent !== this.elRef.nativeElement[this.getProperty()]) {
this.elRef.nativeElement[this.getProperty()] = newContent;
}
}
private getProperty(): string {
return this.contenteditableHtml ? ''innerHTML'' : ''innerText'';
}
private sanitize(content: string): string {
return this.contenteditableHtml ? this.sanitizer.sanitize(SecurityContext.HTML, content) : content;
}
}
Estoy tratando de usar ngModel para vincular de dos maneras el contenido de entrada contento de div de la siguiente manera:
<div id="replyiput" class="btn-input" [(ngModel)]="replyContent" contenteditable="true" data-text="type..." style="outline: none;" ></div>
pero no funciona y se produce un error:
EXCEPTION: No value accessor for '''' in [ddd in PostContent@64:141]
app.bundle.js:33898 ORIGINAL EXCEPTION: No value accessor for ''''
Aquí hay una solución simple si lo que está vinculando es una cadena, no se necesitan eventos. Simplemente coloque una entrada de cuadro de texto dentro de la celda de la tabla y vincúlela. Luego formatee su cuadro de texto a transparente
HTML:
<tr *ngFor="let x of tableList">
<td>
<input type="text" [(ngModel)]="x.value" [ngModelOptions]="{standalone: true}">
</td>
</tr>
He jugueteado con estas soluciones y utilizaré la siguiente solución en mi proyecto ahora:
<div #topicTitle contenteditable="true" [textContent]="model" (input)="model=topicTitle.innerText"></div>
Prefiero usar la variable de referencia de plantilla para el material "$ event".
Enlace relacionado: https://angular.io/guide/user-input#get-user-input-from-a-template-reference-variable
Trabajando Plunkr aquí http://plnkr.co/edit/j9fDFc , pero el código relevante a continuación.
El enlace y la actualización manual de
textContent
no funcionaba para mí, no maneja los saltos de línea (en Chrome, escribir después de que un salto de línea salta el cursor al principio) pero pude hacerlo funcionar usando una directiva de modelo contento de
https://www.namekdev.net/2016/01/two-way-binding-to-contenteditable-element-in-angular-2/
.
Lo modifiqué para manejar texto sin formato de varias líneas (con
/n
, no con s) usando
white-space: pre-wrap
keyup
, y lo actualicé para usar
keyup
lugar de
blur
.
Tenga en cuenta que algunas soluciones a este problema utilizan el evento de
input
que aún no es compatible con IE o Edge en elementos
contenteditable
.
Aquí está el código:
Directiva:
import {Directive, ElementRef, Input, Output, EventEmitter, SimpleChanges} from ''angular2/core'';
@Directive({
selector: ''[contenteditableModel]'',
host: {
''(keyup)'': ''onKeyup()''
}
})
export class ContenteditableModel {
@Input(''contenteditableModel'') model: string;
@Output(''contenteditableModelChange'') update = new EventEmitter();
/**
* By updating this property on keyup, and checking against it during
* ngOnChanges, we can rule out change events fired by our own onKeyup.
* Ideally we would not have to check against the whole string on every
* change, could possibly store a flag during onKeyup and test against that
* flag in ngOnChanges, but implementation details of Angular change detection
* cycle might make this not work in some edge cases?
*/
private lastViewModel: string;
constructor(private elRef: ElementRef) {
}
ngOnChanges(changes: SimpleChanges) {
if (changes[''model''] && changes[''model''].currentValue !== this.lastViewModel) {
this.lastViewModel = this.model;
this.refreshView();
}
}
/** This should probably be debounced. */
onKeyup() {
var value = this.elRef.nativeElement.innerText;
this.lastViewModel = value;
this.update.emit(value);
}
private refreshView() {
this.elRef.nativeElement.innerText = this.model
}
}
Uso:
import {Component} from ''angular2/core''
import {ContenteditableModel} from ''./contenteditable-model''
@Component({
selector: ''my-app'',
providers: [],
directives: [ContenteditableModel],
styles: [
`div {
white-space: pre-wrap;
/* just for looks: */
border: 1px solid coral;
width: 200px;
min-height: 100px;
margin-bottom: 20px;
}`
],
template: `
<b>contenteditable:</b>
<div contenteditable="true" [(contenteditableModel)]="text"></div>
<b>Output:</b>
<div>{{text}}</div>
<b>Input:</b><br>
<button (click)="text=''Success!''">Set model to "Success!"</button>
`
})
export class App {
text: string;
constructor() {
this.text = "This works/nwith multiple/n/nlines"
}
}
Solo probado en Chrome y FF en Linux hasta ahora.
NgModel
espera que el elemento enlazado tenga una propiedad de
value
, que los
div
s no tienen.
Es por eso que obtiene el error de
No value accessor
.
Puede configurar su propia propiedad equivalente y enlace de datos de eventos utilizando la propiedad
textContent
(en lugar del
value
) y el evento de
input
:
import {Component} from ''angular2/core'';
@Component({
selector: ''my-app'',
template: `{{title}}
<div contenteditable="true"
[textContent]="model" (input)="model=$event.target.textContent"></div>
<p>{{model}}`
})
export class AppComponent {
title = ''Angular 2 RC.4'';
model = ''some text'';
constructor() { console.clear(); }
}
No sé si el evento de
input
es compatible con todos los navegadores para
contenteditable
.
Siempre se puede vincular a algún evento de teclado.
Respuesta actualizada (2017-10-09) :
Ahora tengo un módulo ng-contenteditable . Su compatibilidad con formas angulares.
Antigua respuesta (2017-05-11) : en mi caso, puedo hacer lo siguiente:
<div
contenteditable="true"
(input)="post.postTitle = $event.target.innerText"
>{{ postTitle }}</div>
Donde
post
: es un objeto con propiedad
postTitle
.
La primera vez, después de
ngOnInit()
y obtener la
post
desde el backend, configuro
this.postTitle = post.postTitle
en mi componente.