DEV Community

Cover image for HOWTO: Create a cool animated button for API calls!
AvogadroSG1
AvogadroSG1

Posted on • Updated on

HOWTO: Create a cool animated button for API calls!

I work in a business focused operation where we aren't specifically able to focus on "code" and many times need to focus on the "business logic" instead. So I'll push out code in order to get things completed and to support our user base. They are happy, but I tend to see improvements and don't always have time to push that through. When I do, great things like a cool animated button that is standardized throughout our application occurs!

Let's start somewhere in the beginning with some code for a button on a settings page:

If you dig into our button's template code, I've put four different buttons in one spot to display:

  • Action
  • Successful action response
  • Error response
  • Action performing

They each have their own bits of logic for display. For instance, a successful action response has logic to use a special class to give a green color and determine if we should show based on some booleans. During API calls, these booleans would be switched around to make sure to display the correct buttons.

As one could imagine, this is the code for one button! Imagine a page with a lot of buttons and maintaining it. Even within sub-components, we could have many sets of this code with many sets of the same booleans and probably even more complex business logic! My issue ended up stemming from changing the button type (from a mat-raised-button to a mat-mini-fab). The number of code changes was insane.

I set out to make a single button for our apps. There are two requirements:

  1. When making API calls, I'd like the processing of the observable to determine the button to use.
  2. The button should be agnostic to business logic.
  3. If not using for API call, be able to manually change the button being displayed.

Let's take a look at where I landed and discuss some choices.

First off, I've remodeled the front end to use a clearer ngSwitch instead of confusing *ngIf directives. The switch is dependent on an Enum of the Result. It should be much easier to understand the button that is active now.

Next, there needs to be customization of the just about anything on the button. So ButtonData and ActionButtonData have been push into the component to support customization. I made a conscious choice not to do anything specific with the progress spinner since it's consistent throughout my applications and at least it's in one spot now.

The most interesting decision was in how to control the display. To maintain both specifications of manually changing and automatically changing based on an Observable, two pathways are available:

  • monitorObservable() allows for applying pipe() operations to properly set the status variable during the result. I tried for awhile to think of a way to attach the observable pipe() operations on an observable that is pushing into a @Input directive, however, many of my API calls aren't setup until a user has completed doing a lot of modifications to a form. And I use NSwag to create my API calls to my local API, so I don't want to change how it handles creating the object to send.

  • Within the pipe() we use the tap() function to monitor the output. Since observables emit next, error, complete possibilities we tap into each to know what to do. Obviously our end points tend to emit all their values at once and then complete. It would take some reworking to make this work with something that consistently emitted data until unsubscribed, but that was beyond the scope of work.

  • The property status is an input variable that can be updated from outside of itself, allowing the changing manually.

I think this is a pretty slick DRY solution to a common problem I was having. Now, there is one easy to understand component that I can share across my application.

I'd like to also mention my team members: Brandon and Mike for helping with this as well.

Top comments (1)

Collapse
 
avogadrosg1 profile image
AvogadroSG1 • Edited

As an update to this post, I recognized that in the tap() the complete: function can't be guaranteed to run for many different reasons. See the RxJS docs for the information on error and how to handle them asynchronously.

github.com/Reactive-Extensions/RxJ...

I decided to change the code for action button to use the finalize instead since we always want the function to reset the button state.

      finalize(() => {
          setTimeout(
            () => {
              this.status = null as any;
            },
            this.status === Result.SUCCESS ? 2000 : 3000
          );
        })
Enter fullscreen mode Exit fullscreen mode