Sitemap
Netanel Basal

Learn development with great articles

Keeping It Simple: Implementing Edit-in-Place in Angular

--

I’ve wanted to write my own edit-in-place implementation for a long time. Luckily, in the last few nights, my kids — the youngster and the newborn — fell asleep early (finally 😅), which gave me some time to experiment with writing it.

In this article, I’ll share with you the solution I came up with. We’ll learn how to write a flexible edit-in-place component and make it work in conjunction with Angular Reactive forms.

In our example, we’ll use a simple HTML table. Whenever a user double-clicks on a cell, we switch to edit mode. There we show a matching control type, giving the user the possibility to edit it. When the cell loses focus, we’ll switch back to view mode, and depending on the control validity, we’ll choose whether to update its value or leave it as-is.

To get the idea of what we’re going to build, here’s an illustration of the final result:

Let’s get started.

First, let’s create mock data so we have something to work with, and then render the table.

We use ngFor to iterate over the entities and display each user’s name and whether the user is an admin. Our next step is to create a reactive form presentation of our data.

Since we work with a collection of entities, our best option would be to use a FormArray where each element in the array is a FormGroup that represents a single entity:

We iterate over the entities and for each one create a new FormGroup which holds two FormControls: one for the name property and one for the isAdmin property. We also create a getControl() method that receives an entity index and a field name, and returns the form control which corresponds to that entity and its field.

For example, the following code:

Returns the name FormControl for the second entity. Later, we’ll see how we use it in the template.

Now that we’ve finished with the boring stuff, let’s continue with fun part — creating the edit-in-place component. Your first instinct might be to imagine it would look something like this:

At first glance, this looks accurate. Yes, it could work, but we’re not going down this road for two reasons:

  1. We’d need to expose each one of the internal components’ inputs and events, so our consumers can use it if they need to. For example, if they use a text input type, we must expose its events, like blur, focus, etc.
  2. We’d need to maintain it. I like to give the open-closed principle (part of SOLID design) as a motivation. This principle states:

“Entities should be open for extension, but closed for modification.”

Or, in our case, components. When we say “closed for modification,” we mean we want to be able to change the behavior of a component without modifying that component’s source code. We should always strive to write code that doesn’t have to be changed every time the requirements change.

What if in the future, we’ll need to add a new component type? A switch control for example. To do that, we’ll have to modify the edit-in-place component’s codebase in order to support it; This can lead to side-effects such as unexpectedly breaking the original behavior, and would also require updating the existing specs.

Because of the above reasons, we’ll go with the following markup, which is more flexible and modular:

Let the fun begin 😎

First, we create two structural directives — one responsible for the view mode, and the other responsible for the edit mode:

They are in charge of exposing a reference to their TemplateRef, so that we can access it from the editable parent component. Let’s create that component:

We’re using the ContentChild decorator to obtain a reference to the ViewMode and EditMode directives. Then, based on the mode property (which has a default value of view), we decide which template should be rendered by the ngTemplateOutlet directive.

We also create an update output which is emitted whenever an update has occurred.

Let’s continue with the viewModeHandler() method implementation:

We listen to the host’s element dblclick event and change the mode property to edit, which causes the edit view to be rendered. We also create a subject that fires when we change the edit mode. We’ll see in a minute how we use it.

Now, let’s examine the editModeHandler() method:

In the first part, we create an observable that fires when we click outside the current element.

We subscribe to the editMode$ observable. When this observable emits, it means that we’ve entered edit mode and we should activate the clickOutside$ observable. When the clickOutside$ observable fires, we emit the update event and switch back to view mode.

This setup will ensure that at all times we have a single event listener for the document placed by the component, which isn’t created until the first time we’ve entered edit mode in that component. Now we can use the component:

Let’s finish with the updateField() method implementation:

We check if the control is valid. If it is, we find the corresponding entity based on the index and update its value based on the key. In real life, you’ll probably make a server request, add it to your store, and display to the user an indicator of a successful update.

🦊 We Can Do More

At this point, if we switch to edit mode which displays a text input, edit it and press the enter key, nothing will happen. It would be nice to add this functionality to our users.

Our first option is to implement it inside the input element or directive (note that we haven’t created one here, but in your application, you’ll probably have one such as <app-input> or <input appInput>), however this creates a coupling between the two, and it’s not reusable; therefore, we want to avoid this. We’ll achieve the functionality by using an additional directive:

Nice and clean. The EditableOnEnterDirective injects the EditableComponent parent and registers an event listener, which calls the component’s toViewMode() method whenever the user clicks ‘enter’. Here’s the toViewMode() method implementation:

And we’re done. I leave as an exercise to you an additional configuration you can add to the editable component: modify it to allow the component’s consumers the ability to select which event triggers entering into edit mode (click, double-click, right-click, hover, or any custom event they wish).

😍 Have You Tried Akita Yet?

One of the leading state management libraries, Akita has been used in countless production environments. It’s constantly developing and improving.

Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which help you manage the data and negate the need for massive amounts of boilerplate code. We/I highly recommend you try it out.

Here’s the final code:

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

Netanel Basal
Netanel Basal

Written by Netanel Basal

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.

Responses (26)