Comment utiliser [(ngModel)] sur les div's contenteditable en angular2?
j'essaie d'utiliser ngModel pour lier dans les deux sens le contenu d'entrée contenteditable de div comme suit:
<div id="replyiput" class="btn-input" [(ngModel)]="replyContent" contenteditable="true" data-text="type..." style="outline: none;" ></div>
mais ça ne fonctionne pas et une erreur se produit:
EXCEPTION: No value accessor for '' in [ddd in PostContent@64:141]
app.bundle.js:33898 ORIGINAL EXCEPTION: No value accessor for ''
6 réponses
NgModel
attend l'élément lié à avoir un value
propriété, qui div
s n'ont pas. C'est pour ça que vous avez la No value accessor
erreur.
vous pouvez configurer votre propre banque de données d'événements et de propriétés équivalentes en utilisant le textContent
propriété (au lieu de value
) et input
événement:
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(); }
}
je ne sais pas si le input
event est supporté sur tous les navigateurs pour contenteditable
. Vous avez toujours la possibilité de les lier à des un événement clavier à la place.
mise à Jour de la réponse (2017-10-09):
maintenant j'ai ng-contenteditable module. Sa compatibilité avec les formes angulaires.
Vieux-réponse (2017-05-11): Dans mon cas, je peux simple à faire:
<div
contenteditable="true"
(input)="post.postTitle = $event.target.innerText"
>{{ postTitle }}</div>
Où post
- c'est objet avec propriété postTitle
.
Première fois, après ngOnInit()
et obtenir post
depuis le backend, j'ai mis this.postTitle = post.postTitle
dans mon composant.
Travail Plunkr ici http://plnkr.co/edit/j9fDFc, mais le code ci-dessous.
liaison et mise à jour manuelle textContent
ne fonctionnait pas pour moi, il ne gère pas les ruptures de ligne (dans Chrome, taper après une rupture de ligne saute le curseur de retour au début) mais j'ai été en mesure de le faire fonctionner en utilisant une directive de modèle contenteditable de https://www.namekdev.net/2016/01/two-way-binding-to-contenteditable-element-in-angular-2/.
j'ai modifié pour gérer plusieurs lignes de texte brut avec \n
, pas <br>
s) en utilisant white-space: pre-wrap
, et mis à jour pour utiliser keyup
au lieu de blur
. Notez que certaines solutions à ce problème utilisent le input
événement qui n'est pas pris en charge sur IE ou une Arête contenteditable
éléments encore.
Voici le code:
la Directive
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
}
}
Utilisation:
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"
}
}
testé seulement dans Chrome et FF sur Linux jusqu'à présent.
Voici une autre version, basé sur la réponse de @tobek, qui supporte aussi html et coller:
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;
}
}
j'ai bricolé avec ces solutions et j'utiliserai la solution suivante dans mon projet maintenant:
<div #topicTitle contenteditable="true" [textContent]="model" (input)="model=topicTitle.innerText"></div>
je préfère utiliser la variable de référence du template au truc "$event".
lien: https://angular.io/guide/user-input#get-user-input-from-a-template-reference-variable
Voici une solution simple si vous êtes une liaison est une chaîne de caractères, pas d'événements nécessaires. Il suffit de mettre une boîte de texte entrée à l'intérieur de la cellule de table et de lier à cela. Formatez ensuite votre zone de texte en transparent
HTML:
<tr *ngFor="let x of tableList">
<td>
<input type="text" [(ngModel)]="x.value" [ngModelOptions]="{standalone: true}">
</td>
</tr>