Estados de carga simples para tus botónes

Éste es un hack que suelo utilizar en todos los proyectos de Bootstrap que trabajo (y en los que no utilizan Bootstrap también)

Bootstrap no incluye por defecto una forma sencilla de mostrar un estado de carga en los botones, aunque propone una solución combinando varios elementos

Nos ofrece la clase .spinner-border para dibujar un loading spinner independiente, y la clase .btn para estilar el botón, y nos propone usar la combinación de ambos para lograr un loading state, pero me parece una solución sucia y poco intuitiva

<button class="btn btn-primary" disabled>
    <span class="spinner-border spinner-border-sm"></span>
    <span>Guardar</span>
</button>

Se vuelve trabajoso si queremos aplicar el estado de carga al botón dinámicamente, porque implica utilizar Javascript para renderizar/eliminar nodos dentro del botón, o mantenerlos en el dom siempre y mostrarlos/ocultarlos

.btn-loading

Entonces lo que hago es crear la clase .btn-loading que hereda las propiedades CSS del spinner y las aplica en un pseudo-elemento ::before del botón

.btn-loading::before {
    content: '';
    margin-right: 0.4em;
    margin-top: -4px;
    @extend .spinner-border;
    @extend .spinner-border-sm;
}
<button class="btn btn-primary btn-loading" disabled>
    Guardar
</button>

Delegando la responsabilidad de dibujar el spinner al CSS, y simplificando el proceso de alternar el estado de carga del botón simplemente haciendo toggle a la clase .btn-loading

const loadingStateToggler = document.querySelector('#loading-state-toggler');
const loadingStateButton = document.querySelector('#loading-state-button-container').shadowRoot.querySelector('#loading-state-button');

loadingStateToggler.addEventListener('click', () => {
    loadingStateButton.classList.toggle('btn-loading');
    loadingStateButton.disabled = !loadingStateButton.disabled;
});