Back to Blog Home
← all posts

A new Vue for NativeScript

June 6, 2017 — by Jen Looper

logo

Do you ever get a hankering to try software in its early days? There’s a certain charm and level of excitement in forking a newly-built repo and trying it, in all its shiny glory, on your mobile emulator. This is the feeling I got when taking a look at the project built by the amazing Igor Randjelovic, which is an integration of Vue.js for NativeScript. Want to write your NativeScript app using Vue.js instead of Angular or plain vanilla NativeScript? Look no further.

Interested in learning more about NativeScript and Vue? Watch the NativeScript-Vue 2.0 webinar on YouTube


Why Vue.js?

The standard way to develop a NativeScript app, currently, is either writing ‘vanilla’ NativeScript or using Angular, which also offers the opportunity to share code between the web and mobile. Many voices, however, have been asking for a Vue.js integration with NativeScript: it is the second most popular ‘idea’ request in our Aha ideas list, after support for Windows Universal, which is marked as ‘planned’. We are lucky to have a wonderful community member who has taken the initiative to spin up Vue for NativeScript. This repo, just started in mid-April already has over 160 stars. If you’re interested in helping this initiative, PRs are gladly accepted and there’s plenty to talk about on the #vue channel of NativeScript Community Slack.

For those who are not as familiar with the many JavaScript frameworks that seem to crop up on a weekly basis nowadays, Vue.js is a lightweight, ‘progressive framework’ that is designed for building user interfaces. Unlike larger, more opinionated frameworks like Angular, Vue is designed to incrementally adoptible - use it just for the view layer, or build an entire Single-Page Application by leveraging various supporting libraries. An excellent comparison between Vue and other frameworks, including React and Angular, shows that the creator(s) of Vue have been inspired by the best of both of these frameworks: Vue uses a virtual dom, like React, and its syntax resembles Angular’s.

Benchmarks demonstrate that, on the web, Vue outperforms both Angular and React slightly and is lighter weight than Angular: 

“Recent versions of Angular, with AOT compilation and tree-shaking, have been able to get its size down considerably. However, a full-featured Vue 2 project with Vuex + vue-router included (~30kb gzipped) is still significantly lighter than an out-of-the-box, AOT-compiled application generated by angular-cli (~130kb gzipped).” source
You can listen to Evan You, Vue’s creator on the Full-Stack Podcast as he explains the thinking behind Vue’s architecture.



While this integration is in its early days, you can try it out here. Fork the repo and follow the instructions here on the development setup. 

Note, currently the project runs more consistently on Android emulators, rather than iOS

When you run the project from the vue-sample folder, you’ll see a sample Reddit reader like this:

vuesample-reddit

Change the starting template by editing the package.json in the vue-sample/app folder to reference the various templates in that folder: 


{
  "main": "app-with-list-view.js",
  "name": "nativescript-template-tutorial",
  "version": "1.0.1"
}

Change the first line to “main”: “app.js” and you’ll get an entirely new spin on the standard NativeScript tap challenge (I won’t ruin the surprise):

tap-challenge-vue


Why NativeScript with Vue?

To answer this question, I asked Igor what inspired him to create it.  He told me that had heard about NativeScript a few times but had never really looked into it before, thinking it was some sort of a scripting language that compiles down to JavaScript. It was when he started learning about progressive web apps, a new hot topic right now, and decided to try his hand at making one. Frustrated at the slow performance of the scheduling app provided by his university, he decided to build a minimalistic web app that would let him check his class schedule quickly and easily, even offline. He had it working in about ten hours and was happy with the result but it still wasn't feeling quite right. In particular, the startup time felt sluggish: 

time-table

Since he loves tinkering with code, he decided to try to make a real native app. Igor had heard about Weex which is a native app platform built on top of Vue, but quickly ran up against Weex’s sub-par documentation. In fact he was left with no clue how to use Weex, and in addition the project has been inactive for a while. Some more research led him to this Github issue which piqued his interest. He watched a couple of introduction videos which all led to the conclusion that NativeScript is amazingly simple to get started with. After about 5 minutes he had a simple vanilla NativeScript application running on his phone. 

Excited by this progress, Igor decided to try a couple of different Hello World examples, including the Angular one. “I'm not a huge fan of Angular, and I'm totally biased towards Vue... Anyway I got excited about Vue in NativeScript, the two seemed like a match made in heaven, and I was surprised that nobody has ever tried to integrate the two.” Looking at the Github issue, it seemed obvious that many people would love to see it happen. Soon he started exploring the internals of Vue and came to a conclusion that integrating it with NativeScript shouldn't be too hard, since Vue doesn't depend on the browser. At first he had no idea where to begin, but tinkering with small bits of code, he somehow managed to get a native label to render through Vue. “It was so exciting, I even tweeted about it and it was pretty amazing to see the interest.”


“I tend to get over excited about things, and then have a hard time sleeping due to thoughts and ideas flying through my mind. This was no different; I believe I have slept around 8 hours total in the initial 3-4 days. More and more things started working, with fairly minimal effort which made me even more excited.” Igor credits Evan You (the creator of Vue.js) for its excellent and extensible architecture, and also Weex, which gave a roadmap of sorts for mobile app development with Vue. By copying and pasting bits and pieces together, he slowly got a better idea of how things should be, and started organizing the code. “I had to dig deep into the internals of Vue that I have never seen before. Using a framework is very different than understanding the internals of it. Honestly it still is a bit of a mystery but I have gained useful insight into the way it works.”

The project is progressing more slowly right now due to Igor’s exam schedule, but rest assured that once exams are over, he’ll put a lot of work into it again! “I would also love to get others on board, who are just excited as I am.”


Show me the code!

A Vue/NativeScript template can be broken down into several parts. A simple example looks like this: 


const Vue = require('nativescript-vue/dist/index')
new Vue({
    data: {
        test: 'testing',
        test2: 50,
        test3: 30
    },
    template: `
        
            <stack-layout>
                <button @tap="onTap">whatever
                <text-field v-model="test"></text-field>
                <slider v-model.number="test2">
                <slider v-model.number="test3" minvalue="-10" maxvalue="50" style="margin-top: 15;">
                
                
                
                
            </stack-layout>
        
    `,
    methods: {
        onTap() {
            this.test = 'changed'
            this.test2 = 42
        }
    }
}).$start()

Vue is first included into the app project on the first line, where the Vue library is imported via require. The app’s pages are then represented as new Vue({}) components.

Within a component, we normally include data, a template, and methods. Data could include an array of properties that must be set before they can be leveraged in the template. In the Reddit example above, the default subreddit for use in the popup window is set:

data: {
  subreddit: '/r/funny'
},

Templates include the markup you need to build up your NativeScript presentation tier. The markup is quite similar to Angular, with a few differences: events are designed with @: <button @tap="onTap">whatever</button> but bindings contain the familiar curly brackets.

Methods contain the standard syntax for a page’s method: 


methods: {
        onTap() {
            this.test = 'changed'
            this.test2 = 42
        }
    }


Within the single .js file, you can build out several pages. By using simple REST API calls on my Firebase instance, I was able to build out most of my QuickNoms recipe app, all redone in Vue: 


const Vue = require('nativescript-vue/dist/index')
const VueRouter = require('vue-router')
Vue.use(VueRouter)
global.process = {env: {}} // hack! a build process should replace process.env's with static strings.
const http = require("http")
const SegmentedBarItem = require('tns-core-modules/ui/segmented-bar').SegmentedBarItem
Vue.prototype.$http = http
const Recipe = {
    data: function(){
        return {
            recipe: []
        }
    },
    created() {
        var id = this.$route.params.id
        this.fetchOneRecipe(id)
        var firstItem = new SegmentedBarItem();
        var secondItem = new SegmentedBarItem();
        var thirdItem = new SegmentedBarItem();
        firstItem.title = "Ingredients";
        secondItem.title = "Tools";
        thirdItem.title = "Procedure";
        this.recipeSteps = [ firstItem, secondItem, thirdItem ];    
    },
    template: `
    <stack-layout>
    <img :src="recipe.image" height="25%" stretch="aspectFill">            
        <stack-layout class="innerCard">
               
            <segmented-bar class="bar" bordercolor="#8AC215" :items="recipeSteps" selectedbackgroundcolor="#8AC215" #sb="" selectedindex="0" @selectedindexchange="changeTab(sb)"></segmented-bar>
            <stack-layout verticalalignment="top">
                <scroll-view height="75%" verticalalignment="top">
                    
                </scroll-view>
                <stack-layout height="25%" verticalalignment="center">
                </stack-layout>
            </stack-layout>
        </stack-layout>
    </stack-layout>
    `,
    methods: {
        fetchOneRecipe(id){
            this.$http.getJSON(`https://quicknoms-91e39.firebaseio.com/Recipes.json?orderBy="$key"&equalTo="${id}"`).then((res) => {
                this.recipe = res;
                for( var key in res) {
                    this.recipe.name = res[key].Name
                    this.recipe.image = res[key].Image
                    this.recipe.notes = res[key].Notes
                    this.recipe.procedure = res[key].Method
                }
                console.log(JSON.stringify(this.recipe))                
            }).catch((err) => {
                console.log('err..' + err)
            })
        },
        changeTab(id){
         switch (id) {
            case 0:
                this.procedure = this.ingredients; 
                break;
            case 1:
                this.procedure = this.tools; 
                break;
            case 2:
                this.procedure = this.method; 
                break;            
         }
    }
    }
}
const Recipes = {
    data: function(){
        return {
            recipes: []
        }
    },
    created() {
        this.fetchRecipes()
    },
    template: `
            <scroll-view class="green">
                <wrap-layout horizontalalignment="center">      
                    <stack-layout style="margin-left: 10" class="card" width="45%" v-for="(recipe, i) in recipes" key="i">
                        <stack-layout horizontalalignment="center" @tap="$router.push({ name:'recipe',params: {id: recipe.id} })">
                            <img :src="recipe.image">          
                            
                        </stack-layout>
                    </stack-layout>           
                </wrap-layout>
            </scroll-view>
    `
    ,
    methods: {
        
        fetchRecipes() {
            this.$http.getJSON(`https://quicknoms-91e39.firebaseio.com/Recipes.json`).then((res) => {
                for( var key in res) {
                    this.recipes.unshift({id : key, name: res[key].Name, image: res[key].Image})                    
                } 
            }).catch((err) => {
                console.log('err..' + err)
            })
        }
    }
}
const router = new VueRouter({
    routes: [
        {path: '/recipes', component: Recipes},
        {path: '/recipe/:id', name: 'recipe', component: Recipe},
        {path: '*', redirect: '/recipes'}
    ]
})
router.replace('/recipes')
new Vue({
    router,
    template: `
        
            <stack-layout>
                <router-view></router-view>
            </stack-layout>
        
    `   
}).$start()

Of course, when you move past two pages, having all your components in one file starts to get cumbersome. Support for single-file components, imported into your page as a separate .vue file, are planned in the  future.

The result made me really happy!

vuenoms

While this project is in its very preliminary stages, we can already get a feel for how using Vue.js to build NativeScript apps might work. If you’re interested in helping, please join us on Slack, check out the project on Github and join the fun!