Creating an Inline Edit Component for Form-Inputs in Angular 2
The need
In two words User Experience, but one word in German Benutzererfahrung 😏.
Most web pages with Create, View, and Edit pages seem to encounter the redundancy of having isolated View and Edit pages. It's super awesome to have just a Create and a View page, with the Edit system encapsulated in the View page. Before I begin, I'd like to thank the Business Analyst of my team, Jorge Patrão, for giving me the idea.
Before we begin
You'll need to be familiar with Angular 2's component-based architecture, and it's 2-way data binding. If you are familiar with these systems, then we are good to go. Similarly, here are some useful Angular 2 framework highlights that are worth knowing.
Creating the inline-edit component
I use angular-cli to scaffold my Angular 2 applications — you should, too.
So we create a new component and call it inline-edit
like so: ng generate component inline-edit
(Shorthand ng g component inline-edit
).
I'll also recommend you have a folder for your custom components so you don't mix up pages, routes, and services all in the same directory, so I'd do ng g component components/custom/inline-edit
, provided that directory components/custom/
already exists.
The source code
So in our empty component, we need to create a bunch of variables that would help bind data from the parent component into our component, handle the events for hiding or showing the form -input
element and so on.
So first, we use the Input
decorator from @angular/core
. It's used to receive data from parent components.
The ViewChild
decorator is used to reference a native DOM element in the component's script.
So in our inline-input
component, we declare all the variables we need:
inline-edit.component.ts
@ViewChild('inlineEditControl') inlineEditControl; // input DOM element
@Input() label: string = ''; // Label value for input element
@Input() type: string = 'text'; // The type of input element
@Input() required: boolean = false; // Is input requried?
@Input() disabled: boolean = false; // Is input disabled?
private _value: string = ''; // Private variable for input value
private preValue: string = ''; // The value before clicking to edit
private editing: boolean = false; // Is Component in edit mode?
public onChange: any = Function.prototype; // Trascend the onChange event
public onTouched: any = Function.prototype; // Trascend the onTouch event
// Control Value Accessors for ngModel
get value(): any {
return this._value;
}
set value(v: any) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
// Required for ControlValueAccessor interface
writeValue(value: any) {
this._value = value;
}
// Required forControlValueAccessor interface
public registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
// Required forControlValueAccessor interface
public registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
NB — For the purpose of this tutorial, I'll be using inline comments, IRL, it's bad practice.
Next, we should create the functions that help handle the focus and editing of the input element; i.e. when some text is clicked, the input element should appear and disappear right after it looses focus, showing us the newly entered value in a stylish format.
inline-edit.component.ts
// Do stuff when the input element loses focus
onBlur($event: Event) {
this.editing = false;
}
// Start the editting process for the input element
edit(value) {
if (this.disabled) {
return;
}
this.preValue = value;
this.editing = true;
// Focus on the input element just as the editing begins
setTimeout(_ => this._renderer.invokeElementMethod(this.inlineEditControl,
'focus', []));
}
Also, note that the overridden methods need come from the ControlValueAccessor
interface in @angular/forms
. This helps control the getter and setter for the value and the writeValue
function as well. So we'll need to import the ControlValueAccessor
class and create a provider that will be placed in our Component's initializer. See below all the required import to set this up.
inline-edit.component.ts
import { Component,
Input,
ElementRef,
ViewChild,
Renderer,
forwardRef,
OnInit } from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
Now we create and insert the provider for our inline-edit
component.
inline-edit.component.ts
const INLINE_EDIT_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InlineEditComponent),
multi: true
};
@Component({
selector: 'app-inline-edit',
templateUrl: './inline-edit.component.html',
providers: [INLINE_EDIT_CONTROL_VALUE_ACCESSOR],
styleUrls: ['./inline-edit.component.css']
})
export class InlineEditComponent implements ControlValueAccessor, OnInit {
……
}
The easy part
Now that we are done with the Component's controller, time for the view.
The view is pretty simple, all we need to do is transcend some properties from our controller and parent component (disabled, required, value, label, type, etc). and hide or show the input and values based on the boolean value editing
.
inline-edit.component.html
<div>
<div *ngIf="editing">
<input #inlineEditControl [required]="required" (blur)="onBlur($event)" [name]="value" [(ngModel)]="value" [type]="type" [placeholder]="label" />
</div>
<div *ngIf="!editing">
<label class="block bold">{{label}}</label>
<div title="Click to edit" (click)="edit(value)" (focus)="edit(value);" tabindex="0" class="inline-edit">{{value}} </div>
</div>
</div>
Usage
Now we get to use our custom component. To reference a component in Angular 2, all you need to do is use the selector tag, and pass in the Input()
parameters as attributes. See below:
app.component.ts
<h3>
{{title}}
</h3>
<div>
<app-inline-edit [(ngModel)]="name" label="Name" [required]="true" type="text">
</app-inline-edit>
</div>
Step 1
Step 2
Step 3
And that's it!
I'll be pushing the project to a git repo here, soon.
Hey, nice article. However, would it not be simple if we just use “contenteditable=‘true’” in the form element.
editing should be public , and you forgot about the constructor
I created an app at work and this helped me quite a bit. I was wondering if you could steer me in the direction of doing something similar with select box.