Rails Vuejs Webpacker: Passing instance variable data

Viewed 1456

I am trying to learn how to build an app with Rails 5.1 with Vuejs, as generated via the Webpacker gem.

$ rails new myvueapp --webpack=vue

How do I pass instance variable data back an forth between the Vue components, to get my data in/out of the database?

Let's say for example that I have a User model with Username and Email fields. Is there a simple example of how to pass instance variable, @user, data from the controller to a component?

I believe there are two ways to do this:

(1) In the controller, has the render :json => @user and then use something like the fetch API or Axios to retrieve the data in the .vue component.

-or-

(2) Use the something like the example <%= javascript_pack_tag 'hello_vue' %> together with a <%= content_tag ... %> in a view.

When I do this <%= javascript_pack_tag 'hello_vue' %> example, I can see the "Hello Vue!" from the data in the component "message" variable, but am having trouble finding an example of instance variable data, eg: @user data, being passed in and out of Rails.

Any examples of how to pass @user data would be much appreciated.

Thank you.

4 Answers

I did something that works but I don't really like: create a method in the window scope like:

// app/javascript/packs/some_app.js

window.someAppPack = function (var1, var2) {
  // ...
  new Vue({
    // ...
    data () {
      return {
        var1,
        var2,
      }
    }
    // ...
  })
}

And in the Rails template:

<%= javascript_pack_tag 'somePack' %>

<script>
  someAppPack(#{raw @var1.to_json}, #{raw @var2.to_json})
</script>

This seems dirty to me, and I'm wondering the benefits :

  • Vue apps should be reactive, so the data may be updated sometime, and a call to a JSON resource will be made. Which makes passing original data pointless.
  • if Vue is only used to make some portions of the page reactive, why use all the webpack stuff when we can use "inline" Vue with standard Rails pipeline ?

I think I've had the same issue, however a props helped me.

app/views/**/*.html.erb

<div id="app" data-initial-value="#{raw(html_escape(@var.to_json))}"></div>
<%= javascript_pack_tag 'app' %>

app/javascript/packs/app.ts

import Vue from 'vue'
import App from '../app.vue'

Vue.config.productionTip = false;

document.addEventListener('DOMContentLoaded', () => {
    const app = new Vue({
        render: h => h(App, {
            props:{
                value: JSON.parse(document.querySelector("#app").dataset.initialValue)
            }
        })
    }).$mount()
    document.body.appendChild(app.$el)
})

app/javascript/app.vue

<template>
  <div>
  {{ value.content }}
  </div>
</template>

<script lang="ts">
export default {
  name: "App",
  props: {
    value: Object
  }
};
</script>

I found a way to do this job by using gem gon's power.

  1. in a controller
class MyController < ApplicationController
  def index
    @user = User.first
    gon.user = @user
  end
end
  1. the in layout:
# app/views/layouts/application.html.erb
<%= Gon::Base.render_data %>
  1. in webpacker's entry file
// app/javascript/packs/hello_vue.js
import Vue from 'vue'
import HelloVue from '../views/HelloVue.vue'

window.addEventListener('load', () => {
  const app = new Vue({
    el: document.getElementById('div-you-want-to-mount'),
    components: { HelloVue },
    render: h => h(HelloVue, { props: gon })
  }).$mount()
})
  1. in HelloVue.vue
<template>
  <div id="hello">
    User: {{ user }}
  </div>
</template>

<script>
export default {
  props: ['user']
}
</script>
Related