cft

Allow users to edit the content of any element with this Angular directive

I created an Angular directive that allows you to edit content on the fly.


user

Yoko Ishioka

3 years ago | 3 min read

As a side project, I am working on a note taker/learning aid app built with Angular. One of my main goals is to make it as user friendly as possible, so I decided to make an Angular directive that does the following:

  1. Lets user click on text to edit the contents
  2. Can restrict edits by requiring the user to hit the edit icon first, essentially entering an edit mode
  3. Checks whether the text was altered when the input loses focus or when the user hits ‘Enter’
  4. Saves the text if it was altered

The Directive Has the Following Properties:

// Use this variable to store the original value
value: string; // Use this input if you want to restrict editing to only be allowed in edit mode
@Input() allowEdit: boolean = true; // Use this input if you are saving the change and need to reference the identifier of the change
@Input() id: number; // Use this input to indicate what field is being changed
@Input() update: string; // Use this output to notify the parent that a change has been made
@Output() onChange: EventEmitter<EditUpdate> = new EventEmitter();

For the Constructor, You Will Need:

// Automatically get a reference to the element you're changing with ElementRef
private el: ElementRef // Safely update the element without accessing the DOM directly by using Renderer2
private renderer: Renderer2

Detect Events With HostListener

The beauty of using HostListener is that you don’t have to keep track of who is sending the event because as the name implies, it’s the host of whatever you added the directive to. (To me, this is the true power of directives!)

That means no more redundant code of individually assigning event listeners and then hooking those up to the functions that contain callbacks of what you want to happen when those events are made. No more having to build hierarchical relationships so that you can modify the element in relation to the elements around it!

I have three HostListeners for this directive because I want to detect when the user clicks on the element, when the user clicks off of the element, and when the user hits ‘Enter’ on the keyboard.

A HostListener to Detect Click Events

If you want to toggle editing capabilities, wrap an if statement around the function that’s handling the click event. Because the parent determines whether or not the element should be editable, you don’t have to worry about toggling this variable. (I’ll cover how to reset the variable further down this article.)

@HostListener("click") onClick() {
if (this.allowEdit) {
this.makeEditable();
}
}

Some Reasons to Make Editing Not Automatic By Default

  1. You want only certain users with the proper permissions to be allowed to edit the text.
  2. You want the text to be selectable when it’s not being editing.
  3. You want to limit the possibility of accidental edits.

HostListeners to Detect When the User Is Done Editing

When first making this directive, I spent a lot of time creating a save/undo button interaction that hid/showed other functionality in the different modes but then later scrapped that approach because it required the user to click unnecessarily. After all, nothing is more frustrating than losing all of your work because you forgot to hit save.

I created one for when the user clicks elsewhere and also another to listen for the ‘Enter’ key.

@HostListener('blur') blurred() {
this.checkValues();
}@HostListener('keydown', [('$event')]) onKeydown(event) {
if (event.keyCode === 13) {
event.preventDefault();
this.checkValues(); }
}

Enter Edit Mode

You can use ElementRef to identify the element rather than querying the DOM and Renderer2 to manipulate it. This function simply adds the editing class to the element, thereby providing visual feedback to the user that it’s now editable.

makeEditable() {
this.renderer.addClass(this.el.nativeElement, "editing");
this.renderer.setAttribute(this.el.nativeElement, "contentEditable", "true");
this.el.nativeElement.focus();
this.setValue(this.el.nativeElement.innerText);
}

Check To See If an Edit Was Made

Next, set the ‘contentEditable’ attribute to ‘true’. Then, give it focus and save the value of the inner text.

After the user finishes editing, we’re going to compare the value of the inner text with the previous one to see if there are any changes. That way, we’ll send the data to the parent only if there is a difference and thus reduce the number of unnecessary trips to the database.

checkValues(): Observable<EditUpdate> {
const newValue = this.el.nativeElement.innerText; if (this.value !== newValue) {
const edit: EditUpdate = {
id: this.id,
update: this.update,
value: newValue
}
this.onChange.emit(edit);
} this.reset();
return this.onChange;
}

Exit Edit Mode

Don’t forget to reset edit mode by removing the ‘editing’ class and setting ‘contentEditable’ to ‘false.’

reset() {
this.editing = false;
this.renderer.removeClass(this.el.nativeElement, "editing");
this.renderer.setAttribute(this.el.nativeElement, "contentEditable", "false");
}

The Final Product

Here’s the code all together. Hope you enjoy!

https://gist.github.com/yokoishioka/f746f261a86c91ffb2a4ffdeef18a096



Upvote


user
Created by

Yoko Ishioka


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles