*NOTE: A full working demo can be found here - GitHub - dales/dash-sw-demo
My dash application allows for users to dynamically add data visualizations. This is done by passing a list of “views” to the backend callback which then gets the new view appended to this list and returned to client.
This works fine for small tables and graphs but when dealing with large datasets the performance takes a huge hit since all of the current views need to be sent to the backend and then returned (this can be 10s of MB of data just to add a new empty view).
To get around this I have created a JavaScript ServiceWorker that intercepts all requests that match _dash-component-update url. It then inspects the POST payload to see if it is a request to add a new view. If it is, then it strips the current views from the payload and sends the request without them. It also waits for the response and adds them back in also adjusting the ids of the new component.
This works perfectly at the moment and is very efficient.
Can anyone think of another way to do this that is more dashy? I havent see the use of service workers anywhere but for my use case this solution works well as it kind of strips the large datasets from the callback
Any other solutions or suggestions would be appreciated. Here is the basic flow of my code
===============================
/assets/sw/sw_register.js
window.addEventListener('load', () => {
if (!('serviceWorker' in navigator)) {
// service workers not supported 😣
return
}
navigator.serviceWorker.register('/service_worker', {scope: '/'}).then(
() => {
console.log("Service Worker Registered Successfully 👍")
},
err => {
console.error('Service Worker registration failed! 😱', err)
}
)
})
*Needs to be on root scope to get access to /_dash-update-component requests
To load the service worker from file add the following view to your dash app
app.py
@app_flask.route("/service_worker")
def service_worker():
file_name = "serviceworker.js"
return send_file(file_name)
serviceworker.js
self.addEventListener("install", event => {
console.log("Service worker installed 👍");
});
self.addEventListener("activate", event => {
event.waitUntil(clients.claim());
console.log("Service worker activated 👍");
});
self.addEventListener("fetch", function(event) {
if (event.request.url.includes("_dash-update-component")) {
return event.respondWith(remove_data_obj(event.request));
}
});
async function remove_data_obj(initialRequest) {
const request = await initialRequest.clone();
const payload = await request.json();
// Change the payload being sent
......
response = await fetch(destURL, {
method: request.method,
headers,
body: JSON.stringify(Object.assign(payload))
});
// Adjust the response payload
....
return new Response(JSON.stringify(fixedJson), {
headers: response.headers
});
My magic happens inside the remove_data_obj function
If you look in the network tab you can see the service worker now handles the fetch