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.

Martin K
2,9478 silver badges17 bronze badges
asked Dec 24, 2019 at 8:30

1 Answer 1

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.

answered Dec 24, 2019 at 13:31
1
  • Oh, this is a good design. Thanks. I'll have to learn how things like async and promises work. Commented Dec 26, 2019 at 2:15

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.