1

Let's say I'm using a MVC pattern. Here's some pseudocode:

View {
    main() {
        Controller.callAPI(url);

        display(item, position) {
          // display item at position
        }
    }
}

Controller {
    callAPI(url) {
        app.request('GET', function(response){
            Model.handleCode(response);
        });
    }
}

Model {
    handleCode(response) {
        var cleaned = cleanData(response);
        store(cleaned);
        cleaned.forEach(item, position) {
           View.main.display(item, position);
        }
    }
}

One way flow: User > View > Controller > Model > View.


Now let's say I want to add a preloader, one of those circly loading indicators. The preloader is on the View. The preloader is visible while waiting on the response of the API.

The preloader is shown before the API endpoint is called and hidden after the API has given a response.

Option 1: Do it all in the View

I can show the preloader on the View, before calling the Controller.

View {
    var preloader;
    main() {
        preloader.show();
        Controller.callAPI(url);

        display(item, position) {
          // display item at position
        }

        dismissPreloader(){
          preloader.hide();
        }
    }
}

Controller {
    callAPI(url) {
        app.request('GET', function(response){
            Model.handleCode(response);
        });
    }
}

Model {
    handleCode(response) {
        var cleaned = clean(response);
        store(cleaned);
        View.main.dismissPreloader();
        cleaned.foreach(item, position) {
           View.main.display(item, position);
        }
    }
}

But this violates DRY, because every time I call the endpoint, I need to add another line to show the preloader, and a redundant method.

Option 2: Let each part handle it

I can pass it into the controller, which passes it into the model, which passes it back into the view, but this seems like a lengthy route and an unnecessary parameter.

View {
    var preloader;
    main() {
        Controller.callAPI(url, preloader);

        display(item, position) {
          // display item at position
        }
    }
}

Controller {
    callAPI(url, preloader) {
        if (preloader) preloader.show();
        app.request('GET', function(response){
            Model.handleCode(response, preloader);
        });
    }
}

Model {
    handleCode(response, preloader) {
        if (preloader) preloader.hide();
        var cleaned = clean(response);
        store(cleaned);
        cleaned.foreach(item, position) {
           View.main.display(item, position);
        }
    }
}

Option 3: Extra layer/merge with Controller

My best solution seems to be adding a middle layer for each API call, which passes the preloader and the API content. The downside here is that it's the longest solution, either adding an extra layer or bloating the Controller layer.

View {
    var preloader;
    main() {
        ViewHelper.manageCall(url, preloader);

        display(item, position) {
          // display item at position
        }
    }
}

ViewHelper {
  var preloader;
  manageCall(url, preloader) {
      this.preloader = preloader;
      if (preloader) preloader.show();
  }

  done() {
      if (preloader) preloader.hide();
  }

  display(item, position) {
      View.main.display(item, position);
  }
}

Controller {
    callAPI(url) {
        app.request('GET', function(response){
            Model.handleCode(response);
        });
    }
}

Model {
    handleCode(response) {
        ViewHelper.done();
        var cleaned = clean(response);
        store(cleaned);
        cleaned.forEach(item, position) {
           ViewHelper.display(item, position);
        }
    }
}

Are there any other ways I can do this? MVC is not a requirement.

3

The way I see it the second option is the most consistent with your existent solution. However, what concerns me is that your MVC implementation forces your model class to be concerned with representation concerns such as displaying a spinner.

This is the reason why I suggest making your controller code async. Historically, callbacks were the first implementation of asynchronicity and I decided to stick with it for another pseudocode example.

View {
    var preloader;

    display(itemsWithPositions) {
          // display item at position
        }

    dismissPreloader() {
        preloader.hide();
    }

    callback(itemsWithPositions) {
        dismissPreloader()
        display(itemsWithPositions)
    }   

    main() {
        preloader.show();
        Controller.callAPI(url, callback);
    }
}

Controller {
    callAPI(url, callback) {
        app.request('GET', function(response){
            var itemsWithPositions = Model.handleCode(response);
            callback(itemsWithPositions)
        });
    }
}

Model {
    handleCode(response) {
        var cleaned = cleanData(response);
        store(cleaned);
        var itemsWithPositions = []
        cleaned.forEach(item, position) {
           itemsWithPositions.push(item, position);
        }
        return itemsWithPositions;
    }
}

What has changed comparing to your implementation is that model now only produces items that should be displayed but it's the view which decides how to actually represent them.

Also, please note that most of the modern languages now have constructs like promises, futures or async/await so you shouldn't necessarily stick with callback implementation and it's here only for illustration purposes.

| improve this answer | |
  • Oh, this is a good design. Thanks. I'll have to learn how things like async and promises work. – Muz Dec 26 '19 at 2:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.