Lazy Loading
Liveblade allows you to lazy load components that would otherwise slow down the initial page load.
For example, imagine you have a Revenue
component which contains a slow database query in mount()
:
from django.db.models import Sum
from pyblade import liveblade
from app.models import Transaction
class Revenue(liveblade.Component):
amount: float
def mount(self):
# Slow database query...
self.amount = Transaction.objects.month_to_date().aggregate(
total=Sum('amount', default=0))['total']
def render(self):
return self.view('liveblade.revenue')
<div>
Revenue this month: {{ amount }}
</div>
Without lazy loading, this component would delay the loading of the entire page and make your entire application feel slow.
To enable lazy loading, you can pass the lazy
attribute into the component tag :
<liveblade:revenue lazy />
... or set the lazy=True
key-word argument when using the @liveblade
directive:
@liveblade("revenue", lazy=True)
Now, instead of loading the component right away, Liveblade will skip this component, loading the page without it. Then, when the component is visible in the viewport, Liveblade will make a network request to fully load this component on the page.
Lazy requests are isolated by default
Unlike other network requests in Liveblade, lazy loading updates are isolated from each other when sent to the server. This keeps lazy loading fast, by loading each component in parallel when a page loads. Read more on disabling this behavior here →
Rendering placeholder HTML
By default, Liveblade will insert an empty <div></div>
for your lazy loading component before it is fully loaded. As the component will initially be invisible to users, it can be jarring when the component suddenly appears on the page.
To signal to your users that the component is being loaded, you can define a placeholder()
method to render any kind of placeholder HTML you like, including loading spinners and skeleton placeholders:
from django.db.models import Sum
from pyblade import liveblade
from app.models import Transaction
class Revenue(liveblade.Component):
amount: float
def mount(self):
# Slow database query...
self.amount = Transaction.objects.month_to_date().aggregate(
total=Sum('amount', default=0))['total']
def placeholder(self):
return self.inline("""
<div>
<!-- Loading spinner... -->
<svg>...</svg>
</div>
""")
def render(self):
return self.view('liveblade.revenue')
Because the above component specifies a "placeholder" by returning inline HTML from a placeholder()
method, the user will see an SVG loading spinner on the page until the component is fully loaded.
The placeholder and the component must share the same element type
For instance, if your placeholder's root element type is a <div>
, your component must also use a <div>
as root element.
For more complex loaders, such as skeletons, you can return a template from the placeholder()
similar to render()
.
def placeholder(self):
return self.view('liveblade.placeholders.skeleton', context={})
If you want to set a default placeholder template for all your lazy-loaded components, you can do so by referencing the view in the pyblade.json
config file:
"liveblade":{
"enabled": true,
"lazy_placeholder": "liveblade.placeholders.default",
}
Now, when a component is lazy-loaded and no placeholder()
method is defined, Liveblade will use the configured PyBlade component (liveblade.placeholders.default
in this case.)
Lazy loading outside of the viewport
By default, Lazy-loaded components aren't full loaded until they enter the browser's viewport, for example when a user scrolls to one.
If you'd rather lazy load all components on a page as soon as the page is loaded, without waiting for them to enter the viewport, you can do so by passing "on_load" into the lazy
parameter:
<liveblade:revenue lazy="on_load" />
... or set the lazy="on_load"
key-word argument when using the @liveblade
directive:
@liveblade("revenue", lazy="on_load")
Now this component will load after the page is ready without waiting for it to be inside the viewport.
Lazy load by default
If you want to enforce that all usages of a component will be lazy-loaded, you can add the @lazy
decorator above the component class:
from pyblade import liveblade
@lazy
class Revenue(liveblade.Component):
...
Pro tip !
You may enable lazy loading outside of the viewport with the on_load=True
parameter:
@lazy(on_load=True)
class Revenue(liveblade.Component):
...
If you want to override lazy loading somewhere, you can set the lazy
parameter to False
:
<liveblade:revenue :lazy="False" />
or
@liveblade("revenue", lazy=False)
Disabling request isolation
If there are multiple lazy-loaded components on the page, each component will make an independent network request, rather than each lazy update being bundled into a single request.
If you want to disable this isolation behavior and instead bundle all updates together in a single network request you can do so with the isolate=False
parameter:
from pyblade import liveblade
@lazy(isolate=False)
class Revenue(liveblade.Component):
...
Now, if there are ten Revenue
components on the same page, when the page loads, all ten updates will be bundled and sent the server as single network request.
Passing in props
In general, you can treat lazy
components the same as normal components, since you can still pass data into them from outside.
For example, here's a scenario where you might pass a time interval into the Revenue
component from a parent component:
<input type="date" b-model="start">
<input type="date" b-model="end">
<liveblade:revenue lazy :start="start" :end="end" />
You can accept this data in mount()
just like any other component:
from django.db.models import Sum
from pyblade import liveblade
from app.models import Transaction
class Revenue(liveblade.Component):
amount: float
def mount(self, start, end):
...
def placeholder(self):
return self.view('liveblade.placeholders.skeleton', context={})
def render(self):
return self.view('liveblade.revenue')
However, unlike a normal component load, a lazy
component has to serialize or "dehydrate" any passed-in properties and temporarily store them on the client-side until the component is fully loaded.
For example, you might want to pass in a Database model to the Revenue
component like so:
<liveblade:revenue lazy :user="user" />
In a normal component, the actual Python in-memory user
model would be passed into the mount()
method of Revenue
. However, because we won't run mount()
until the next network request, Liveblade will internally serialize user
to JSON and then re-query it from the database before the next request is handled.
Typically, this serialization should not cause any behavioral differences in your application.