Deliver asynchronous requests using Web Components (Model-View-Controller)

I am currently working on developing an application using pure javascript and Web Components. My goal is to incorporate the MVC Pattern, but I have encountered a challenge with asynchronous calls from the model.

Specifically, I am creating a meal-list component that retrieves data from an API in JSON format:

[
   {
     id: 1,
     name: "Burger",
    },
]

The issue arises when I attempt to pass this data from the model to the controller, and then to the view.

meals.js (Model)

export default {
    get all() {
        const url = 'http://localhost:8080/meals';
        let menu = [];
        fetch(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            },
        }).then(res => {
            return res.json()
        }).then(data => {
            console.log(data);
            menu = data;
        });
        return menu;
    },
}

This is the approach I took to fetch data from the API.

meal-list.component.js (Controller)

import Template from './meal-list.template.js'
import Meal from '../../../../data/meal.js'

export default class MealListComponent extends HTMLElement {
    connectedCallback() {
        this.attachShadow({mode: 'open'});
        this.shadowRoot.innerHTML = Template.render(Meal.all);
    }
}

if (!customElements.get('mp-meal-list')) {
    customElements.define('mp-meal-list', MealListComponent);
}

meal-list.template.js (View)

export default {
    render(meals) {
        return `${this.html(meals)}`;
    },
    html(meals) {
        let content = `<h1>Menu</h1>
                       <div class="container">`;
        // Display the data from the API using meals.forEach
        return content + '</div>';
    },
}

My main struggle lies in returning the asynchronous data from the model to the view. When attempting to simply return the data, it is undefined. Saving the data into an array also does not work. I have considered returning the entire fetch() method, but this returns a promise, which I believe the controller should not handle.

I have explored numerous resources, such as a notable thread on How do I return the response from an asynchronous call?, but have yet to find a viable solution tailored to my specific scenario.

Answer №1

By specifying `speisekarte` as an array, my expectation is that it will consistently return an empty array. Unfortunately, in the current implementation, the fetch executes too late, causing it to not fulfill the promise in time.

To address this issue, there are several approaches you could take:

  • Consider providing a callback function for the fetch result
  • Alternatively, use event dispatch and listeners to notify your application when the data has finished loading, allowing it to begin rendering

The link in your post contains valuable information on callbacks and async/await, providing a comprehensive explanation on the topic.

Answer №2

I am grateful to lotype and Danny '365CSI' Engelman for providing me with the ideal solution for my project. By using custom events and an EventBus, I was able to successfully resolve the issue:

meal.js (model)

get meals() {
    const url = 'http://localhost:8080/meals';

    return fetch(url, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        },
    }).then(res => {
        return res.json()
    }).then(data => {
        let ce = new CustomEvent(this.ESSEN_CHANGE_EVENT, {
            detail: {
                action: this.ESSEN_LOAD_ACTION,
                meals: data,
            }
        });
        EventBus.dispatchEvent(ce);
    });
},

EventBus.js (from book: Web Components in Action)

export default {
    /**
     * add event listener
     * @param type
     * @param cb
     * @returns {{type: *, callback: *}}
     */
    addEventListener(type, cb) {
        if (!this._listeners) {
            this._listeners = [];
        }

        let listener = {type: type, callback: cb};
        this._listeners.push(listener);
        return listener;
    },

    /**
     * trigger event
     * @param ce
     */
    dispatchEvent(ce) {
        this._listeners.forEach(function (l) {
            if (ce.type === l.type) {
                l.callback.apply(this, [ce]);
            }
        });
    }
}

Now, once the data is available, a signal is sent to the event bus. The meal-list-component is then able to receive the data by listening for the events:

export default class MealListComponent extends HTMLElement {

    connectedCallback() {
        this.attachShadow({mode: 'open'});
        this.shadowRoot.innerHTML = Template.render();
        this.dom = Template.mapDOM(this.shadowRoot);

        // Load Speisekarte on init
        this.dom.meals.innerHTML = Template.renderMeals(MealData.all);

        // Custom Eventlistener - always triggers when essen gets added, deleted, updated etc.
        EventBus.addEventListener(EssenData.ESSEN_CHANGE_EVENT, e => {
            this.onMealChange(e);
        });
    }

    onMealChange(e) {
        switch (e.detail.action) {
            case EssenData.ESSEN_LOAD_ACTION:
                this.dom.meals.innerHTML = Template.renderMEals(e.detail.meals);
                break;
        }
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to ensure that the select option is always at the top of the select dropdown list in a React.js application

Within the select dropdown menu, I have included a default option value of "--select--". As it currently appears at the bottom of the list, I would like it to be displayed at the top instead. Can someone please assist me in achieving this? In the sandbox, ...

Google Chrome extension with Autofocus functionality

I developed a user-friendly Chrome extension that allows users to search using a simple form. Upon opening the extension, I noticed that there is no default focus on the form, requiring an additional click from the user. The intended behavior is for the ...

Modifying a Nested Component with react-icons

As I work on creating a rating component that utilizes react-icons to display icons, I have encountered an interesting challenge. Currently, I am using the FaStarhalf icon which represents a pre-filled half star that can be flipped to give the appearance o ...

Observing nested data changes in Vue.JS?

In my Vue.js components, I have a group of checkboxes that are sending data to the parent element. My goal is to monitor this data for changes and use it to filter information in an API call. When certain checkboxes are selected, the data stored on the pa ...

JavaScript Calendar lacks two days

I'm currently developing a custom calendar application using Javascript with Vue JS. One of the methods I've created is for getting the number of days in a specific month: daysInYearMonth(y,m){ return new Date(y, m, 0).getDate() } When I log ...

Using Javascript to search and refine a JSON field based on a specific string

I am attempting to use JavaScript to filter a JSON field based on a string input. Essentially, I have a search box and a simulated JSON response. When I type letters into the search box, an ajax call should filter my simulated response based on the input s ...

Unable to locate the module model in sequelize

Having some trouble setting up a basic connection between Postgres and SQL using Sequelize. I keep getting an error where I can't require the model folder, even though in this tutorial he manages to require the model folder and add it to the sync, lik ...

How to handle .find errors in Mongoose, Node.js, and Express

I am attempting to perform a search with mongoose, but I am encountering the following error message: "TypeError: Query.find is not a function" Below is the model I am using: // file: ./models/request.js var mongoose = require('mongoose'), ...

Ways to automatically refresh a page in Javascript after a short period of user inactivity

Similar Question: How Can I Modify This Code To Redirect Only When There Is No Mouse Movement I am looking to update a web page automatically if the user is inactive, specifically through key presses or mouse clicks using Javascript. ...

Lazy loading AngularJS UI router named views is an efficient way to improve performance

AngularJS UI router named views loading based on user access rather than loading at the time of state route access. Example: $stateProvider .state("login", { url: "/login", templateUrl: getTemplateUrl("login/Index") }) ...

Add a third-party library file to Visual Studio

I'm currently working in Visual Studios and attempting to utilize the library provided at . However, I am encountering difficulties when trying to import the library. I have added the file to the project and attempted to use it within the Book.js (Vi ...

Two DataTables on a Single Page - Odd Initialization in the Second One

My page contains two dataTable elements and I've created a method as shown below: function ToDataTable() { $(".dataTable").css("width", "100%"); $(".dataTable").each(function () { var $that = $(this); /* Start of custom ...

Deactivate a component in React Js

Starting out in the world of react Js and looking to disable certain elements based on conditions Within my form, I have a select element and two input elements Depending on the selected item, one of the input elements should be disabled Here is a snipp ...

Encountering the "node:internal/modules/cjs/loader:1050" error while attempting to execute a folder using the "npm run dev" command

I am encountering an issue while attempting to execute a folder using npm run dev, consistently resulting in the same error message. PS C:\Users\My Name\Desktop\My Folder> npm run dev Debugger attached. > <a href="/cdn-cgi/l/e ...

A warning has been issued: CommonsChunkPlugin will now only accept one argument

I am currently working on building my Angular application using webpack. To help me with this process, I found a useful link here. In order to configure webpack, I created a webpack.config.js file at the package.json level and added the line "bundle": "web ...

What is the best way to eliminate the occurrence of the word 'undefined' from the cycle output?

Can anyone assist with solving this issue? The webpage on JSFIDDLE displays 4 news containers, but an 'undefined' string appears before the first news container. I am looking to remove that 'undefined' string. Here is the HTML code: ...

Node.js Interceptor: A powerful tool for controlling and

I've been attempting to construct an interceptor in Node.js, but I haven't had much success. I'm trying to create the interceptor to capture each request and include a unique header retrieved from the Koa context. Basically, when making an ...

Triggering the body onunload event

I am currently developing a HTA that needs to make final modifications on the onunload event. However, I am facing an issue as the event does not appear to be triggered. Can someone confirm if this event is still supported? Is there an equivalent event in ...

The Strapi registration feature in version 4 is giving a "method not allowed 405" error, which

Feeling a bit confused with a Strapi query. I am currently working on v4 and trying to set up a registration feature. The code snippet for invoking the function is provided below. Everything seems fine, such as submitting function variables, etc. However, ...

Challenges encountered when bringing in modules from THREE.js

Is there a way for me to import the custom geometry file called "OutlinesGeometry.js" from this link? I've attempted to import it like this: <script type="module" src="./three/build/three.module.js"></script> <scrip ...