I'm trying to use Angular 2 Forms, but instead of using input
I would like to use editable div
.
This works:
<input type="text" [(ngModel)]="value">
But this will throw an error:
<div contentEditable="true" [(ngModel)]="value"></div>
TypeError: setting a property that has only a getter
Is there a way to use div with Angular Forms? I don't want to use input or textarea
Not out of the box..
ngModel
is only accessible on elements which are supporting it!
You could create a directive or a custom-input-component.
Anyway, it has to implement this interface ControlValueAccessor
.
Directive:
Working demo: https://plnkr.co/edit/12vAEFf2OBS3ERu9fhwk?p=preview
import {Directive, Component, NgModule, forwardRef, ViewChild, ElementRef, HostListener, Renderer} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
selector: 'div[contentEditable]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EditableDivDirective),
multi: true
}
]
})
export class EditableDivDirective implements ControlValueAccessor {
constructor(private _elRef: ElementRef, private _renderer: Renderer) { }
onChange() {
if (this._onChange) {
this._onChange(this._elRef.nativeElement.innerText);
}
}
@HostListener('keyup', ['$event'])
keyup(event: any) {
this.onChange();
}
// ControlValueAccessor implementation
// ====================================
private _onChange = (_) => { }; // call it if your value changed..
private _onTouched = () => { }; // call it "on blur" ..
// will be called if a values comes in via ngModule !
writeValue(val: any) {
if (!val) val = '';
this._renderer.setElementProperty(this._elRef.nativeElement, 'innerText', val);
}
registerOnChange(fn: (_: any) => void): void { this._onChange = fn; }
registerOnTouched(fn: () => void): void { this._onTouched = fn; }
}
@Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<div contentEditable="true" [(ngModel)]="name">test test test</div>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ App, EditableDivDirective ],
bootstrap: [ App ]
})
export class AppModule {}
Component:
See a working demo: https://plnkr.co/edit/XMSTrWSe3gN9iwVTBukz?p=preview
import {Component, NgModule, forwardRef, ViewChild, ElementRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'my-name-input',
template: `
<div>
<label>first name</label>
<div #firstName contentEditable="true" (keyup)="onChange()">{{this._firstName}}</div>
<br />
<label>last name</label><input [(ngModel)]="_lastName" (ngModelChange)="onChange()" />
</div>
`,
providers: [ // IMPORTANT !!
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCompleteNameInputComponent),
multi: true
}
]
})
export class MyCompleteNameInputComponent implements ControlValueAccessor {
@ViewChild('firstName') editableDiv: ElementRef;
private _firstName: string = '';
private _lastName: string = '';
onChange() {
this._firstName = this.editableDiv.nativeElement.innerText;
if (this._onChange) {
this._onChange(this._firstName + ' ' + this._lastName);
}
}
// ControlValueAccessor implementation
// ====================================
private _onChange = (_) => { }; // call it if your value changed..
private _onTouched = () => { }; // call it "on blur" ..
// will be called if a values comes in via ngModule !
writeValue(val: any) {
if (!val || !val.split) val = '';
let splitted = val.split(' ');
this._firstName = splitted[0] || '';
this._lastName = splitted[1] || '';
}
registerOnChange(fn: (_: any) => void): void { this._onChange = fn; }
registerOnTouched(fn: () => void): void { this._onTouched = fn; }
}
@Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<my-name-input [(ngModel)]="name"></my-name-input>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ App, MyCompleteNameInputComponent ],
bootstrap: [ App ]
})
export class AppModule {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With