Angular 2 で ViewChild と Renderer2 を使って contenteditable な div 要素を扱う
Angular 2 で contenteditable
な div
要素と [(ngModel)]
を使う方法
そこで、contenteditable
な div
要素と ngModel
を双方向バインディングするには、カスタムディレクティブを作成する必要があります。
カスタムディレクティブの作成
- 以下のコードのように、
EditableDivDirective
という名前のカスタムディレクティブを作成します。
import { Directive, Input, Output, EventEmitter, ElementRef, HostListener, Renderer } from '@angular/core';
@Directive({
selector: 'div[contenteditable]',
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableDivDirective), multi: true }
]
})
export class EditableDivDirective implements ControlValueAccessor {
@Input() ngModel: any;
@Output() ngModelChange = new EventEmitter();
constructor(private _elRef: ElementRef, private _renderer: Renderer) { }
writeValue(value: any) {
if (!value) { value = ''; }
this._renderer.setElementProperty(this._elRef.nativeElement, 'innerHTML', value);
}
registerOnChange(fn: any) { this._onChange = fn; }
registerOnTouched(fn: any) { this._onTouched = fn; }
@HostListener('input') onInput() { this.onChange(); }
@HostListener('blur') onBlur() { this._onTouched(); }
private onChange() {
if (this._onChange) { this._onChange(this._elRef.nativeElement.innerHTML); }
this.ngModelChange.emit(this._elRef.nativeElement.innerHTML);
}
private _onChange: Function;
private _onTouched: Function;
}
このディレクティブは、以下の機能を提供します。
onChange
メソッド:div
要素の内容をngModel
プロパティとngModelChange
イベントに反映します。onBlur
イベントリスナー:div
要素がフォーカスアウトしたときに_onTouched
メソッドを呼び出します。onInput
イベントリスナー:div
要素の内容が変更されたときにonChange
メソッドを呼び出します。registerOnTouched
メソッド:div
要素がフォーカスアウトしたときに呼び出される関数を登録します。writeValue
メソッド:div
要素に値を設定します。ngModelChange
出力イベント:div
要素の内容が変更されたときに発生します。ngModel
入力プロパティ: バインディングするモデルプロパティを指定します。
- 作成した
EditableDivDirective
をコンポーネントのテンプレートで使用します。
<div contenteditable [(ngModel)]="myContent" editableDiv></div>
この例では、myContent
プロパティと div
要素の内容が双方向バインディングされます。
- コンポーネントのクラスで、
myContent
プロパティを定義します。
myContent: string = '初期値';
ngModel
ディレクティブの他の機能も、EditableDivDirective
で使用できます。contenteditable
属性に加えて、spellcheck
やautocorrect
などの他の属性もdiv
要素に設定できます。
この方法は、contenteditable
な div
要素と ngModel
を双方向バインディングするための一般的な方法です。必要に応じて、この方法を拡張して、独自の要件を満たすことができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Angular 2 ContentEditable Example</title>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/core.mjs"></script>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/common.mjs"></script>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/compiler.mjs"></script>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/platform-browser.mjs"></script>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/platform-browser-dynamic.mjs"></script>
<script src="https://unpkg.com/@angular/[email protected]/fesm2015/forms.mjs"></script>
<script src="app.component.ts"></script>
</head>
<body>
<app-root></app-root>
</body>
</html>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div contenteditable [(ngModel)]="myContent" editableDiv></div>
<p>内容: {{ myContent }}</p>
`
})
export class AppComponent {
myContent: string = '初期値';
}
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { EditableDivDirective } from './editable-div.directive';
@NgModule({
declarations: [
AppComponent,
EditableDivDirective
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
editable-div.directive.ts
import { Directive, Input, Output, EventEmitter, ElementRef, HostListener, Renderer } from '@angular/core';
@Directive({
selector: 'div[contenteditable]',
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableDivDirective), multi: true }
]
})
export class EditableDivDirective implements ControlValueAccessor {
@Input() ngModel: any;
@Output() ngModelChange = new EventEmitter();
constructor(private _elRef: ElementRef, private _renderer: Renderer) { }
writeValue(value: any) {
if (!value) { value = ''; }
this._renderer.setElementProperty(this._elRef.nativeElement, 'innerHTML', value);
}
registerOnChange(fn: any) { this._onChange = fn; }
registerOnTouched(fn: any) { this._onTouched = fn; }
@HostListener('input') onInput() { this.onChange(); }
@HostListener('blur') onBlur() { this._onTouched(); }
private onChange() {
if (this._onChange) { this._onChange(this._elRef.nativeElement.innerHTML); }
this.ngModelChange.emit(this._elRef.nativeElement.innerHTML);
}
private _onChange: Function;
private _onTouched: Function;
}
このコードを実行すると、div
要素をクリックして編集できます。編集内容が変更されると、コンポーネントの myContent
プロパティと ngModelChange
イベントに反映されます。
contenteditable
ngModel と FormControl を使う
FormControl
オブジェクトを作成します。FormControl
オブジェクトをngModel
ディレクティブのngModel
入力プロパティにバインディングします。FormControl
オブジェクトのvalueChanges
Observable を購読して、div
要素の内容を更新します。
<div [ngModel]="myControl.value"></div>
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-root',
template: `
<div [ngModel]="myControl.value"></div>
`
})
export class AppComponent implements OnInit {
myControl = new FormControl('');
constructor() { }
ngOnInit() {
this.myControl.valueChanges.subscribe(value => {
// div 要素の内容を更新
});
}
}
この方法は、比較的シンプルで、FormControl
オブジェクトを使用してフォームデータの検証や処理を行うことができます。
ViewChild と Renderer2 を使う
ViewChild
を使ってdiv
要素を取得します。Renderer2
を使ってdiv
要素の内容を更新します。
<div #myDiv contenteditable></div>
import { Component, OnInit, ViewChild } from '@angular/core';
import { Renderer2 } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div #myDiv contenteditable></div>
`
})
export class AppComponent implements OnInit {
@ViewChild('myDiv') myDiv: ElementRef;
constructor(private renderer: Renderer2) { }
ngOnInit() {
// div 要素の内容を初期化
this.renderer.setProperty(this.myDiv.nativeElement, 'innerHTML', '初期値');
// div 要素の内容が変更されたときにイベントを処理
this.myDiv.nativeElement.addEventListener('input', (event) => {
// div 要素の内容を更新
this.renderer.setProperty(this.myDiv.nativeElement, 'innerHTML', event.target.innerHTML);
});
}
}
この方法は、比較的シンプルで、ViewChild
と Renderer2
を使って DOM 操作を行うことができます。
サードパーティライブラリを使う
ngContenteditable
や ngx-editor
などのサードパーティライブラリを使用して、contenteditable
な div
要素と ngModel
を双方向バインディングすることができます。
これらのライブラリは、contenteditable
な div
要素の編集機能を拡張する機能を提供することが多いです。
ngx-editor
どの方法を選ぶべきか
どの方法を選ぶべきかは、プロジェクトの要件によって異なります。
contenteditable
なdiv
要素の編集機能を拡張する必要がある場合は、サードパーティライブラリを使うのがおすすめです。- DOM 操作をより細かく制御する必要がある場合は、
ViewChild
とRenderer2
を使う方法がおすすめです。 FormControl
オブジェクトを使用してフォームデータの検証や処理を行う必要がある場合は、ngModel
とFormControl
を使う方法がおすすめです。- シンプルで使いやすい方法が必要な場合は、
ngModel
とFormControl
を使う方法がおすすめです。
注意事項
- サードパーティライブラリを使用する場合は、ライブラリのライセンスとドキュメントをよく確認してください。
- いずれの方法を使用する場合も、
contenteditable
なdiv
要素に対して XSS 攻撃などのセキュリティ対策を行う必要があります。
angular ionic2 contenteditable