Updating text on a Loading spinner with Ionic 3


Ionic has a handy Loading component, typically used to display a spinner and message while some network operation completes or some other work goes on in the background.
It works well for simple operations, but falls down for dynamic or multi-step operations.
For instance, when you call the LoadingController to set things up, you can specify the content that the loading screen will display:

const loading = this.loadingCtrl.create({
  content: 'Please wait...'

However, you can’t go much further. This doesn’t work:

let loading = this.loadingCtrl.create({
  content: "Working on step {{stepNumber}}: "+

You’ll just get a display with literal curly braces and no binding: “Working on step {{stepNumber}}: {{stepMessage}}” Even if it worked, it’s not clear where those variables would live, since the LoadingController instantiates a new controller and view under the covers when you invoke it.
Fortunately, there’s a dirty workaround available. If you inspect the view that’s generated, it looks like this (specific definition here):

<div class="loading-wrapper">
  <div *ngIf="showSpinner" class="loading-spinner">
   <ion-spinner [name]="d.spinner"></ion-spinner>
  <div *ngIf="d.content" [innerHTML]="d.content"

This explains why the binding doesn’t work — the content is just set as the innerHTML of a div, and that doesn’t handle Angular binding syntax.
But also, it means that so long as you specify some content to begin with, there’s an identifiable place it will appear in the DOM. We can grab it with a CSS query such as div.loading-wrapper div.loading-content
So the trick to update the content is to find that div and update its innerHTML. Again, this won’t handle Angular data binding, but it lets you just replace the text on demand. If we imagine that we have some functions that do work and return promises, we might set things up like this:

setLoadingText(text:string) {
  const elem = document.querySelector(
        "div.loading-wrapper div.loading-content");
  if(elem) elem.innerHTML = text;
someLongOperation() {
  const loading = this.loadingCtrl.create({
    spinner: 'bubbles',
    content: "Step 1: doing some work"
  return doSomeWork()
    .then(() => {
      this.setLoadingText("Step 2: more work");
      return doSomeMoreWork();
    }).then(() => {
      this.setLoadingText("Step 3: wrap up");
      return finishDoingWork();
      () => loading.dismiss(),
      (err) => loading.dismiss()
                  .then(() => alert(err))

The initial Loading popup:
Step 1 image
Which then changes to this:
Step 2 image
…and so on.