Possible to use VueJS SFC components with with template in rendered html?

Viewed 3918

I have experience with both single page apps and multi-page apps (classic websites). In the past I have used AngularJS 1.x on each page, it has been useful because all components can live in separate files and be executed as they appear on each page.

I'm now looking at VueJS to replace AngularJS, but not finding it easy to understand how to architect my multi-page app.

As expected I want to use some components on all the pages, and some on only a few pages.

Example:

components

I came across SFC - single file components using ES2015 which looked promising, but my backend is Java which outputs my html from JSPs. It appears that .vue files are precompiled by webpack, but if my templates are only ready when the page is rendered that won't be possible will it?

How would one architect a solution so that each component is modular but utilize either an x-template in the html and somehow attach it to a .vue SFC, or is there some other way to have components in separate files which can be imported using ES2015?

I hope this is making sense, can't seem to figure it out.

2 Answers

One possible approach would be to set the template for the Vue Component inline. So this would be to have a Component File like

Home.vue:

<script>
    export default {
        data() {
            return {
                msg: 'text',
            }
        }
    }
</script>

import it as a global component for Vue (using require, import, etc.)

Vue.component('home', require('./components/Home.vue'));

and in your server generated HTML you'd have to use an inline template, which will have all the flexibility from normal templates

home.jsp:

<home inline-template>
    <h2 v-text="msg"></h2>
</home>

Update

I've added an example on GitHub here

If I understand your question, you have want to make single file components out of HTML.

If this is the case, you should make use of the render() function and regular components.

The render function decides what to use as a template for a component:

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>

<div id="app">
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script type="text/javascript">
    new Vue({
        el: '#app',
        render (createElement) {
            return createElement({
                template: '<div>Hello World</div>'
            })
        },
    })
</script>
</body>
</html>

will render Hello World to the screen.

Now, let's see how this function is reactive:

<script type="text/javascript">
    new Vue({
        el: '#app',
        data: {
            count: 0
        },
        render (createElement) {
            return createElement({
                template: '<div>Hello World ' + this.count + '</div>'
            })
        },
        created () {
            setTimeout(() => {
                this.count++
            }, 2000)
        }
    })
</script>

Here, after 2 seconds, the counter in <div>Hello World ' + this.count + '</div> will increment from 0 to 1.

Now, what if we want to separate the template from the data?

<script type="text/javascript">
    new Vue({
        el: '#app',
        render (createElement) {
            return createElement({
                template: '<div>Hello World {{ count }}</div>',
                data () {
                    return {foo: 'bar'}
                }
            })
        }
    })
</script>

This code will display Hello World bar.

Now, let's see what happen if we try to load our template over http. We'll use the axios library to do so. Let's create a remote.html file to contain our html code:

<div>
    I'm a remote component {{ foo }}
</div>

Now, let's try to load it via Ajax:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.min.js"></script>

<script type="text/javascript">
    new Vue({
        el: '#app',
        data: {
            template: null
        },
        render (createElement) {
            return createElement({
                template: this.template ? this.template : '<div>Hello World {{ foo }}</div>',
                data () {
                    return {foo: 'bar'}
                }
            })
        },
        created () {
            axios({
                url: '/remote.html',
                method: 'get'
            }).then(response => {
                this.template = response.data
            })
        }
    })
</script>

This code will display I'm a remote component {{ foo }} as soon as remote.html has been loaded from the browser.

Note that the object passed to the createElement function is actually a component structure. You can use the same methods on it:

render (createElement) {
    return createElement({
        template: this.template ? this.template : '<div>Hello World {{ foo }}</div>',
        data () {
            return {foo: 'bar'}
        },
        mounted () {
            alert('Hello from mounted')
        }
    })
}

will trigger an alert on the browser.

Anyway, here is a complete example with nested components:

Index.html

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>

<div id="app">
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.min.js"></script>

<script type="text/javascript">

    const headerComponent = {
        data () {
            return {
                template: '<div>Loading...</div>'
            }
        },
        render (createElement) {
            return createElement({
                template: this.template,
                data () {
                    return {
                        search: ''
                    }
                }
            })
        },
        created () {
            axios('/header.html').then(response => {
                this.template = response.data
            })
        }
    }

    new Vue({
        el: '#app',
        data: {
            template: null
        },
        render (createElement) {
            return createElement({
                template: this.template ? this.template : 'Loading...',
                data () {
                    return {foo: 'bar'}
                },
                components: {
                    'my-header': headerComponent
                }
            })
        },
        created () {
            axios({
                url: '/remote.html',
                method: 'get'
            }).then(response => {
                this.template = response.data
            })
        }
    })
</script>
</body>
</html>

header.html

<div>
    <label>Search</label>
    <input v-model="search" name=""> The search is: {{ search }}
</div>

I'm not sure that this is really the best approach and if I'm really responding to the question, but it will at list give you some tips on how Vue handles rendering and components...

Related