[LiveComponent] Advanced Polling Features
| Q | A |
|---|---|
| Bug fix? | no |
| New feature? | yes |
| Docs? | yes |
| Issues | |
| License | MIT |
Added support for poll lifecycle hooks and pollingDirector control in UX LiveComponent
This PR introduces the following updates to the LiveComponent polling system:
- Added
limit(n)modifier to limit the number of poll iterations. - Added new lifecycle events:
poll:started,poll:running,poll:paused,poll:stopped, andpoll:error. - Added the
pollingDirectorAPI to start, stop, pause, or resume polling via JavaScript. - Added usage examples using Stimulus controllers.
- Updated documentation with examples and descriptions for all new methods and hooks.
This enhancement introduces support for limiting polling cycles using the limit modifier. Once the limit is reached, the polling will automatically stop.
Additionally, new lifecycle hooks (poll:started, poll:running, poll:paused, poll:stopped, poll:error ) provide full control over the polling process via JavaScript (e.g. Stimulus controllers).
Developers can now track the polling count value to build dynamic UI components such as counters, countdown timers, or progress bars.
Preview:
https://github.com/user-attachments/assets/0602cb70-1fe1-4fe2-8956-e2efa1808dbc
Basic usage
By default, polling runs the $render action every 2000 milliseconds (2 seconds).
{# Unlimited polling every 2 seconds (default) using $render #}
<div {{ attributes }} data-poll>...</div>
{# Poll every 5 seconds, up to 20 times, using $render #}
<div {{ attributes }} data-poll="delay(5000)|limit(20)|$render">...</div>
{# Poll a custom action (savePost) every 3 seconds, up to 10 times #}
<div {{ attributes }} data-poll="delay(3000)|limit(10)|savePost">...</div>
Available Modifiers
delay(ms)— The delay between polls in milliseconds (default:2000)limit(n)— Maximum number of times to run the poll (default: unlimited)actionName— The component action to call (default:$render)
Poll Hooks
The component emits lifecycle hooks during polling. You can listen to these using JavaScript, for example:
// controllers/poll_controller.js
import { Controller } from '@hotwired/stimulus';
import { getComponent } from '@symfony/ux-live-component';
export default class extends Controller {
async connect() {
this.component = await getComponent(this.element);
// Disable default error window (optional)
this.component.on('response:error', (backendResponse, controls) => {
controls.displayError = false;
});
this.component.on('poll:started', ({ actionName, limit }) => {
console.log(`Polling started: ${actionName}, limit: ${limit}`);
});
this.component.on('poll:running', ({ actionName, count, limit }) => {
console.log(`Polling running: ${actionName} (${count}/${limit})`);
});
this.component.on('poll:paused', ({ actionName, count, limit }) => {
console.log(`Polling paused: ${actionName}`);
});
this.component.on('poll:stopped', ({ actionName, finalCount, limit }) => {
console.log(`Polling stopped: ${actionName}, total runs: ${finalCount}`);
});
this.component.on('poll:error', ({ actionName, finalCount, limit, errorMessage }) => {
console.error(`Polling error on ${actionName}: ${errorMessage}`);
});
}
}
These events are dispatched on the component and can be handled using Stimulus. You must retrieve the component instance with
getComponent(this.element)before accessing event listeners.
Handling Poll Actions (Start, Stop, Pause, Resume)
Polling can be programmatically managed using the pollingDirector API.
This allows you to start, pause, resume, or stop polling dynamically for a given action.
<div {{ attributes.defaults(stimulus_controller('poll')) }} data-poll="delay(5000)|limit(50)|$render">
<button type="button" data-action="click->poll#start" data-poll-action-param="$render">Start</button>
<button type="button" data-action="click->poll#stop" data-poll-action-param="$render">Stop</button>
</div>
// controllers/poll_controller.js
import { Controller } from '@hotwired/stimulus';
import { getComponent } from '@symfony/ux-live-component';
export default class extends Controller {
static values = {
action: String
}
async start(event) {
const actionName = event.params.action;
(await getComponent(this.element)).pollingDirector.start(actionName);
}
async stop(event) {
const actionName = event.params.action;
(await getComponent(this.element)).pollingDirector.stop(actionName);
}
async pause(event) {
const actionName = event.params.action;
(await getComponent(this.element)).pollingDirector.pause(actionName);
}
async resume(event) {
const actionName = event.params.action;
(await getComponent(this.element)).pollingDirector.resume(actionName);
}
}
Available Methods
The pollingDirector API exposes the following methods:
component.pollingDirector.start(actionName)— Starts polling for the given action (if previously stopped).component.pollingDirector.pause(actionName)— Temporarily pauses polling. Can be resumed later.component.pollingDirector.resume(actionName)— Resumes a previously paused poll.component.pollingDirector.stop(actionName)— Stops polling entirely. Usestart()to restart.