Skip to main content

28 Vue.js Components in Rails Views

Episode 239 · April 12, 2018

Learn how to deeply integrate your Vue.js components with Ruby on Rails views

Javascript VueJS


Transcripts

What's up guys? This episode we're diving into another Vue.js episode, and this time we're going to be talking about how to mount Vue components from inside your rails application, this is going to be really interesting because it's something I've learned from the Laravel community on how you can actually have Vue as a wrapper around your entire application's template and then you can automatically mount those components from inside your rails views, and this is really interesting and it makes it even easier to pass in data into your Vue components, so we're going to be exploring this, and it's going to be a pretty awesome improvement over stuff we've done previously, so let's dive in.

First off, I'm just going to go set up webpacker again, we're going to run rails webpacker:install, and then right after this we're going to run rails webpacker:install vue and that will go ahead and install everything for us, then we're going to set up our layouts, so we're going to go modify our layouts so we have a wrapper around all of our content, and that content is going to be where Vue gets mounted to Vue, so Vue is going to be kind of this wrapper around all of our content, so let's run rails webpacker:install vue, that's going to get us set up with everything that we need, and while that's running, let's go ahead and make some adjustments to our rails app.

I'm going to go to our head tag and what we're going to do is just grab this JavaScript include tag and we're going to change it to JavaScript pack tag lo load our webpacker pack, then we can go into app/javascript and go into our packs. Now hello_vue.js is where it has some examples for this, and we're going to grab this example down here at the bottom that uses Vue Turbolinks and we're going to pull that into our application.js file, so we're going to actually set that up in here instead. This has your traditional turbolinks load, create a view app, and you have to mention the components here, and that's going to be what it renders and all of that stuff. One of the dependencies that we have is the Vue turbolinks package, so we're going to run yarn add vue-turbolinks and we can run foreman start afterwards to get our rails application loaded. Now, one of the things that I want to point out here is that the way that this works is that we have to create an element with an ID of hello and then we have our component replace that element on the page, this is fine, but we can actually make some really interesting adjustments to this by mounting it to say some sort of single tag on the page, so if we did like a

el: '[data-behaviour="vue"]'

We could go to our layout for application.erb and then, we can put this and wrap our content of our page with that, so we can say

<div class="container" data-behavior="vue"

that would mount our vue application to this wrapper. This will make the vue application mount across all of our views like index show new and edit and those will be all wrapped in the vue application, so that means that any time we type our components in here, like app, we can then actually register those inside of vue and have them automatically mounted on the appropriate tag. If we have view components that we wanted the app tag to render the app component, we can get rid of the data and components down here and this is going to make it so that these automatically are rendered whenever you come across them, so you can put these inside your layout, you can put them inside your views, and it will automatically mount the vue components to them, which is really cool, so if we go back to our browser, we can load this up and you'll see a "hello vue" is rendered there automatically, and that is really awesome, that means that we can go and paste this in a bunch of times, refresh our page and it's going to show up every single time. One of the benefits though is that we previously had to do like a <div data-message><%= @object.errors.full_messages.to_json %> and that would be something that we'd have to manually parse out, pass it in as the data into our vue application that we would mount here manually. That stuff I've talked about in previous episodes, and it works, and it's fine, but we can actually improve that if we do this inside of an application tag that's wrapping all of our content, so what we could do instead is we can actually have

<app :message="<%= "hello".to_json %>"></app>

The magic of this is actually going to be that the colon part and the prop name is going to be automatically parsed as json and then available in the props inside of that component, so here we can go into app, we can get rid of this data function, and we can say props: ["message"] and that's going to give us access to this message that we could then display, so if we refresh our page we get "hello", we could go change this to testing, and refresh our page and we're going to get testing, so it's really cool, so the really nice part about this is that it saves us from having to do the setup and mounting and we don't have to pull the datat attributes off of there and then json parse them and then pass them into the view instance as data, we can have that automatically handled for us by just the normal way that vue.js works, so that is really cool and saves us a lot of time and code from our client side, another cool thing is that if you wanted to do something like 1.upto(10).each do |i|, you can actually have rails render a bunch of these components, and then have dynamic content for each one, so if you wanted to have components for some item on your index page, you can loop through all of your items in rails just like you normally would, and then each item could then be rendered out using vue components like so, so you could have this, where it displays 1 through 10 because we're using a rails loop to create 10 of those items of the app component tag on the page with the necessary json in it, so it's really neat and a great way to integrate rails and vue.js in together. Now, another feature that I want to point out here is that we can actually do a special thing called <app inline-template> here, where our app is going to be ignoring the template inside of our vue single file component, and this is really interesting because that allows us a lot more freedom the way that we might want, so we might want to say, well in this case we want our message to be in an h1 for this version of it, so we'll have our message up here,

<app inline-template :message"<%= "GORAILS".to_json %">

Then, we can refresh the page and we're going to see GORAILS in really big font because it's an h1 tag, and this is overriding the single file components template as well, so if you wanted to, you can actually have rails code insideo of your app templates or your vue component templates, and so you could have your image tags in here that would be harder to pass over into vue js, you could actually use them inside of here if you wanted, you could use form-for if you wanted as well, or form-with, we do any of that type of thing because you were inside of a rails vue, and that's really interesting and something that you might want to do or might take advantage of in certain situations. For the most part I would just render all of these as single file templates and keep that template along with the code to process it and the styles in single file components, but there are opportunities where using an inline template can come in handy, and so I wanted to point that out and talk about that because that is something that you could consider doing in your application. Now before we go, I just wanted to point out how clean this ends up being compared to the old style of doing things that I was previously showing in episodes. Now, this old style is basically the same thing we would still register our components but we were doing them inside of our Vue instance, and then we were specifying a template and saying: Whenever you find this element of id of hello, then we're going to go mount and replace that element with our new template here which was just rendered out this app component, and this works fine, but we can actually simplify that a lot by number one having that element always on every page, so it wraps all of our content which means that we can use vue there if we want, but we don't have to use any vue components, and so this ends up being, we don't have to check every page anymore, that is handy and the json parsing that we previously had to do to load say the message would have had to come inside of this if statement, we need to make sure the element exists, then we need to check it's data sets and grab the message here, and then we would have had to pass that into like the data object, something like this, where we would specify the message is the message, and that is fine, but it was kind of messy to set up, and we can simplify a lot of that stuff into just a really really showrt vue method here where we set up vue and then rails templates can determine what needs to go where on which page, so this allows you to get rid of kind of all this complexity in your JavaScript and then your views in your rails app can go and determine which vue components should be rendered and which data.

It's kind of interesting a different approach that you might want to take using Vue.js in there, it makes for a light coupling between the two, but it allows them to talk to each other really really easily and can save you a lot of trouble as you go and add more interactive stuff into your app, in effect you're almost building out your vue components, sort of like you would with Stimiulus, where you can just drop them in wherever you might need to into your application, and that I think is pretty awesome and saves a lot of trouble, I've been using this in Hatchbox as I've been building it, so I wanted to talk to you guys about this, because I first learned about this in Laravel where they're doing that around their applications and I though that was pretty cool, so I hope you enjoyed this episode, if you want to see more Vue.js stuff let me know in the comments below, and I will talk to you guys in the next episode. Peace v

Transcript written by Miguel

Discussion


Gravatar
Nice video. What about Vue state managment in Rails. When one Vue component mess with state of another Vue component?
Gravatar
Chris had done a really nice tutorials for Vue + statemanagement  tutorial series https://gorails.com/series/vuejs-trello-clone-in-rails


Gravatar
Hey Chris

How would you go about showing images uploaded via ActiveStorage on your vue components?
In ERB the image tag magically renders the image, but I'm not so sure how it would work on the webpacked vue files.

Gravatar
This approach makes for nice DRY looking code. Beware of what happens what browsers parse HTML into the DOM, though. For example, if you write 

<table>
  <row-component></row-component>
  <row-component></row-component>
</table>
the browser may say "hey, the row-component element is not valid inside a table!" during parsing and hoist them out, making it effectively
<row-component></row-component>
<row-component></row-component>
<table></table>
before Vue runs, which is not what you wanted, and there are other cases besides. Arguably it's always a concern when mounting components on an existing document, but becomes more likely when writing in this style.

Gravatar
Great episode Chris! Would it be possible to have something similar to stimulus [1] `definitionsFromContext` helper. A strong [naming] convention would reduce the manual import of vue components. Do you have a smart idea on this?

Gravatar
Second time I tried to install webpack and vue. Keep getting this error and I don't have a clue as to how to fix it. rails installed both webpacker and yarn ok, and I have node on my system. Here's the error:
Webpacker can't find application.js in /home/tom/paul/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
   unless you are using the `webpack -w` or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.
Your manifest contains:
{
}
You never seem to get errors. How is that?

Gravatar
Great episode as always!

One minor warning to anyone looking to build out applications that are potentially larger in scale, or even just Vue heavy with a fair mix of ERB/HAML. 

The way Vue parses applications targeting a wrapper such as the main/section/div, or with inline-templates is such that it walks the entirety of the DOM tree contained within the target to make the replacements to what will be considered for Vue render. Vue2 optimizes for compiled template render (templates built and compiled from your JS components) and has significantly worse performance than Vue1 for this technique (which relied very heavily on being able to just drop into your existing traditional server-side template much like Angular). Not a concern for small/medium projects but the Mobile performance for larger applications can suffer quite a bit.

Like anything, it can be taken with a grain of salt and your own mileage will vary. I know for the company I work for we have a significant amount of Vue built with this technique, and it has been a limiting factor for being able to migrate from Vue1 -> Vue2.

Gravatar
Hi Chris, nice work in this. I'm really excited about VueJS and the potential it has to streamline the Rails frontend dev process.

Some simple handy component examples would be very helpful ie. Form components like file_field with appropriate behaviour or table rows with CRUD actions.
Gravatar
Ok, after some research I found bootstrap-vue, which makes it easy to build out these components and super easy to drop them into a Rails app.
https://bootstrap-vue.js.org/docs/#individual-components-and-directives

Gravatar

Gravatar
Thanks
  
By following your pattern with Turbolink/ [and data-behavior="vue »], it works perfectly on localhost, but yield not load in production (heroku) even if build success, and compiling asset «seems» good.  
  
Have you already got that type of behavior  ?

thanks

Gravatar
Unfortunately, this cool method is not a better way to include components to an existing layout.

First of all, VUE will parse all the page, starting from data-behaviour="vue". So it could be a really hard load.

The second, parsing the page rewrites the DOM. So all your other non VUE stuff and events won't work anymore (like jQuery or Bootstrap plugins, etc.) Or they must be initialized after VUE did its job.

So it could be a trick to make them work together.


Login or create an account to join the conversation.