Basics: Setting up a Meteor + VueJS project

Wow, so in order to migrate my existing project from Blaze to Vue, I’m going to have to rewrite the entire front end – and roll my own forms too – oh man that’s going to take a large amount of work.

The main obstacle is simple schema – I have a lot of validation logic and of course it dictates the shape of the data going into Mongo. Can I use simple schema and all it’s goodness with Vue?

Would switching to React be an easier “upgrade” path?

Yes, and for Apollo too.

IMO, there is no need to wrap Meteor.call(), I don’t see any value I could add to it.

2 Likes

Thanks. :slight_smile: I saw such a wrapper in your Vuex package and was wondering if you’re still using it with Vuex 2. If not, then fine.

You can use Simple Schema but Autoforms is unfortunately tied up to Blaze. A partial workaround is to write a simple wrapper around Autoform so that you’ll still be using Autoform’s Blaze templates inside of your Vue components.

That said, I moved to Astronomy which doesn’t work with Autoforms anyways.

React provides Uniforms library which works with Simple Schema out of the box. We (vue-meteor community) didn’t write such a library for Vue yet.

I made some experiments and I’ve got a half baked forms generator, but it’s working with Astronomy and is too tied up to my code at the moment and doesn’t cover all use cases, as I didn’t write it with open sourcing it in mind.

I don’t mind rewriting my forms, as long as I can perform validation and have some sort of schema that can limit field lengths, and can do default values, etc.

Can someone help me get started with the demo project I posted?

With Vue it would look this way (just an example)

App.vue file:

<template>
  <div id="app">
    <div class="site">
      <my-header />
      <div class="site-content">
        <router-view>
      </div>
      <footer-button />
      <my-footer />
    </div>
    <search-overlay />
    <login-overlay />
    <product-popup />
    <s-alert />
  </div>
</template>

The rest depends if you register your components globally (with Vue.component) or locally (to use with imports and components option)

As for the router part:

import Home from '/imports/ui/pages/Home/Home.vue'

const routes = {
  name: 'Home',
  path: '/home',
  component: Home
  }

Thanks, thats of great help!

To get my data reactive the vue-meteor-tracker package is the way to go right? Does it work well? Still seems a bit experimental but I think I can get it to work.

I also wonder, can I start with replacing the routing and then slowly change all my templates in to vue components and meanwhile the website will still work? Or would I need to change all my templates first. I mean can Blaze and Vue work together while migrating.

It does work well and is nicely documented.

They can theoretically work well together while migrating, but you’d have to rewrite all your routing and provide a common parent Blaze template (initiated with <div v-blaze="">) that gets the data from Vue via Meteor Session and then dynamically loads particular Blaze template of your app with the data passed to it as props.

That is a workaround needed because of no possibility to pass props directly from Vue to Blaze.

So the following structure:

  1. Vue component initialized by router
  2. Blaze template serving as a bridge to pass data from Vue to Blaze
  3. Dynamically loaded Blaze template depending on the route

You can also get rid of step 2 and pass the Session to your actual Blaze Templates but that will be more of rewriting.

It will be a worse case though if you work with nested routes in Flow Router. Vue-router has a great system of nested routes, but I didn’t try to make it work with Flow Router based app yet.

1 Like

Have a question maybe someone here know the answer to.

I have a constants.js file with something like this in:

Meteor.App = {
  TITLE: 'website.co',
  DESCRIPTION: 'this is a cool site',
  NAME: 'website name',
}

Then I also got a file with global helpers global_helpers.js:

Template.registerHelper('constant', function (what) {
  return Meteor.App[what.toUpperCase()];
});

I access my constants in my blaze templates like:

<h1> {{constant 'TITLE'}} </h1>

Can I do something similar for Vue + Meteor to access directly in my components html?

Or I should define my constants also in my vue components meteor object:

<template>
    <div class="ui product large modal">
        <div class="ui two columns stackable grid">
            <h1>{{title}}</h1>
        </div>
    </div>
</template>


<script>

import { Meteor } from 'meteor/meteor'

export default {
    data: function () {
        return {
            title: "",
            description: "",
        }
    },
    meteor: {
        title () {
            return Meteor.App['TITLE'];
        },
        description() {
            return Meteor.App['DESCRIPTION'];
        }
    },
    methods: {
    }
}
</script>

<style lang="less" scoped>

</style>

like this?

Also Im a bit confused if I should meteor npm install vue-router as seen here or do meteor add akryum:vue-router2 as seen here?

Thanks! :slight_smile:

akryum:vue-router2 is more like a helper that uses vue-router (the official Vue routing library) under the hood, so it’s your choice.

1 Like

This could work.

There are other ways:

Vue prototype

This exposes the thing on all instances.

Vue.prototype.AppInfo = Meteor.App
{{ AppInfo['TITLE'] }}

Or:

Vue.prototype.AppInfo: key => Meteor.App[key]
{{ AppInfo('TITLE') }}

Filter

Vue.filter('appInfo', (key) => Meteor.App[key])

In your template:

{{ 'TITLE' appInfo }}

Mixins

// mixins/app-info.js
export default {
  computed: {
    appInfo () {
      return Meteor.App
    }
  }
}

In your component script:

import { appInfo } from 'mixins/app-info'

export default {
  mixins: [appInfo],
}

In your component template:

{{ appInfo['TITLE'] }}

Injection

On your root instance:

new Vue({
  provide: {
    appInfo: Meteor.App,
  },
})

In your component:

export default {
  inject: ['appInfo'],
}
{{ appInfo['TITLE'] }}

Method

export default {
  methods: {
    appInfo (key) {
      return Meteor.App[key]
    }
  }
}
{{ appInfo('TITLE') }}

You can also use a mixin or injection (or extends: Component) to give access to this method on all the needed components.


Using Mixins or Injection is more verbose, but you gain the maintenance advantage of being more explicit (someone reading that code knows where appInfo comes from).

2 Likes

Thats really great, thanks! gave me much more to work with.

I got almost all blaze templates refactored into Vue now and it works great. However, I cant get meteor-vue-tracker to work at the moment for my project. Not receiving any data. I was thinking if someone could find what I do wrong since Im getting blind:

main.js

import '/imports/startup/client';

import Vue from 'vue'
import { Meteor } from 'meteor/meteor'

import VueRouter from 'vue-router'
Vue.use(VueRouter)

import VueMeteorTracker from 'vue-meteor-tracker'
Vue.use(VueMeteorTracker)

Vue.prototype.AppInfo = Meteor.App

import routes from '/imports/startup/client/routes.js'
import App from '/imports/ui/App.vue'

const router = new VueRouter({
    mode: 'history',
    routes,
})

Meteor.startup(() => {
    new Vue({
      render: h => h(App),
      router,
    }).$mount('app');
});

App.vue

<template>
    <div>
        <search-overlay />
        <repick-header />
        <div class="site">
            <div class="site-content">
                <router-view />
            </div>
        </div>
        <h1>hey : {{count}}</h1>
        <repick-footer />
        <product-popup />
    </div>
</template>

<script>
import RepickHeader from './RepickHeader.vue';
import RepickFooter from './RepickFooter.vue';
import SearchOverlay from './SearchOverlay.vue';

export default {
    data() {
        return {
        }
    },
    meteor: {
        data: {
        },
        $subscribe: {
            'products': []
        },
        products() {
            return Products.find({});
        },
    },
    computed: {
      count () {
        console.log("hey");
        console.log(this.$subReady.products);
        console.log(this.products);
        return this.products.length;
      }
    },
    components: {
        RepickHeader,
        RepickFooter,
        SearchOverlay
    },
    metaInfo: {
        title: 'Repick.co',
    },
};

</script>

<style lang="less" scoped>

    .site {
        display: flex;
        min-height: 100vh;
        flex-direction: column;
        padding-top: 70px; //Becuase fixed header
    }

</style>

Printing undefined in the console.
Also getting TypeError: Cannot read property 'products' of undefined at some compressed code that looks like })(key, meteor.subscribe[key]);

Im publishing my collection on the server like:

Meteor.publish("products", function() {
    return Products.find();
});

I read in the instructions that I might need to polyfill Object.assign.

What exactly does this mean? Not sure if that might be the problem here or something simple that I cant see.

Remove the empty data. Your products() should either be inside the data or the data should not exist at all.

Object.assign() is supported since Node 4, but older versions of Meteor are running on Node 0.10. If you run on Meteor 1.4, don’t worry about it.

1 Like

Turns out if I do:

    mounted() {
        this.$subscribe('products');
    },
    meteor: {
      products() {
          return Products.find({})
      }
    },
    computed: {
      count () {
        console.log(this.$subReady.products);
        return this.products.length;
      }
    },

It works!

But if Im instead doing:

    meteor: {
      products() {
          return Products.find({})
      },
      $subscribe: {
        'products': []
      }
    },
    computed: {
      count () {
        console.log(this.$subReady.products);
        return this.products.length;
      }
    },

It does not work and Im getting TypeError: Cannot read property 'products' of undefined at this line

I think this is actually a bug or need to update the documentation. If I changed into subscribe instead of $subscribe it works again:

    meteor: {
      products() {
          return Products.find({})
      },
      subscribe: {
        'products': []
      }
    },
    computed: {
      count () {
        console.log(this.$subReady.products);
        return this.products.length;
      }
    },

It starts working as well.

2 Likes

I’ll take a look at this.

1 Like

i wish someone here created a video tutorial on how to use, setting up, and basic app using Vue with Meteor for us who prefer watching than reading. :slight_smile:

I’m currently trying to setup a Vue application i’ve built, with Meteor and my problem at the moment is nothing is rendering. I’m using vue, vue-router and vuex from NPM. There is only one error in the console, which states:

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build. (found in <Root>)

Other than that everything seems to be working, but I’m staring at a white screen. I’m sure its something small i’m missing. Any help would be great. Thanks

Edit: I figured it out. Was missing render: h => h(App)
Now its rendering all black on my phone :frowning:

when i install some plugin, hase the same +1