Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Stimulus Controllers

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.

Ok: time for Stimulus! First stimulus is... a JavaScript library! If you start a new project and install Encore fresh, like we did, then thanks to the recipe, stimulus is already inside of your package.json file.

We also have an @symfony/stimulus-bridge library. This adds superpowers on top of stimulus. I'll tell you exactly what those are as we go along.

If you don't have these packages, install them with:

yarn add stimulus @symfony/stimulus-bridge --dev

Let me close a few tabs and open up our assets/app.js file. This imports a bootstrap.js file that the recipe also gave us. And you will need this: if you don't have it, you can get it from the code block on this page or the stimulus-bridge docs.

The one line in this file starts stimulus by telling it to look for stimulus controllers in this controllers/ directory, which is literally assets/controllers/. Symfony gives us one dummy controller to start.

And... yea, the entire point of this file is to say:

Hey Stimulus! I have some controllers in this controllers/ directory.

We'll learn what all this weird lazy-controller stuff is a bit later, it's not important yet.

Creating our First Controller

The best way to see how Stimulus works is just to try it. Delete the hello_controller.js file: let's create our first controller from scratch. Call it counter_controller.js. To learn the Stimulus basics, we're going to create an element that tracks how many times we click it.

Oh and this naming convention is important. All of our controller files will be something_controller.js. And you'll see why in a minute.

Tip

If you start a new project today, you'll be using Stimulus 3. You can check by looking in your package.json file for @hotwired/stimulus. The only thing you need to change for Stimulus 3 is the import statement. Use:

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

Inside the file, these always start the same way: import {} from stimulus and what we want to import is Controller. Then, export default a class that extends Controller. Inside the class, add a method called connect() with this.element.innerHTML = a message:

You have clicked zero times 😢

import { Controller } from 'stimulus';
export default class extends Controller {
connect() {
this.element.innerHTML = 'You have clicked me 0 times ?';
}
}

Adding the data-controller Element

That's all we need for now. To see what this does, we need to add a matching element to one of our pages. Open templates/product/index.html.twig.

This is the template for the homepage. Down a bit, how about at the top of the main content, add <div data-controller="counter"></div>.

... lines 1 - 2
{% block body %}
... lines 4 - 9
<div class="col-xs-12 col-9">
... line 11
<div data-controller="counter"></div>
... lines 13 - 82
</div>
... lines 84 - 85
{% endblock %}

We can put something in the div, but we don't need to for our example.

The data-controller connects this element to the controller class that we just created. Because we named the file counter_controller.js, the controller's name internally in Stimulus is counter: it strips off the _controller.js part.

So we connected the element to that controller with data-controller="counter". Thanks to that, this should work!

As a reminder, I still have a yarn watch going over in my terminal, so it's been happily rebuilding each time we make a change.

Spin over to your browser and click to get to the homepage. Yes! It's alive! The empty div has our message! Inspect that element. Yep! We can see data-controller and the text inside.

Elements & Controller Objects

This is the magic of stimulus. As soon as it sees an element with data-controller="counter", it instantiates an instance of our "counter" controller and calls the connect() method... named that way because Stimulus is "connecting" this object to a specific element on the page. And, as you can see, the element we just got connected to is available via this.element.

That allowed us to easily set its inner HTML.

Multiple Controller Instances on the Page

The beauty is that we can have as many of these elements on the page at the same time as we want. I'll copy the div and, up in the aside, paste.

... lines 1 - 2
{% block body %}
... lines 4 - 5
<aside class="col-xs-12 col-3">
<div data-controller="counter"></div>
... line 8
</aside>
... lines 11 - 86
{% endblock %}

Go refresh now. Two messages! And the really cool part is that each element is connected to a separate instance of our controller class. This means we get to write clean JavaScript code in a class and store information specific to its element as properties on that object. We'll do that very soon.

So... with Stimulus, we get objects that are bound to individual HTML elements and are instantiated automatically when those elements appear on the page. I would use Stimulus just for that! It's the simple, object-oriented JavaScript approach I've always tried to create on my own.

But wait there's more! Next: let's add a count property and a click listener to show how each element is connected to a separate controller object. Then I'll show you the feature of Stimulus that absolutely knocked me over when I first saw it.

Leave a comment!

31
Login or Register to join the conversation
Thomas-34 Avatar
Thomas-34 Avatar Thomas-34 | posted 1 month ago

Hi,
Would someone tell me why stimulus is in the dev dependencies ?
yarn add stimulus @symfony/stimulus-bridge --dev

Thanks

1 Reply

Hey Thomas,

Good question :) Well, the simple answer is because technically it's really a dev dependency :) You do not use any of your JS dependencies directly on the front end because everything is going through the Webpack Encore, i.e. the Webpack Encore compiles the final files into the public/build/ directory. And as soon as the files are compiled - you can even completely drop the node_modules/ directory, it will still work. From this point of you all your JS dependencies are just dev deps. But it's not that much important, you can put them in the "dependencies" section instead if you want, but that's not the best practice we recommend. That's it :)

Cheers!

Cheers!

Reply
Thomas-34 Avatar

Thank you very much for your answer Victor.
I think I am doing a confusion about dev dependencies and thiere good practices.
For example the good practice for jquery is also: npm install jquery --save-dev
But in reality when I go in the production mode I need jquery.
In my mind the good practice would have been to use, a testing module for example, in dev dependencies.
So why stimulus or jquery use the flag --save-dev ?
Do you uderstand what I mean ?

Reply

Hey Thomas,

Unfortunately, you do not use the jQuery directly in prod :) What you're actually using in prod is a compiled version of jQuery, i.e. Webpack Encore compiled it, added some internal code around the jQuery code, and put it into the public/build/ directory. So once again, even with jQuery, it's technically a dev dependency for your project because it's needed only for Webpack Encore which in turn will compile the final files for you.

I hope it clarifies thing for you. But as I said, do whatever you like, you can add it to the "dependencies" section and it will still work ;)

Cheers!

Reply
Thomas-34 Avatar

Ok thank you Victor.
So I imagine you mean that if I modify my jquery libraries It will became some production libraries ?
I my mind, developpement libraries, are libraries used in the developpement periode only like eslint. My confusion must come from here.
Anyway, thank you very much for your answers and your time.

Reply
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 1 year ago

Loving Stimulus, and using it in places where I had been using jQuery for just a bit of interactivity.

What's the proper way to load stimulus controllers from a Symfony bundle? I'm assuming that in the bundle I can create an assets/controllers directory, but then how do I configure the app using the bundle to look in that directory for controllers? Perhaps in controllers.json? Or in Webpack?

Thanks.

1 Reply

Hiya Tac Tacelosky !

You have 2 options here - one is the more "automated" way that the actual UX packages work. And the second is a simpler, but more manual process, which is totally fine if you are the one installing the bundles into your app :).

First, in both cases, yea, it's common to have an assets/ or Resources/assets directory in your bundle. And then, a controllers subdirectory is fine - you can organize that part however you want. So that's totally correct. You'll also want to put a package.json file in that directory so that it looks like a real package. The name won't really matter - since it will be an internal JavaScript package. Example: https://github.com/symfony/...

You'll notice in the above example that they have a src/ and dist/ directory, you shouldn't need to worry about that for your own bundles. Just have the source files.

Anyways, if I remember correctly, as soon as you have this assets/package.json or Resources/assets/package.json file, when you install the bundle into a project, Flex will automatically update the project's package.json to point to this. It will look something like:


"@tacman/your-package": "file:/vendor/tacman/your-package/assets"

Where @tacman/your-package is whatever you named your package in the bundle's package.json file.

At this point (after running yarn install --force, you have JavaScript package available! Woo! To expose its controllers to the project's Stimulus, this is where those 2 options come into play:

1) The simpler option

Go into assets/bootstrap.js and import each controller and register with Stimulus. So


import TacController from '@tacman/your-package/controllers/tac-controller';
// ...

app.register('tac', TacController);

2) The more magic option

To mimic what the core UX packages do, add a section like this to your bundle's package.json file https://github.com/symfony/...

NOW you will be able to go into the assets/controllers.json file and point to the new controller. Actually, if you NOW installed the bundle, Flex would update the controllers.json file automatically.

Let me know if you hit any bumps! Really happy you're digging Stimulus - me too!

Cheers!

2 Reply
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | weaverryan | posted 4 months ago

Thanks, Ryan, this is great. Now that Symfony 6.1 has an easy way to configure bundles, do you think you'll do another tutorial, maybe one that includes a stimulus controller?

Reply
Tac-Tacelosky Avatar

I still can't figure this out, I've added it as an issue to symfony/flex, can you take a look if you have some time? Bundles with stimulus controllers -- can't wait!

https://github.com/symfony/...

Reply

Hey Tac Tacelosky!

Sorry for the slow reply! It looks like you figured it out. Tbh, I didn't even realize that they keyword was a requirement - that issue will likely help others.

Cheers!

Reply
Michael B. Avatar

Hello Ryan. I just have a problem. My Bundles package.json contains a working configuration. And this even worked when I wrote the given entries manually into the controllers.json of my test app. Anyway symfony flex don't update automaticly the controllers.json when I install the bundle via composer. Is there something I've been missing? Maybe I have to push a recipe to the Contrib repo? I mean somehow flex has to know where my package.json is...

Reply

Hey Michael,

Yeah, unfortunately, Flex won't update those files. You can create a recipe, but Flex just replace files instead of updating them, so it will mean that your recipe will update users custom configuration. The good part is that with git diff users can check the difference and merge configs. Btw, IIRC you can write a post-install message in the recipe, so it might be an another way telling your users to go there and copy the working config into their own configuration. So, it's up to you what way to choose.

Cheers!

Reply
Michael B. Avatar
Michael B. Avatar Michael B. | victor | posted 1 year ago

Ok, thank you for your reply. Where can I find a working example for that kind of recipe? I searched one for Ux swup but did not found one. And there is something what confuses me - when I install one of the symfony-ux composer packages both the package.json and the controller.json files are updated and NOT just overwritten. But when I install my bundle none of these gets even touched - not overwritten or modified...

Reply

Hey Michael,

See symfony/webpack-encore-bundle recipe, it should have all that stuff like package.json, assets/controllers.json, etc. Here's the link to the latest version: https://github.com/symfony/... - but I suppose you're interested in manifest.json file where everything is configured. Also, it has an example of that post-install message I was talking about, see post-install.txt there.

I hope this helps!

Cheers!

Reply
Michael B. Avatar
Michael B. Avatar Michael B. | victor | posted 1 year ago

Thank you Victor. I found the mistake by looking at ux-collection-bundle.
The problem was symfony-flex don't see the package.json
when its inside src/Resources/assets folder. The Resources folder has to be inside the project root to make this work.

Reply

Hey Michael,

Ah, yes, it makes sense, good catch! Thanks for sharing the problem with others

Cheers!

Reply
Michael B. Avatar

My question is now - Do I have to name my package inside the package.json after the symfony bundle? I guess yes. I tried to rename it (make it shorter) but it was not recognized anymore. Am I right, that renaming is not possible ?

Reply
Michael B. Avatar

Ok, I found this here the moment I wanted to ask the same :D.
So I did it. What's worth mentioning is you will not be able to use the controller in a "normal" way. You have to write for every thing (controller, target, value ...) for example


data-controller="tacman--your-package--tac"
data-tacman--your-package--tac-target="target"
Reply

Hey Michael Brauner!

It looks like you already answered your own question - nice! I think it would be nice (we actually need it for Symfony Live Components - https://github.com/symfony/... to be able to add an "name" key in your package's package.json file that would override the controller name to make it short. The reason this doesn't exist is... mostly due to potential collisions - i.e. 2 packages both naming their controllers "foo". Unfortunately, we can't simply allow the user to override the name in their controllers.json file because some packages include PHP helpers that output the controller's name (e.g. for data-controller or targets). So if the user can change it dynamically... those helpers would output the wrong controller name.

Anyways, I hope you found a solution that makes you happy - it's on my todo list to at least make it possible to shorten the name in the package.json file.

Cheers!

Reply

Oh, and one more thing. For the long, ugly names, using the helpers - like {{ stimulus_controller('tacman/your-package/tag') }} or {{ stimulus_target() }} can make them less ugly - the "/" are normalized into the "--".

1 Reply
Michael B. Avatar

Yes. I found the {{ stimulus_controller('tacman/your-package/tag') }} function and thats how I found out the right spelling for the data-controller ;). I don't want to use this function for a bundle but it's good to know about it for an app.

Thank you! Cheers!

Reply
Michael B. Avatar

Yes - I already thought that it is going in that direction. Thank you anyway for your answer.

Reply
Tomáš S. Avatar
Tomáš S. Avatar Tomáš S. | posted 5 months ago

Hello,

I want to ask how to pass .env variable to stimulus controller.

I am using libphonenumber-js and I am using this method
this.phoneNumber = parsePhoneNumber(this.inputTarget.value, 'CZ');.

I want to change 'CZ' with a variable from Symfony's .env...

Thank for reply

Reply

Hey Tomáš Skočdopole

I believe the way to go is to inject that value as a Stimulus controller value. You can get the env var from a Symfony Controller and pass it through your JS
Here are the docs for using Stimulus values https://stimulus.hotwired.d...

Cheers!

Reply
MAli Avatar

Hi,
yarn watch not working.
It worked with out any problem until yesterday but when I have made some changes and started yran watch it is not working/syncing.
- yarn watch shows no error message and says `webpack compiled successfully`
- yarn build works filne and gets me the latest changes

Any help please!

Reply
MAli Avatar

It's working now but taking long time to copy/sync new changes !!

Reply

Hey Ali

What's your local dev environment setup? I used to run under Windows WSL 1 and it's very slow at reading files. You may be facing something similar

Reply
MAli Avatar

Hi @DDiego Aguiar
Thank you for your reply!
I'm using Vagrant on mac.

Reply

Uhmm, I'm not familiar with that environment but you may want to upgrade your tools, for example, upgrade Nodejs to the latest version, also Yarn's version, it may help
Besides that, you could search for tricks to optimize your setup performance

Cheers!

Reply
MAli Avatar

Yes I'll try. Thank you very much :)

Reply
megdev Avatar

hello,
don't understand why ! but controller don't work for me ... ?
i follow exactly the same as the course ?

Reply
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": ">=7.4.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
        "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
    }
}