web-dev-qa-db-fra.com

Comment utiliser [(ngModel)] sur le contenu de div dans content de angular2?

J'essaie d'utiliser ngModel pour le contenu d'entrée contenteditable de bind div, comme suit:

<div id="replyiput" class="btn-input"  [(ngModel)]="replyContent"  contenteditable="true" data-text="type..." style="outline: none;"    ></div> 

mais cela 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 ''
31
Kim Wong

NgModeloczekuje, l'élément związany będzie posiadał właściwość valuename__, której divname__s nie mają. Dlatego masz błąd No value accessor.

Cliquez ici pour afficher le texte intégral textContent(zamiast valuename__) i zdarzenia inputname__:

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

Nie wiem, czy zdarzenie inputjest obsługiwane nous wszystkich przeglądarkach dla contenteditablename__. Zamiast tego możesz zawsze powiązać się z jakimś wydarzeniem z klawiatury.

75
Mark Rajcok

Réponse mise à jour (2017-10-09):

Maintenant, j'ai ng-contenteditable module. Sa compatibilité avec les formes angulaires.

Ancienne réponse (2017-05-11): Dans mon cas, je peux simplement faire:

<div
  contenteditable="true"
  (input)="post.postTitle = $event.target.innerText"
  >{{ postTitle }}</div>

post - c'est un objet avec la propriété postTitle.

Pour la première fois, après ngOnInit() et obtenir post à partir du backend, j'ai défini this.postTitle = post.postTitle dans mon composant.

10
ktretyak

Travailler Plunkr ici http://plnkr.co/edit/j9fDFc , mais le code pertinent ci-dessous.


La liaison et la mise à jour manuelle de textContent ne fonctionnaient pas pour moi, elle ne gérait pas les sauts de ligne (dans Chrome, le fait de taper après un saut de ligne ramène le curseur au début), mais j'ai pu le faire fonctionner à l'aide d'une directive de modèle contenteditable de https://www.namekdev.net/2016/01/two-way-binding-to-contenteditable-element-in-angular-2/ .

Je l'ai modifié pour gérer le texte brut multiligne (avec \ns, pas <br>s) à l'aide de white-space: pre-wrap, et l'ai mis à jour pour utiliser keyup au lieu de blur. Notez que certaines solutions à ce problème utilisent l'événement input qui n'est pas encore pris en charge sur les éléments IE ou Edge sur contenteditable.

Voici le code:

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"
  }
}

Seulement testé dans Chrome et FF sous Linux jusqu'à présent.

7
tobek

Voici une autre version , basée sur la réponse de @ tobek, qui supporte également le langage HTML et le collage:

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;
  }
}
6
Rene Hamburger

J'ai bricolé cette solution et vais maintenant utiliser la solution suivante dans mon projet:

<div #topicTitle contenteditable="true" [textContent]="model" (input)="model=topicTitle.innerText"></div>

Je préfère utiliser la variable de référence du modèle au truc "$ event".

Lien connexe: https://angular.io/guide/user-input#get-user-input-from-a-template-reference-variable

3
Flo

Voici une solution simple si ce que vous souhaitez lier est une chaîne, aucun événement n'est nécessaire. Il suffit de placer une entrée de zone de texte dans la cellule du tableau et d’y accéder Puis formatez 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>
0
Isaac