How to programmatically inject content in bootstrap-vue modal body and footer?

If I understand correctly, you'd like to display the Modal content based on different state combinations.

As your descriptions, there should be 2 state:

  1. deletingState: it indicates whether begin deleting

  2. loadingState: it indicates whether is waiting the response from the server

Check Bootstrap Vue Modal Guide, then search keyword= Disabling built-in buttons, you will see we can use cancel-disabled and ok-disabled props to control the disable state of default Cancel and OK buttons (or you can use the slot=modal-footer, or modal-ok, modal-cancel.).

Other props you may use: ok-only, cancel-only, busy.

Finally bind v-if and props with the state combinations to show the content.

Like below demo:

Vue.config.productionTip = false
new Vue({
  el: '#app',
  data() {
    return {
      customer: {name: 'demo'},
      deletingState: false, // init=false, if pop up modal, change it to true
      loadingState: false // when waiting for server respond, it will be true, otherwise, false
    }
  },
  methods: {
    deleteCustomer: function() {
    	this.deletingState = false
      this.loadingState = false
      this.$refs.myModalRef.show()
    },
    proceedReq: function (bvEvt) {
    	if(!this.deletingState) {
        bvEvt.preventDefault() //if deletingState is false, doesn't close the modal
        this.deletingState = true
        this.loadingState = true
        setTimeout(()=>{
          console.log('simulate to wait for server respond...')
          this.loadingState = false
          this.deletingState = true
        }, 1500)
      } else {
      	console.log('confirm to delete...')
      }
    },
    cancelReq: function () {
    	console.log('cancelled')
    }
  }
})
.customer-name {
  background-color:green;
  font-weight:bold;
}
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>

<div id="app">
  <b-button v-b-modal.modal1 variant="danger" @click="deleteCustomer()">Delete</b-button>

  <b-modal title="Delete Customer" centered no-close-on-backdrop no-close-on-esc ref="myModalRef"
  @ok="proceedReq($event)" @cancel="cancelReq()" :cancel-disabled="deletingState" :ok-disabled="loadingState" :ok-only="deletingState && !loadingState">
    <div v-if="!deletingState">
      <p class="my-4">Are you sure, you want to delete customer:<span class="customer-name">{{customer.name}}</span></p>
    </div>
    <div v-else>
      <p v-if="loadingState">
        Deleting customer <span class="customer-name">{{customer.name}}</span>
      </p>
      <p v-else>
        Successfully deleted customer <span class="customer-name">{{customer.name}}</span>
      </p>
    </div>
    
  </b-modal>
</div>


You might prefer to use separate modals, the logic becomes a bit clearer and you can easily add more pathways, for example retry on API error.

console.clear()
const CustomerApi = {
  deleteCustomer: (id) => {
    return new Promise((resolve,reject) => {
      setTimeout(() => { 
        if (id !== 1) {
          reject(new Error('Delete has failed'))
        } else {
          resolve('Deleted')
        }
      }, 3000);
    });
  }
}
  
new Vue({
  el: '#app',
  data() {
    return {
      customer: {id: 1, name: 'myCustomer'},
      id: 1,
      error: null
    }
  },
  methods: {
    deleteCustomer(e) {
      e.preventDefault()

      this.$refs.modalDeleting.show()
      this.$refs.modalDelete.hide()

      CustomerApi.deleteCustomer(this.id)
        .then(response => {
          this.$refs.modalDeleting.hide()
          this.$refs.modalDeleted.show()
        })
        .catch(error => {
          this.error = error.message
          this.id = 1  // For demo, api success 2nd try
          this.$refs.modalError.show()
        })
    }
  }
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>

<div id="app">
<b-button v-b-modal.modal-delete variant="danger">Delete</b-button>

<input type="test" id="custId" v-model="id">
<label for="custId">Enter 2 to make it fail</label>

<b-modal 
  id="modal-delete" 
  ref="modalDelete"
  title="Delete Customer" 
  @ok="deleteCustomer" 
  centered no-close-on-backdrop close-on-esc>
  <p class="my-4">Are you sure, you want to delete customer: {{customer.name}}</p>
</b-modal>

<b-modal 
  ref="modalDeleting"
  title="Deleting Customer" 
  centered no-close-on-backdrop no-close-on-esc
  no-fade
  :busy="true">
  <p class="my-4">Deleting customer: {{customer.name}}</p>
</b-modal>

<b-modal 
  ref="modalDeleted"
  title="Customer Deleted" 
  centered no-close-on-backdrop close-on-esc
  no-fade
	:ok-only="true">
  <p class="my-4">Customer '{{customer.name}}' has been deleted</p>
</b-modal>

<b-modal 
  ref="modalError"
  title="Error Deleting Customer" 
  centered no-close-on-backdrop close-on-esc 
  no-fade
  :ok-title="'Retry'"
  @ok="deleteCustomer"> 
  <p class="my-4">An error occured deleting customer: {{customer.name}}</p>
  <p>Error message: {{error}}</p>
</b-modal>

</div>


Here is a generic wrapper component for Bootstrap-vue modal that takes an array of states and navigates according to the nextState property. It makes use of computed properties to respond to the state changes.

In the parent, the array of states is also defined in a computed property so that we can add customer (or photo) properties to the messages.

Edit

Added content slots which allow the parent component to define the exact markup inside the modal content.

console.clear()

// Mock CustomerApi
const CustomerApi = {
  deleteCustomer: (id) => {
    console.log('id', id)
    return new Promise((resolve,reject) => {
      setTimeout(() => { 
        if (id !== 1) {
          reject(new Error('Delete has failed'))
        } else {
          resolve('Deleted')
        }
      }, 3000);
    });
  }
}

// Wrapper component to handle state changes
Vue.component('state-based-modal', {
  template: `
    <b-modal 
      ref="innerModal"
      :title="title"
      :ok-disabled="okDisabled"
      :cancel-disabled="cancelDisabled"
      :busy="busy"
      @ok="handleOk"
      :ok-title="okTitle"
      @hidden="hidden"
      v-bind="otherAttributes"
      >
      <div class="content flex-grow" :style="{height: height}">

        <!-- named slot applies to current state -->
        <slot :name="currentState.id + 'State'" v-bind="currentState">
          <!-- default content if no slot provided on parent -->
          <p>{{message}}</p>
        </slot>

      </div>
    </b-modal>`,
  props: ['states', 'open'],  
  data: function () {
    return {
      current: 0,
      error: null
    }
  },
  methods: {
    handleOk(evt) {
      evt.preventDefault();
      // save currentState so we can switch display immediately
      const state = {...this.currentState}; 
      this.displayNextState(true);
      if (state.okButtonHandler) {
        state.okButtonHandler()
          .then(response => {
            this.error = null;
            this.displayNextState(true);
          })
          .catch(error => {
            this.error = error.message;
            this.displayNextState(false);
          })
      }
    },
    displayNextState(success) {
      const nextState = this.getNextState(success);
      if (nextState == -1) {
        this.$refs.innerModal.hide();
        this.hidden();
      } else {
        this.current = nextState;
      }
    },
    getNextState(success) {
      // nextState can be 
      //  - a string = always go to this state
      //  - an object with success or fail pathways
      const nextState = typeof this.currentState.nextState === 'string'
        ? this.currentState.nextState
        : success && this.currentState.nextState.onSuccess
          ? this.currentState.nextState.onSuccess
          : !success && this.currentState.nextState.onError
            ? this.currentState.nextState.onError
            : undefined;
      return this.states.findIndex(state => state.id === nextState);
    },
    hidden() {
      this.current = 0;     // Reset to initial state
      this.$emit('hidden'); // Inform parent component
    }
  },
  computed: {
    currentState() {
      const currentState = this.current;
      return this.states[currentState];
    },
    title() { 
      return this.currentState.title; 
    },
    message() {
      return this.currentState.message; 
    },
    okDisabled() {
      return !!this.currentState.okDisabled;
    },
    cancelDisabled() {
      return !!this.currentState.cancelDisabled;
    },
    busy() {
      return !!this.currentState.busy;
    },
    okTitle() {
      return this.currentState.okTitle;
    },
    otherAttributes() {
      const otherAttributes = this.currentState.otherAttributes || [];
      return otherAttributes
        .reduce((obj, v) => { obj[v] = null; return obj; }, {})
    },
  },
  watch: {
    open: function(value) {
      if (value) {
        this.$refs.innerModal.show();
      } 
    }
  }
})

// Parent component
new Vue({
  el: '#app',
  data() {
    return {
      customer: {id: 1, name: 'myCustomer'},
      idToDelete: 1,
      openModal: false
    }
  },
  methods: {
    deleteCustomer(id) {
      // Return the Promise and let wrapper component handle result/error
      return CustomerApi.deleteCustomer(id)  
    },
    modalIsHidden(event) {
      this.openModal = false;  // Reset to start condition
    }
  },
  computed: {
    avatar() {
      return `https://robohash.org/${this.customer.name}?set=set4`
    },
    modalStates() {
      return [
        { 
          id: 'delete', 
          title: 'Delete Customer', 
          message: `delete customer: ${this.customer.name}`,
          okButtonHandler: () => this.deleteCustomer(this.idToDelete),
          nextState: 'deleting',
          otherAttributes: ['centered no-close-on-backdrop close-on-esc']
        },
        { 
          id: 'deleting', 
          title: 'Deleting Customer',
          message: `Deleting customer: ${this.customer.name}`,
          okDisabled: true,
          cancelDisabled: true,
          nextState: { onSuccess: 'deleted', onError: 'error' },
          otherAttributes: ['no-close-on-esc'],
          contentHeight: '250px'
        },
        { 
          id: 'deleted', 
          title: 'Customer Deleted', 
          message: `Deleting customer: ${this.customer.name}`,
          cancelDisabled: true,
          nextState: '',
          otherAttributes: ['close-on-esc']
        },
        { 
          id: 'error', 
          title: 'Error Deleting Customer', 
          message: `Error deleting customer: ${this.customer.name}`,
          okTitle: 'Retry',
          okButtonHandler: () => this.deleteCustomer(1),
          nextState: 'deleting',
          otherAttributes: ['close-on-esc']
        },
      ];
    }
  }
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>

<div id="app">
<b-button @click="openModal = true" variant="danger">Delete</b-button>

<input type="test" id="custId" v-model="idToDelete">
<label for="custId">Enter 2 to make it fail</label>

<state-based-modal 
  :states="modalStates" 
  :open="openModal"
  @hidden="modalIsHidden"
  >
  <template slot="deleteState" scope="state">
    <img alt="Mindy" :src="avatar" style="width: 150px">
    <p>DO YOU REALLY WANT TO {{state.message}}</p>
  </template>
  <template slot="errorState" scope="state">
    <p>Error message: {{state.error}}</p>
  </template>
</state-based-modal> 

</div>