Angular self-saving dropdowns - yet another directive

Post Editor

Use the power of Angular directives to create a reusable self-saving dropdown directive

4 min read
post

Angular self-saving dropdowns - yet another directive

Use the power of Angular directives to create a reusable self-saving dropdown directive

post
post
4 min read
“It is not enough for code to work” - Robert C. Martin

Angular directive is a powerful pattern provided by the framework which can be used to add additional behaviors to the elements. We will create a custom Angular directive in this article to enable self-saving(autosaving) for dropdown elements.

What is a self-saving dropdown anyways?
Link to this section

Consider this simple scenario in an example application where you have a dropdown to determine the sexual age group of a person. The natural and efficient implementation of this case is to use a dropdown with options. It is typical to use a form with a dropdown field and a submit button. But what if this dropdown is not part of a larger form? What if this is a single select item in the page without a save button? You might not want the user to click a button to initiate the save in some cases as it can cause friction.

We can use a self saving dropdown which syncs the state with the backend database on dropdown change events. You can find this pattern commonly in react applications with the data fetcher approach where the component is responsible for fetching its required data itself.

Great! Now, how do we communicate to the user that this change triggered a save? How do we let users know in case of an error? It is obvious that we need an elegant solution to provide visual feedback to the user of in-progress, success, and failure events. This is where you might want to implement self-saving behavior.


Let’s get to it
Link to this section

Take a  peek into the final results on how it works before we start.

Self saving drop-downs

That looks cool, doesn't it? Let’s now outline the plan

  1. Directive should take an input that refers to the http call being made to the backend.
  2. When the host element triggers the 'change` event, we need the listener to run.
  3. Need to show the loader before subscription
  4. Need to remove the loader, add green tick icon on successful call
  5. Need to remove the tick mark after one second
  6. On failure, need to show the error icon next to the select field

In the diagram,

Self saving dropdown, the plan

Let’s start by creating an Angular directive using the Angular CLI.

ng generate directive self-save

We will have only one input to this directive, ie

<>Copy
@Input('observableFn') observableFn!: () => Observable<any>;

ObservableFn will have the reference to the http request that has to be made to save this particular data. We will be passing this information down from the parent where this directive is getting used.

Let’s now bring in ElementRef, Renderer2, and Document

<>Copy
constructor( private elRef: ElementRef, private renderer: Renderer2, @Inject(DOCUMENT) private document: Document ) {}

We will be using these dependencies to

  1. ElementRef - Refer to the host element
  2. Renderer2 - To Attach the error text element in case of error
  3. Document- reference to the document

We need to listen to the change event on the host element to know when the dropdown value changes

<>Copy
@HostListener('change') onChange() { // Do all craziness }

Let’s start implementing the self-saving behavior now,

<>Copy
if (this.observableFn instanceof Function) { const element: HTMLElement = this.elRef.nativeElement; this.addLoader(element); const changeObservable: Observable<unknown> = this.observableFn(); changeObservable.subscribe( _ => { this.handleSuccessCase(element); }, _ => { this.handleErrorCase(element); } ); }

We are making use of the elementRef to grab the, native host element(select element) as we need to manipulate this as we go. Next, we are subscribing to the observable that is already passed as the input by calling the input function. There are two helper methods here to which we are passing the select element . Let us now see how they are implemented.

<>Copy
handleSuccessCase(element: HTMLElement) { this.removeBackground(element); this.addSuccess(element); setTimeout(() => { this.removeBackground(element); }, 1000); } handleErrorCase(element) { this.removeBackground(element); const child = this.document.createElement('img'); child.src = ERROR_ICON; const parent = this.renderer.parentNode(this.elRef.nativeElement); this.renderer.appendChild(parent, child); setTimeout(() => { this.renderer.removeChild(parent, child); }, 1000); }

This code follows the initial flowchart that we created. ie, on change of the dropdown value,

  1. Adds a loader to the element to indicate save in-progress
  2. Gets the http observable and subscribes

On success

  1. Removes the loader background
  2. Add success tick icon
  3. Remove the tick icon after 1 second

And on failure,

  1. Create a dummy div element to hold the error text
  2. Populated the innerText as the error message received from server
  3. Use Renderer to append this child element to the parent node so that the error message is not displayed below the dropdown

Let us now implement these helper methods that we used above,
To add a loader to the dropdown,

<>Copy
addLoader(element: HTMLElement) { this.addBackground( element, LOADER_ICON, 20 ); }

The same can be followed to add the success indicator as well,

<>Copy
addSuccess(element: HTMLElement) { this.addBackground( element, SUCCESS_ICON, 20 ); }

Now to implement the addBackground() method,

<>Copy
addBackground( element: HTMLElement, backgroundImg: string, backgroundSize: number ) { element.style.background = `#fff url("${backgroundImg}") no-repeat right 20px center`; element.style.backgroundSize = `${backgroundSize}px`; }

We can simply set the element background to none to remove background.

<>Copy
removeSuccess(element: HTMLElement) { this.removeBackground(element); } removeBackground(element: HTMLElement) { element.style.background = ‘none’; }

Now to use this directive on any select element,

<>Copy
<select selfSave [observableFn]="post()"> <option value="One">One</option> <option value="Two">Two</option> </select>

Let us also create the post() method which returns a function that returns the http save observable.

<>Copy
// Done for demo purposes only. Use a service to talk to APIs post(): Function { return () => { return this.http.post(“https://jsonplaceholder.typicode.com/posts”, {}); }; }

You can play around with the stackblitz project here.

Conclusion
Link to this section

Directives are very handy when it comes to a repeated pattern that we need to apply to multiple elements in our application. They can be used to make reusable patterns across the application and make the developer's life easier by keeping the application cleaner!




Share

About the author

author_image

Passionate web developer from India. Building the world's best contract management solution at https://spotdraft.com. Love to write, talk and teach Angular

author_image

About the author

Bharath Ravi

Passionate web developer from India. Building the world's best contract management solution at https://spotdraft.com. Love to write, talk and teach Angular

About the author

author_image

Passionate web developer from India. Building the world's best contract management solution at https://spotdraft.com. Love to write, talk and teach Angular

Looking for a JS job?
Job logo
Senior Full-Stack Developer (Node+Angular)

A-Listware

Ukraine
Remote
$48k - $78k
Job logo
Senior Full stack (Angular+Node)

Monolith

Ukraine
Remote
$60k - $84k
Job logo
AngularJS Developer/.net Core - Remote Contract

InfoMagnus

United States
Remote
$115k - $134k
Job logo
Angular Web Developer

NTT Data Services, Inc.

United States
Remote
$115k - $134k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more