HOWTO: Create a cool animated button for API calls!
Tags: angular, typescript, ui, rxjs
Published: 2021-01-31 17:27:22

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:

{% stackblitz anuglaranimatedbutton view=preview %}

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.

{% stackblitz anuglaranimatedbutton-action-button view=preview %}

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.