Inline Editing with Angular2

Image Credit: Pixabay

In this tutorial, we are going to create an Angular 2 inline edit component modeled after Angular-xeditable, which was developed for Angular 1.

Currently, an Angular 2 xeditable is under development, but in this project I am trying to achieve 3 things:

  • Toggling an editable value between a text and form display
  • Canceling or reseting the editable value
  • Saving the editable value

Here is the full working example.

Example Implementation

The inline edit component we are going to create will use the following structure.

<inline-edit [(ngModel)]=”editableText” (onSave)=”saveEditable(value)”></inline-edit>

1. Create the Inline Edit Component

First, we need to create an InlineEditComponent with the following content:

File: inline-edit.component.ts

import {Component, Provider, forwardRef} from '@angular/core';

import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/common";

const INLINE_EDIT_CONTROL_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => InlineEditComponent),
multi: true
});

@Component({
selector: 'inline-edit',
providers: [INLINE_EDIT_CONTROL_VALUE_ACCESSOR],
styleUrls: ['./inline-edit.component.css'],
templateUrl: './inline-edit.component.html'
})
export class InlineEditComponent implements ControlValueAccessor, ngOnInit{

constructor() {}

// The internal data model
private _value:string = '';

// Placeholders for the onChange and onTouch callbacks
public onChange:any = Function.prototype;
public onTouched:any = Function.prototype;

// get accessor
get value(): any { return this._value; };

// set accessor including the onChange callback
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}

// Will update the internal data model with incoming values
writeValue(value: any) {
this._value = value;
}

// ControlValueAccessor interface
public registerOnChange(fn:(_:any) => {}):void {this.onChange = fn;}

// ControlValueAccessor interface
public registerOnTouched(fn:() => {}):void {this.onTouched = fn;};

}

2. Define the Inline Edit View

Now we need to create our inline edit view, which will display the editable value as text when the user is not editing and display it in a form control when the user is editing.

File: inline-edit.component.html

<div id="inlineEditWrapper">

<!-- editable value -->
<a>{{ value }}</a>

<!-- inline edit form -->
<form #inlineEditForm="ngForm" class="inlineEditForm form-inline">
<div class="form-group">

<!-- inline edit control -->
<input #inlineEditControl class="form-control" [(ngModel)]="value"/>

<!-- inline edit save and cancel buttons -->
<span>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-default"><span class="glyphicon glyphicon-remove"></span></button>
</span>

</div>
</form>
</div>

3. Add Basic Styling

File inline-edit.component.css

a {
text-decoration: none;
color: #428bca;
border-bottom: dashed 1px #428bca;
cursor: pointer;
line-height: 2;
margin-right: 5px;
margin-left: 5px;
}
.inlineEditForm{
display: inline-block;
white-space: nowrap;
margin: 0;
}
#inlineEditWrapper{
display: inline-block;
}
.inlineEditForm input, select{
width: auto;
display: inline;
}
.editInvalid{
color: #a94442;
margin-bottom: 0;
}
.error{
border-color: #a94442;
}
[hidden] {
display: none;
}

4. Add Methods Logic

We must now add the logic that will toggle between our editable value’s text and form displays. We’ll also add a cancel method that will allow the user to reset the editable value.

import {Component, Output, Provider, forwardRef, EventEmitter, ElementRef, ViewChild, Renderer} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/common";

const INLINE_EDIT_CONTROL_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => InlineEditComponent),
multi: true
});

@Component({
selector: 'inline-edit',
providers: [INLINE_EDIT_CONTROL_VALUE_ACCESSOR],
styleUrls: ['./inline-edit.component.css'],
templateUrl: './inline-edit.component.html'
})
export class InlineEditComponent implements ControlValueAccessor, ngOnInit{

// inline edit form control
@ViewChild('inlineEditControl') inlineEditControl;
@Output() public onSave:EventEmitter<any> = new EventEmitter();

private _value:string = '';
private preValue:string = '';
private editing:boolean = false;

public onChange:any = Function.prototype;
public onTouched:any = Function.prototype;

get value(): any { return this._value; };

set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}

constructor(element: ElementRef, private _renderer:Renderer) {}

writeValue(value: any) {
this._value = value;
}

public registerOnChange(fn:(_:any) => {}):void {this.onChange = fn;}

public registerOnTouched(fn:() => {}):void {this.onTouched = fn;};

// Method to display the inline edit form and hide the <a> element
edit(value){
this.preValue = value; // Store original value in case the form is cancelled
this.editing = true;

// Automatically focus input
setTimeout( _ => this._renderer.invokeElementMethod(this.inlineEditControl.nativeElement, 'focus', []));
}

// Method to display the editable value as text and emit save event to host
onSubmit(value){
this.onSave.emit(value);
this.editing = false;
}

// Method to reset the editable value
cancel(value:any){
this._value = this.preValue;
this.editing = false;
}

}

5. Update Inline Edit View

Now it’s time to add to our view, the functionality provided by our methods in step 4.

<div id="inlineEditWrapper">

<!-- editable value -->
<a (click)="edit(value)" [hidden]="editing">{{ value }}</a>

<!-- inline edit form -->
<form #inlineEditForm="ngForm" class="inlineEditForm form-inline" (ngSubmit)="onSubmit(value)" [hidden]="!editing">
<div class="form-group">

<!-- inline edit control -->
<input #inlineEditControl class="form-control" [(ngModel)]="value"/>

<!-- inline edit save and cancel buttons -->
<span>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-ok"></span></button>
<button class="btn btn-default" (click)="cancel(value)"><span class="glyphicon glyphicon-remove"></span></button>
</span>

</div>
</form>
</div>

Next Steps

The basic structure of our inline edit component is now complete and implements the minimal features of inline editing:

  1. Toggling in and out of edit mode
  2. Saving
  3. Canceling

In one of my next blog posts, we will add some more advanced features to support validation, multiple types of form controls, and form control attributes such as required and placeholder.