How to add a loading state to Angular Material buttons

Fynn
Fynn
May 25 · 4 min read
Image for post
Image for postImage for post
Spinning Dream by Christophe Hautier

At COYO we are using Angular Material as a technical basis for our UI. The library provides a variety of different directives and components to help you implement a solid frontend based on the Material Design guidelines. One of the library’s core components are native <button> or <a> elements enhanced with Material Design styling and ink ripples. Unfortunately these buttons are missing a loading state to indicate ongoing asynchronous actions. So let’s build our very own loading directive to add this functionality to Angular Material buttons.

Image for post
Image for postImage for post
Loading states for Angular Material buttons

Our goal is to add a loading attribute to the <button> element so that we can easily switch on/off the loading state. While loading, the button should display a spinner and not accept any user interaction. The dimensions of the button should not change when the loading state is toggled.


Step 1: Starting with the groundwork

Let’s start by creating a new directive called MatButtonLoadingDirective. As we want to extend Angular Material’s existing button component, we use the same selector as the component. You can find the selector in the source code at GitHub. We simply copy the selector and append a loading attribute to each one. This ensures that our directive works with all kinds of Material buttons and that we can simply set a loading attribute on each of them.

The directive itself needs to implement Angular’s OnChanges lifecycle hook to perform actions when the loading attribute changes. When loading is active, we create a new spinner within the button. It is removed again when the loading state changes back to false. To apply custom styling we are also adding a .mat-loading class to the button.


Step 2: Implementing the spinner

This is the tricky part. We don’t want to build our own spinner into the existing button. Much more we want to use the already existing MatProgressSpinner component from the Angular Library. We will use a ComponentFactory to dynamically create a new instance of this component and append it to the existing button. If you want to read more about dynamic components you will find a guide in the Angular documentation.

First of all we change the constructor of our directive and inject all the stuff that we need. We also create a new instance of a component factory for the MatProgressSpinner so that we can easily create new instances in the createSpinner method.

Now we can use the factory to create the spinner and also destroy the spinner when it is not needed anymore.

Finally, we will hide the button content whenever the button is in a loading state. We will do this via visibility: hidden in order to not change the dimensions of the button. The spinner itself will be placed in the center of the button.


Step 3: Disabling the button

This is already a very cool result. We can now toggle the button’s loading state via the corresponding attribute. It works for every button that Angular Material provides. However, the button can still be clicked if it is in the loading state. We could just set the disabled state directly in our lifecycle hook but this would result in some faulty behavior. We would override the disabled state that can be set on the button itself. Thus, we use another @Input called disabled . This will be the same input that the button component itself is using to determine its disabled state. If the button is loading, we override the disabled state and set it to true . Otherwise we will fall back to the disabled state on the button itself.


Step 4: Choosing the correct theme palette

One minor detail that is still missing is the correct color of the spinner. Both components — button and spinner — support the use of a color palette. So we can easily make the color of the spinner align with the color of the button. To do this, we add another @Input to the directive and call it color. Again, this is the same input that the button will use to determine its color. We can then simply set the color on the spinner component that we are creating in out createSpinner method.


Final implementation

That’s it. We are done! We have created an easy-to-use loading state for Angular Material buttons. There are a couple of minor tweaks that one could add to the directive (e.g. a progress value) but this is out of the scope of this article. You can find the final result in the StackBlitz below. Have fun!

COYO Tech

Our take on agile, software development, product management and other stuff.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store