Skip to content

Commit e26be66

Browse files
committed
feature #1143 Add deferred live components (jakubtobiasz)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- Add deferred live components | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Tickets | Fix #994 | License | MIT Initially this feature was called `lazy loading`, but this name could be misleading, as my solution doesn't provide **real** lazy loading. So, I named it `deferred live components`, I guess this name fits better. How to use it? ```twig <twig:MyComponent defer /> ``` In such case we'll get an empty div. Once `live:connect` event called, it'll load the component in the background. We can also define a template to be rendered while loading (e.g. a cool spinner or some text). ```twig <twig:MyComponent defer defer-loading-template="my_cool_spinner.html.twig" /> ``` I'm open for any suggestions 🙌🏼! Commits ------- d93cc29 Add deferred live components
2 parents 614cca9 + d93cc29 commit e26be66

15 files changed

+257
-13
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 2.13.0
4+
5+
- Add deferred rendering of Live Components
6+
37
## 2.12.0
48

59
- Add support for (de)hydrating DTO classes in `LiveProp`.

src/LiveComponent/doc/index.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,32 @@ To validate only on "change", use the ``on(change)`` modifier:
22202220
class="{{ _errors.has('post.content') ? 'is-invalid' : '' }}"
22212221
>
22222222

2223+
Deferring the Loading
2224+
---------------------
2225+
2226+
Certain components might be heavy to load. You can defer the loading of these components
2227+
until after the rest of the page has loaded. To do this, use the ``defer`` attribute:
2228+
2229+
.. code-block:: twig
2230+
2231+
{{ component('SomeHeavyComponent', { defer: true }) }}
2232+
2233+
Doing so will render an empty "placeholder" tag with the live attributes. Once the ``live:connect`` event is triggered,
2234+
the component will be rendered asynchronously.
2235+
2236+
By default the rendered tag is a ``div``. You can change this by specifying the ``loading-tag`` attribute:
2237+
2238+
.. code-block:: twig
2239+
2240+
{{ component('SomeHeavyComponent', { defer: true, loading-tag: 'span' }) }}
2241+
2242+
If you need to signify that the component is loading, use the ``loading-template`` attribute.
2243+
This lets you provide a Twig template that will render inside the "placeholder" tag:
2244+
2245+
.. code-block:: twig
2246+
2247+
{{ component('SomeHeavyComponent', { defer: true, loading-template: 'spinning-wheel.html.twig' }) }}
2248+
22232249
Polling
22242250
-------
22252251

src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\UX\LiveComponent\Controller\BatchActionController;
2626
use Symfony\UX\LiveComponent\EventListener\AddLiveAttributesSubscriber;
2727
use Symfony\UX\LiveComponent\EventListener\DataModelPropsSubscriber;
28+
use Symfony\UX\LiveComponent\EventListener\DeferLiveComponentSubscriber;
2829
use Symfony\UX\LiveComponent\EventListener\InterceptChildComponentRenderSubscriber;
2930
use Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber;
3031
use Symfony\UX\LiveComponent\EventListener\ResetDeterministicIdSubscriber;
@@ -215,6 +216,14 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
215216
->addTag('container.service_subscriber', ['key' => LiveControllerAttributesCreator::class, 'id' => 'ux.live_component.live_controller_attributes_creator'])
216217
;
217218

219+
$container->register('ux.live_component.defer_live_component_subscriber', DeferLiveComponentSubscriber::class)
220+
->setArguments([
221+
new Reference('ux.twig_component.component_stack'),
222+
new Reference('ux.live_component.live_controller_attributes_creator'),
223+
])
224+
->addTag('kernel.event_subscriber')
225+
;
226+
218227
$container->register('ux.live_component.deterministic_id_calculator', DeterministicTwigIdCalculator::class);
219228
$container->register('ux.live_component.fingerprint_calculator', FingerprintCalculator::class)
220229
->setArguments(['%kernel.secret%']);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\EventListener;
6+
7+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8+
use Symfony\UX\TwigComponent\Event\PostMountEvent;
9+
use Symfony\UX\TwigComponent\Event\PreRenderEvent;
10+
11+
final class DeferLiveComponentSubscriber implements EventSubscriberInterface
12+
{
13+
private const DEFAULT_LOADING_TAG = 'div';
14+
15+
private const DEFAULT_LOADING_TEMPLATE = null;
16+
17+
public function onPostMount(PostMountEvent $event): void
18+
{
19+
$data = $event->getData();
20+
if (\array_key_exists('defer', $data)) {
21+
$event->addExtraMetadata('defer', true);
22+
unset($event->getData()['defer']);
23+
}
24+
25+
if (\array_key_exists('loading-template', $data)) {
26+
$event->addExtraMetadata('loading-template', $data['loading-template']);
27+
unset($event->getData()['loading-template']);
28+
}
29+
30+
if (\array_key_exists('loading-tag', $data)) {
31+
$event->addExtraMetadata('loading-tag', $data['loading-tag']);
32+
unset($event->getData()['loading-tag']);
33+
}
34+
35+
$event->setData($data);
36+
}
37+
38+
public function onPreRender(PreRenderEvent $event): void
39+
{
40+
$mountedComponent = $event->getMountedComponent();
41+
42+
if (!$mountedComponent->hasExtraMetadata('defer')) {
43+
return;
44+
}
45+
46+
$event->setTemplate('@LiveComponent/deferred.html.twig');
47+
48+
$variables = $event->getVariables();
49+
$variables['loadingTemplate'] = self::DEFAULT_LOADING_TEMPLATE;
50+
$variables['loadingTag'] = self::DEFAULT_LOADING_TAG;
51+
52+
if ($mountedComponent->hasExtraMetadata('loading-template')) {
53+
$variables['loadingTemplate'] = $mountedComponent->getExtraMetadata('loading-template');
54+
}
55+
56+
if ($mountedComponent->hasExtraMetadata('loading-tag')) {
57+
$variables['loadingTag'] = $mountedComponent->getExtraMetadata('loading-tag');
58+
}
59+
60+
$event->setVariables($variables);
61+
}
62+
63+
public static function getSubscribedEvents(): array
64+
{
65+
return [
66+
PostMountEvent::class => ['onPostMount'],
67+
PreRenderEvent::class => ['onPreRender'],
68+
];
69+
}
70+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<{{ loadingTag }} {{ attributes }} data-action="live:connect->live#$render">
2+
{% block loadingContent %}
3+
{% if loadingTemplate != null %}
4+
{{ include(loadingTemplate) }}
5+
{% endif %}
6+
{% endblock %}
7+
</{{ loadingTag }}>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
6+
7+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
8+
use Symfony\UX\LiveComponent\DefaultActionTrait;
9+
10+
#[AsLiveComponent('deferred_component')]
11+
final class DeferredComponent
12+
{
13+
use DefaultActionTrait;
14+
15+
public function getLongAwaitedData(): string
16+
{
17+
return 'Long awaited data';
18+
}
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div {{ attributes }}>{{ computed.longAwaitedData }}</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I'm loading a reaaaally slow live component
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:deferred_component defer />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:deferred_component defer loading-tag='li' />

0 commit comments

Comments
 (0)