Skip to content

Commit 0aaa5bf

Browse files
committed
Extract deferring handle logic to its own subscriber
1 parent fea1c7c commit 0aaa5bf

File tree

6 files changed

+162
-82
lines changed

6 files changed

+162
-82
lines changed

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;
@@ -214,6 +215,14 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
214215
->addTag('container.service_subscriber', ['key' => LiveControllerAttributesCreator::class, 'id' => 'ux.live_component.live_controller_attributes_creator'])
215216
;
216217

218+
$container->register('ux.live_component.defer_live_component_subscriber', DeferLiveComponentSubscriber::class)
219+
->setArguments([
220+
new Reference('ux.twig_component.component_stack'),
221+
new Reference('ux.live_component.live_controller_attributes_creator'),
222+
])
223+
->addTag('kernel.event_subscriber')
224+
;
225+
217226
$container->register('ux.live_component.deterministic_id_calculator', DeterministicTwigIdCalculator::class);
218227
$container->register('ux.live_component.fingerprint_calculator', FingerprintCalculator::class)
219228
->setArguments(['%kernel.secret%']);

src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
*/
3636
final class AddLiveAttributesSubscriber implements EventSubscriberInterface, ServiceSubscriberInterface
3737
{
38-
private const DEFAULT_LOADING_TAG = 'div';
39-
40-
private const DEFAULT_LOADING_TEMPLATE = null;
41-
4238
public function __construct(
4339
private ComponentStack $componentStack,
4440
private TemplateMap $templateMap,
@@ -77,17 +73,6 @@ public function onPreRender(PreRenderEvent $event): void
7773
$originalAttributes['data-embedded-template-index'],
7874
);
7975
}
80-
81-
$inputProps = $event->getMountedComponent()->getInputProps();
82-
$isDeferred = \array_key_exists('defer', $inputProps);
83-
84-
if ($isDeferred) {
85-
$event->setTemplate('@LiveComponent/deferred.html.twig');
86-
87-
$variables['loadingTag'] = $originalAttributes['loading-tag'] ?? self::DEFAULT_LOADING_TAG;
88-
$variables['loadingTemplate'] = $originalAttributes['loading-template'] ?? self::DEFAULT_LOADING_TEMPLATE;
89-
$attributes = $attributes->without('defer', 'loading-element', 'loading-template');
90-
}
9176
}
9277

9378
// "key" is a special attribute: don't actually render it
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\LiveComponent\Util\LiveControllerAttributesCreator;
9+
use Symfony\UX\TwigComponent\ComponentStack;
10+
use Symfony\UX\TwigComponent\Event\PreRenderEvent;
11+
12+
final class DeferLiveComponentSubscriber implements EventSubscriberInterface
13+
{
14+
private const PRIORITY = 16;
15+
16+
private const DEFAULT_LOADING_TAG = 'div';
17+
18+
private const DEFAULT_LOADING_TEMPLATE = null;
19+
20+
public function __construct(
21+
private ComponentStack $componentStack,
22+
private LiveControllerAttributesCreator $attributesCreator,
23+
) {
24+
}
25+
26+
public function onPreRender(PreRenderEvent $event): void
27+
{
28+
$mountedComponent = $event->getMountedComponent();
29+
$inputProps = $mountedComponent->getInputProps();
30+
$isDeferred = $inputProps['defer'] ?? false;
31+
32+
if (!$isDeferred) {
33+
return;
34+
}
35+
36+
$metadata = $event->getMetadata();
37+
$variables = $event->getVariables();
38+
$attributesKey = $metadata->getAttributesVar();
39+
40+
$event->setTemplate('@LiveComponent/deferred.html.twig');
41+
42+
$originalAttributes = $variables[$attributesKey]->all();
43+
44+
$attributes = $originalAttributes;
45+
$attributes = array_filter(
46+
$attributes,
47+
fn ($key) => !\in_array($key, ['defer', 'loading-template', 'loading-tag'], true),
48+
\ARRAY_FILTER_USE_KEY,
49+
);
50+
51+
$variables[$attributesKey] = $attributes;
52+
$variables['loadingTag'] = $originalAttributes['loading-tag'] ?? self::DEFAULT_LOADING_TAG;
53+
$variables['loadingTemplate'] = $originalAttributes['loading-template'] ?? self::DEFAULT_LOADING_TEMPLATE;
54+
55+
$mountedComponent->setAttributes(
56+
$mountedComponent->getAttributes()->without('defer', 'loading-template', 'loading-tag'),
57+
);
58+
59+
$event->setVariables($variables);
60+
}
61+
62+
public static function getSubscribedEvents(): array
63+
{
64+
return [PreRenderEvent::class => ['onPreRender', self::PRIORITY]];
65+
}
66+
}

src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -133,71 +133,4 @@ public function testItDoesNotOverrideDataLiveIdIfSpecified(): void
133133
$this->assertSame('todo-item-1', $lis->first()->attr('data-live-id'));
134134
$this->assertSame('todo-item-3', $lis->last()->attr('data-live-id'));
135135
}
136-
137-
public function testItSetsDeferredTemplateIfLiveIdNotPassed(): void
138-
{
139-
$div = $this->browser()
140-
->visit('/render-template/render_deferred_component')
141-
->assertSuccessful()
142-
->crawler()
143-
->filter('div')
144-
;
145-
146-
$this->assertSame('', trim($div->html()));
147-
$this->assertSame('live:connect->live#$render', $div->attr('data-action'));
148-
149-
$component = $this->mountComponent('deferred_component', [
150-
'data-live-id' => $div->attr('data-live-id'),
151-
]);
152-
153-
$dehydrated = $this->dehydrateComponent($component);
154-
155-
$div = $this->browser()
156-
->visit('/_components/deferred_component?props='.urlencode(json_encode($dehydrated->getProps())))
157-
->assertSuccessful()
158-
->crawler()
159-
->filter('div')
160-
;
161-
162-
$this->assertSame('Long awaited data', $div->html());
163-
}
164-
165-
public function testItIncludesGivenTemplateWhileLoadingDeferredComponent(): void
166-
{
167-
$div = $this->browser()
168-
->visit('/render-template/render_deferred_component_with_template')
169-
->assertSuccessful()
170-
->crawler()
171-
->filter('div')
172-
;
173-
174-
$this->assertSame('I\'m loading a reaaaally slow live component', trim($div->html()));
175-
176-
$component = $this->mountComponent('deferred_component', [
177-
'data-live-id' => $div->attr('data-live-id'),
178-
]);
179-
180-
$dehydrated = $this->dehydrateComponent($component);
181-
182-
$div = $this->browser()
183-
->visit('/_components/deferred_component?props='.urlencode(json_encode($dehydrated->getProps())))
184-
->assertSuccessful()
185-
->crawler()
186-
->filter('div')
187-
;
188-
189-
$this->assertStringContainsString('Long awaited data', $div->html());
190-
}
191-
192-
public function testItAllowsToSetCustomLoadingHtmlTag(): void
193-
{
194-
$crawler = $this->browser()
195-
->visit('/render-template/render_deferred_component_with_li_tag')
196-
->assertSuccessful()
197-
->crawler()
198-
;
199-
200-
$this->assertSame(0, $crawler->filter('div')->count());
201-
$this->assertSame(1, $crawler->filter('li')->count());
202-
}
203136
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Tests\Functional\EventListener;
6+
7+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
8+
use Symfony\UX\LiveComponent\Tests\LiveComponentTestHelper;
9+
use Zenstruck\Browser\Test\HasBrowser;
10+
11+
final class DeferLiveComponentSubscriberTest extends KernelTestCase
12+
{
13+
use HasBrowser;
14+
use LiveComponentTestHelper;
15+
16+
public function testItSetsDeferredTemplateIfLiveIdNotPassed(): void
17+
{
18+
$div = $this->browser()
19+
->visit('/render-template/render_deferred_component')
20+
->assertSuccessful()
21+
->crawler()
22+
->filter('div')
23+
;
24+
25+
$this->assertSame('', trim($div->html()));
26+
$this->assertSame('live:connect->live#$render', $div->attr('data-action'));
27+
28+
$component = $this->mountComponent('deferred_component', [
29+
'data-live-id' => $div->attr('data-live-id'),
30+
]);
31+
32+
$dehydrated = $this->dehydrateComponent($component);
33+
34+
$div = $this->browser()
35+
->visit('/_components/deferred_component?props='.urlencode(json_encode($dehydrated->getProps())))
36+
->assertSuccessful()
37+
->crawler()
38+
->filter('div')
39+
;
40+
41+
$this->assertSame('Long awaited data', $div->html());
42+
}
43+
44+
public function testItIncludesGivenTemplateWhileLoadingDeferredComponent(): void
45+
{
46+
$div = $this->browser()
47+
->visit('/render-template/render_deferred_component_with_template')
48+
->assertSuccessful()
49+
->crawler()
50+
->filter('div')
51+
;
52+
53+
$this->assertSame('I\'m loading a reaaaally slow live component', trim($div->html()));
54+
55+
$component = $this->mountComponent('deferred_component', [
56+
'data-live-id' => $div->attr('data-live-id'),
57+
]);
58+
59+
$dehydrated = $this->dehydrateComponent($component);
60+
61+
$div = $this->browser()
62+
->visit('/_components/deferred_component?props='.urlencode(json_encode($dehydrated->getProps())))
63+
->assertSuccessful()
64+
->crawler()
65+
->filter('div')
66+
;
67+
68+
$this->assertStringContainsString('Long awaited data', $div->html());
69+
}
70+
71+
public function testItAllowsToSetCustomLoadingHtmlTag(): void
72+
{
73+
$crawler = $this->browser()
74+
->visit('/render-template/render_deferred_component_with_li_tag')
75+
->assertSuccessful()
76+
->crawler()
77+
;
78+
79+
$this->assertSame(0, $crawler->filter('div')->count());
80+
$this->assertSame(1, $crawler->filter('li')->count());
81+
}
82+
}

src/TwigComponent/src/MountedComponent.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public function getAttributes(): ComponentAttributes
5353
return $this->attributes;
5454
}
5555

56+
public function setAttributes(ComponentAttributes $attributes): void
57+
{
58+
$this->attributes = $attributes;
59+
}
60+
5661
public function getInputProps(): array
5762
{
5863
if (null === $this->inputProps) {

0 commit comments

Comments
 (0)