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

“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?
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
Take a peek into the final results on how it works before we start.

That looks cool, doesn't it? Let’s now outline the plan
- Directive should take an input that refers to the http call being made to the backend.
- When the host element triggers the 'change` event, we need the listener to run.
- Need to show the loader before subscription
- Need to remove the loader, add green tick icon on successful call
- Need to remove the tick mark after one second
- On failure, need to show the error icon next to the select field
In the diagram,

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
@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
constructor(
private elRef: ElementRef,
private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document
) {}
We will be using these dependencies to
ElementRef
- Refer to the host elementRenderer2
- To Attach the error text element in case of errorDocument
- reference to the document
We need to listen to the change event on the host element to know when the dropdown value changes
@HostListener('change')
onChange() {
// Do all craziness
}
Let’s start implementing the self-saving behavior now,
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.
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,
- Adds a loader to the element to indicate save in-progress
- Gets the http observable and subscribes
On success
- Removes the loader background
- Add success tick icon
- Remove the tick icon after 1 second
And on failure,
- Create a dummy div element to hold the error text
- Populated the
innerText
as the error message received from server - 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,
addLoader(element: HTMLElement) {
this.addBackground(
element,
LOADER_ICON,
20
);
}
The same can be followed to add the success indicator as well,
addSuccess(element: HTMLElement) {
this.addBackground(
element,
SUCCESS_ICON,
20
);
}
Now to implement the addBackground()
method,
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.
removeSuccess(element: HTMLElement) {
this.removeBackground(element);
}
removeBackground(element: HTMLElement) {
element.style.background = ‘none’;
}
Now to use this directive on any select element,
<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.
// 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
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!