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 ''
27
demandé sur danday74 2016-02-13 12:11:15

6 réponses

NgModel attend l'élément lié à avoir un value propriété, qui divs 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(); }
}

Plunker

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.

51
répondu Mark Rajcok 2016-07-05 15:00:56

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>

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.

9
répondu ktretyak 2017-10-09 18:10:02

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.

7
répondu tobek 2016-12-21 03:05:29

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;
  }
}
4
répondu Rene Hamburger 2017-06-28 12:45:03

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

2
répondu Flo 2017-11-06 21:03:50

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>
-1
répondu Isaac 2018-06-08 19:19:33