Displaying Loading Screen when Pages Container is Loading

Hello All,

I was having an issue with wanting to display loading screens only when the pages_content was displaying. This is not possible with just a regularly wrapped loading of the page_container, as this has an unexpected behavior of triggering any time a component is updated inside the layout.

Take this example:

app.py

import dash
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc

app = Dash(__name__, use_pages=True)

sidebar = dbc.Nav(
    [dbc.NavLink(pg['title'], href=pg['path']) for pg in dash.page_registry.values()]
)

app.layout = html.Div([sidebar, dcc.Loading(dash.page_container)])

if __name__ == '__main__':
    app.run(debug=True)

home.py

import dash
from dash import dcc, html, callback, Output, Input
import time

dash.register_page(__name__, path='/', title='Home')

layout = html.Div(
    [
        'testing-home',
        html.Button('button', id='test'),
        html.Div(id='testDiv')
    ]
)

@callback(
    Output('testDiv','children'),
    Input('test', 'n_clicks')
)
def test(n1):
    if n1:
        time.sleep(2)
        return 'testing'
    return dash.no_update

Any time you click the button, you are greeted with this over the whole layout:

Obviously, this is very problematic for any time that you want to have a bunch of updates and callbacks manipulating things.


Here is a workaround that I really like for this, first, we alter the app like so (dls is a nice option, as you can show it initially):

import dash
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc
import dash_loading_spinners as dls

app = Dash(__name__, use_pages=True)

sidebar = dbc.Nav(
    [dbc.NavLink(pg['title'], href=pg['path']) for pg in dash.page_registry.values()]
)

app.layout = html.Div([sidebar, dash.page_container, dls.Hash(id='loading', show_initially=True, fullscreen=True)])

if __name__ == '__main__':
    app.run(debug=True)

Next, add a style.css:

#loading {
    display: none !important;
}

body:has(#_pages_content[data-dash-is-loading="true"]) #loading {
    display: flex !important;
}

The result is that the overall loading screen only displays any time there is a change to the _pages_content that involves a layout being changed and not changes within the layout.

Also, note, that your #loading could be anything you want to display while loading the _pages_content. :slight_smile:

6 Likes

Thanks for this example @jinnyzor. :pray: I just added this to the Tips and Tricks section of the dash-multi-page-apps-domos repo!

2 Likes

As if your were reading my mind! Just recently I thought it’d be cool to have something like that for a page of my current app which takes a while to load. :raised_hands:

2 Likes

I forgot about the dash_loading_spinners . Cool workaround. Thanks for sharing Bryan. I added this post to the “links to helpful posts” in the @Community-Support team.

1 Like

Here is another configuration that demonstrates how to use this with the index method of pages:

app.py:

import dash
from dash import Dash, html, dcc, Output, Input
import dash_bootstrap_components as dbc
import dash_loading_spinners as dls
import time

app = Dash(__name__)

sidebar = dbc.Nav(
    [dbc.NavLink(pg['title'], href=pg['path'], style={'display':'block'}) for pg in [{'path': '/', 'title': 'Home'}, {'path': '/test', 'title': 'test'}]],
)

app.layout = html.Div([sidebar, html.Div(id='page-content'), dcc.Location(id='url'), dls.Hash(id='loading', show_initially=True, fullscreen=True)])

@app.callback(Output('page-content', 'children'), Input('url', 'pathname'))
def navigate(p):
    time.sleep(1)
    if p == '/test':
        return 'test'
    return 'Home'

if __name__ == '__main__':
    app.run(debug=True)

css:

#loading {
    display: none !important;
}

body:has(#page-content[data-dash-is-loading="true"]) #loading {
    display: flex !important;
}