Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Magic with Events, Properties & HTML from Ajax

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

To show off the power of this simple, controller-instance-bound-to-HTML-element concept, let's count how many times each element is clicked and print that inside the element.

Adding a Controller Property

Head over to the controller. I'm going to start by inventing a new property called count in connect(): this.count = 0.

... lines 1 - 2
export default class extends Controller {
connect() {
... line 5
this.count = 0;
... lines 7 - 11

That property isn't a stimulus thing... I'm just creating a property for our own use and initializing it to zero.

Below this, attach a click listener to our element: this.element.addEventListener(), click then a hipster arrow function. Oh, that's mad because I forgot my comma!

Inside, say this.count++ to increment and then this.element.innerHTML = this.count.

... lines 1 - 3
connect() {
... lines 5 - 7
this.element.addEventListener('click', () => {
this.element.innerHTML = this.count;
... lines 13 - 14

What about jQuery?

If you're not super familiar with using the native DOM Element object methods, like addEventListener(), don't worry! I'm not using jQuery because, in a lot of cases, it's really not needed. But if you're more comfortable with using jQuery, awesome! You can totally still use it! Just install it - yarn add jquery --dev - then import it into any file that needs it - import $ from 'jquery'. Oh, and if you're migrating a legacy app where you have jquery already included via a script tag, that's something we talk about in our Encore tutorial.

Anyways, if you are using jQuery, just use $(this.element) before you call any methods - like $(this.element).on('click'). Though, we are going to learn a cooler way to attach event listeners in a few minutes.

Ok: I think this is ready! Move over and refresh. Now... click. Boom! The count increments and prints in the element. And, most importantly, we can see that each element is working independently. This proves that there are two separate objects with two separate count properties.

When data-controller Elements are Loaded via Ajax

This isn't even my favorite part of Stimulus. Down in your browser's inspector, right click on the div around one of our controllers and go to "Edit as HTML". Copy the <div data-controller> and paste to hack in a new one right above it.

What I'm doing is imitating what happens when HTML is added to the page after it's done loading, like via an Ajax call. This is a classic problem with JavaScript. Suppose you have some jQuery code that attaches a click event listener to all elements that have some class. Usually, that code runs when the page finishes loading.

Now, what happens if you load new HTML onto the page later via Ajax and the HTML contains an element with that class? Is the event listener automatically attached to it? Nope! It's not... unless you go to the hassle of manually re-calling your function that attaches the event listener.

So: can stimulus handle this? Can it somehow "notice" that a new element with data-controller was added to the page? The answer is.... yup!

When I click off to add the new element to the page... it works! Behind the scenes Stimulus actually did notice that a new element was added to the page and instantiated a brand new controller object for it. That's incredible! It's a game-changer! I can now write nice controller classes, return HTML via Ajax and not have to worry about re-initializing behavior on that new HTML.

And, of course, the controller works exactly like the other ones: it increments as I click.

If this were the end of Stimulus's features, I'd absolutely use it. But it's not! Let's learn about "targets" next: an easy way to find elements inside of the controller's main element.

Leave a comment!

Login or Register to join the conversation

Hi, the fact that with each click it increases me by 2 instead of 1 is due to some structure of the html.twig file, right ? Thanks


[SOLVED] I found, it is the layout too full of other js files that caused me the double event, I restarted from a very poor layout and stilus is working again.


Hey @pasquale_pellicani ,

Glad to hear you were able to find the solution. Thanks for sharing the actual problem



I think it's a matter of import and positions of the generated assets, for example now I miss the jQuery but if I include it manually (without the webpack) it goes back to firing the clicks two at a time...


Hm, it sounds like you include something twice... or probably you have 2 Stimulus controllers created? Better to import it correctly, or even better to stop using jQuery :)



Hi @Victor thanks for the suggestion but now that i have only one controller it keeps loading me the connect() event twice ... why ? I loaded the app.js file into the main view :(

I solved with / stimulusFetch: 'lazy' / in controller code...

import { Controller } from '@hotwired/stimulus';

/* stimulusFetch: 'lazy' */
export default class extends Controller {....

Hey @pasquale_pellicani ,

That stimulusFetch: 'lazy' is a good idea, all my Stimulus controllers have that. But even without that the connect should not be called more than once, unless you assigned that controller to more than 1 element.



...strange because I assigned it only to a one unique div present in the view index. I don't know what could be the cause.


in console I've this output:

application #starting
conteggia #initialize //conteggia is my stimulus controller
conteggia #connect
application #start
conteggia #disconnect
conteggia #connect

...... any problem in my code ?

Eddie Avatar

Hello, I have a question. What is the right way to manipulate a global variable? Let's say I have two "click me" elements and 1 counter variable. I want If I click on element 1 or element 2, both elements counters to increment by 1. In this scenario, where should I put the global variable?


Hey Eddie

As a recommendation, try to avoid using global variables, they just add complexity to your code and there are alternatives. If you're doing it using Stimulus, it comes to my mind that you can emit an event whenever an element is clicked, so you can listen to that event and increase the counter. Does it makes sense to you?
Btw, if you don't know how to work with events in Stimulus controllers, this chapter will be of great help https://symfonycasts.com/sc...


Alberto rafael P. Avatar
Alberto rafael P. Avatar Alberto rafael P. | posted 2 years ago

hey guys, we are waiting the tutos....


Hey Alberto,

Thank you for your interest in this tutorial! The video is already published, pleasant viewing ;)

P.S. More will come soon, usually we're trying to release at least one video a day, but sometimes things go busy, thank you for your patience!


Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works perfectly with Stimulus 3!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "", //
        "doctrine/annotations": "^1.0", // 1.11.1
        "doctrine/doctrine-bundle": "^2.2", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.8", // 2.8.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.6", // v5.6.1
        "symfony/asset": "5.2.*", // v5.2.3
        "symfony/console": "5.2.*", // v5.2.3
        "symfony/dotenv": "5.2.*", // v5.2.3
        "symfony/flex": "^1.3.1", // v1.18.5
        "symfony/form": "5.2.*", // v5.2.3
        "symfony/framework-bundle": "5.2.*", // v5.2.3
        "symfony/property-access": "5.2.*", // v5.2.3
        "symfony/property-info": "5.2.*", // v5.2.3
        "symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
        "symfony/security-bundle": "5.2.*", // v5.2.3
        "symfony/serializer": "5.2.*", // v5.2.3
        "symfony/twig-bundle": "5.2.*", // v5.2.3
        "symfony/ux-chartjs": "^1.1", // v1.2.0
        "symfony/validator": "5.2.*", // v5.2.3
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.1
        "symfony/yaml": "5.2.*", // v5.2.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.2.1
        "twig/intl-extra": "^3.2", // v3.2.1
        "twig/twig": "^2.12|^3.0" // v3.2.1
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.2.3
        "symfony/maker-bundle": "^1.27", // v1.30.0
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/stopwatch": "^5.2", // v5.2.3
        "symfony/var-dumper": "^5.2", // v5.2.3
        "symfony/web-profiler-bundle": "^5.2" // v5.2.3

What JavaScript libraries does this tutorial use?

// package.json
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.13
        "@popperjs/core": "^2.9.1", // 2.9.1
        "@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
        "core-js": "^3.0.0", // 3.8.3
        "jquery": "^3.6.0", // 3.6.0
        "react": "^17.0.1", // 17.0.1
        "react-dom": "^17.0.1", // 17.0.1
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "stimulus": "^2.0.0", // 2.0.0
        "stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
        "stimulus-use": "^0.24.0-1", // 0.24.0-1
        "sweetalert2": "^10.13.0", // 10.14.0
        "webpack-bundle-analyzer": "^4.4.0", // 4.4.0
        "webpack-notifier": "^1.6.0" // 1.13.0