Write a post

Creating an Inline Edit Component for Form-Inputs in Angular 2

Published Jan 03, 2017Last updated Jan 18, 2017
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}}&nbsp;</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

angular 2

Step 2

angular 2

Step 3

angular 2

And that's it!

I'll be pushing the project to a git repo here, soon.

Cheers.
Discover and read more posts from Godson Ukpere
get started
Enjoy this post?

Leave a like and comment for Godson

14
3
AngularJS Tip: How to Fix an Angular Directive Issue
Building a Real-time Chat App with Angular 2 and deepstream
Why Angular 2 (4, 5, 6) sucks