I'm new to vuejs and just started playing with it. While trying different tutorials I'm stuck at this code sample which is not working. Can anyone please explain why the second-component is not being rendered?
Vue.component('first-component', {
template: `<div>This is first component: {{val}}!</div>`,
data: function () {
return {
val: "FIRST <second-component></second-component>"
}
},
})
Vue.component('second-component', {
template: `<p>This is second component: {{val}}</p>`,
data: function () {
return {
val: 'SECOND'
}
},
})
new Vue({
el: '#example'
})
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="example">
<first-component></first-component>
</div>
The problem is you have put the <second-component> in the val variable, and trying to render it with Mustache syntax However as stated in docs The double mustaches interprets the data as plain text, not HTML
You can use v-html to insert the component as plain HTML but again it is stated: the contents are inserted as plain HTML - they will not be compiled as Vue templates.
The only way you can render second-component is by putting this in the template:
template: `<div>This is first component: {{val}}!<second-component></second-component></div>`,
That's because you are returning it as "data" in "val". So that's not evaluated to a component. If you put it in the template, it works.
Vue.component('first-component', {
template: `<div>This is first component: {{val}}! <second-component></second-component></div>`,
data: function () {
return {
val: "FIRST"
}
},
})
Vue.component('second-component', {
template: `<p>This is second component: {{val}}</p>`,
data: function () {
return {
val: 'SECOND'
}
},
})
new Vue({
el: '#example'
})
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="example">
<first-component></first-component>
</div>
Related
Question
Given I am in component context, how do I get the component object? By component object I mean the object you get when you import Component from 'Component.vue'.
Current progress
Here's one possibility I found.
const component = {
methods: {
getComponent: () => this,
displayItem () {
console.log('this.getComponent()', this.getComponent()) // undefined
console.log('this', this) // component instance
console.log('component', component) // what I need (component object)
},
},
}
export default component
The downside though is that it kills IDE support.
I also checked this manually.
Ideal solution
The approximation to syntax I'd like to see: this.$component.
What's the point?
Instantiate components via :is="component".
Perform instance of check.
The closer you got is vm.$options:
Vue.component('some-comp', {
template: '<p>{{ message }}</p>',
props: {name: String},
data() {
return {
message: 'Open the console!'
}
},
computed: {
example() {
return this.message.toUpperCase();
}
},
watch: {
message() {
console.log('watcher triggered');
}
},
mounted() {
console.log(this.$options);
console.dir(this.$options.__proto__);
}
});
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<some-comp :name="'Alice'"></some-comp>
</div>
But it seems what you want is constructor:
Vue.component('aaa-aaa', {
template: '<div>AAA component</div>'
})
Vue.component('bbb-bbb', {
template: '<div>BBB component</div>'
})
new Vue({
el: '#app',
mounted() {
console.log(this.$refs.a1);
console.log(this.$refs.a1.constructor);
console.log(this.$refs.b1);
console.log(this.$refs.b1.constructor);
console.log('a1 a2', this.$refs.a1.constructor === this.$refs.a2.constructor)
console.log('a1 b1', this.$refs.a1.constructor === this.$refs.b1.constructor)
console.log('b1 b2', this.$refs.b1.constructor === this.$refs.b2.constructor)
console.log('b2 a2', this.$refs.b2.constructor === this.$refs.a2.constructor)
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<aaa-aaa ref="a1"></aaa-aaa>
<aaa-aaa ref="a2"></aaa-aaa>
<bbb-bbb ref="b1"></bbb-bbb>
<bbb-bbb ref="b2"></bbb-bbb>
</div>
I'm new using Vue.js and I had a difficulty creating a Button component.
How can I program this component to conditional rendering? In other words, maybe it should be rendering as a router-link maybe as a button? Like that:
<Button type="button" #click="alert('hi!')">It's a button.</Button>
// -> Should return as a <button>.
<Button :to="{ name: 'SomeRoute' }">It's a link.</Button>
// -> Should return as a <router-link>.
You can toggle the tag inside render() or just use <component>.
According to the official specification for Dynamic Components:
You can use the same mount point and dynamically switch between multiple components using the reserved <component> element and dynamically bind to it's is attribute.
Here's an example for your case:
ButtonControl.vue
<template>
<component :is="type" :to="to">
{{ value }}
</component>
</template>
<script>
export default {
computed: {
type () {
if (this.to) {
return 'router-link'
}
return 'button'
}
},
props: {
to: {
required: false
},
value: {
type: String
}
}
}
</script>
Now you can easily use it for a button:
<button-control value="Something"></button-control>
Or a router-link:
<button-control to="/" value="Something"></button-control>
This is an excellent behavior to keep in mind when it's necessary to create elements that may have links or not, such as buttons or cards.
You can create a custom component which can dynamically render as a different tag using the v-if, v-else-if and v-else directives. As long as Vue can tell that the custom component will have a single root element after it has been rendered, it won't complain.
But first off, you shouldn't name a custom component using the name of "built-in or reserved HTML elements", as the Vue warning you'll get will tell you.
It doesn't make sense to me why you want a single component to conditionally render as a <button> or a <router-link> (which itself renders to an <a> element by default). But if you really want to do that, here's an example:
Vue.use(VueRouter);
const router = new VueRouter({
routes: [ { path: '/' } ]
})
Vue.component('linkOrButton', {
template: `
<router-link v-if="type === 'link'" :to="to">I'm a router-link</router-link>
<button v-else-if="type ==='button'">I'm a button</button>
<div v-else>I'm a just a div</div>
`,
props: ['type', 'to']
})
new Vue({ el: '#app', router })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
<link-or-button type="link" to="/"></link-or-button>
<link-or-button type="button"></link-or-button>
<link-or-button></link-or-button>
</div>
If you're just trying to render a <router-link> as a <button> instead of an <a>, then you can specify that via the tag prop on the <router-link> itself:
Vue.use(VueRouter);
const router = new VueRouter({
routes: [ { path: '/' } ]
})
new Vue({ el: '#app', router })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
<router-link to="/">I'm an a</router-link>
<router-link to="/" tag="button">I'm a button</router-link>
</div>
You can achieve that through render functions.
render: function (h) {
if(this.to){ // i am not sure if presence of to props is your condition
return h(routerLink, { props: { to: this.to } },this.$slots.default)
}
return h('a', this.$slots.default)
}
That should help you start into the right direction
I don't think you'd be able to render a <router-link> or <button> conditionally without having a parent element.
What you can do is decide what to do on click as well as style your element based on the props passed.
template: `<a :class="{btn: !isLink, link: isLink}" #click="handleClick"><slot>Default content</slot></a>`,
props: ['to'],
computed: {
isLink () { return !!this.to }
},
methods: {
handleClick () {
if (this.isLink) {
this.$router.push(this.to)
}
this.$emit('click') // edited this to always emit
}
}
I would follow the advice by #Phil and use v-if but if you'd rather use one component, you can programmatically navigate in your click method.
Your code can look something like this:
<template>
<Button type="button" #click="handleLink">It's a button.</Button>
</template>
<script>
export default {
name: 'my-button',
props: {
routerLink: {
type: Boolean,
default: false
}
},
methods: {
handleLink () {
if (this.routerLink) {
this.$router.push({ name: 'SomeRoute' })
} else {
alert("hi!")
}
}
}
}
</script>
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:
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.
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...
Everything is fine in my main.js
import User from './components/User.vue'
new Vue({
el: '#user',
render: h => h(User),
data() {
return {
groups: []
}
},
mounted() {
this.getGroups();
},
methods: {
getGroups: function () {
axios.get('http://vagrant.dev/groups')
.then(response => this.groups = response.data)
.catch(error => console.log(error));
}
}
});
User.vue
<template>
<div id="user">
{{groups}} <!-- must be printed but it doesn't seem -->
</div>
</template>
<script>
export default {
name: 'user',
props: ['groups']
}
</script>
and index.html
<div id="user">
<user :groups="groups"></user>
</div>
Where is the mistake?
I do not get any errors, but I didn't understand why the value was not printed on the screen
Seems like you are putting 2 Vue instances on the same template since in your main file you are defining the template as el: "#user" which is already controlled by User.vue.
In you index.html add a div with id="app" and in main.js set el: "#app"
Your main.js and index.html shouldn't mix template in HTML and render function. So your main.js should be:
```new Vue({
el: '#user',
components: { User },
data() {
return {
groups: []
}
},
...```
You shouldn't directly reassign data value because Vue cannot detect property additions and rerender view. You should do it via Vue.set or this.$set like this:
this.$set(this, 'groups', response.data)
You can read more about how vue reactive in https://v2.vuejs.org/v2/guide/reactivity.html and Vue.set API: https://v2.vuejs.org/v2/api/#Vue-set
How do can you compile a HTML string to template within a component method?
This was possible in Vue 1 like in this jsfiddle
new Vue({
el: '#app',
data: {
sampleElement: '<button v-on="click: test()">Test</button>'
},
methods:{
addNewElement: function(){
var element = $('#app').append(this.sampleElement);
/* compile the new content so that vue can read it */
this.$compile(element.get(0));
},
test: function(){
alert('Test');
}
}
});
How can you do this in Vue 2?
This is no longer possible, however, Vue 2 is data driven, so you shouldn't be trying to affect the DOM manually at all, instead you should bind elements to the underlying data in your view model. With that in mind your example will need to be re-written. Firstly, start by making your element a component:
Vue.component('my-button', {
template: '<button v-on:click="test()">{{text}}</button>',
props: ['text'],
methods: {
test() {
alert('Test');
}
}
});
Then you can create your main view model and add your button component using a v-for:
Markup:
<button v-on:click="addNewElement()">Add Element</button>
<my-button v-for="button in buttons" :text="button"></my-button>
View model:
new Vue({
el: '#app',
methods: {
addNewElement: function() {
this.buttons.push('Test');
}
},
data: {
buttons: []
}
});
In this example we are pushing buttons to an array that will then be displayed on the page, rather than manually appending them to the template.
Here's the JSFiddle: http://jsfiddle.net/10q9je5a/
If you want something more generic, then you can simply create an array of different components and use :is to let Vue know which component to render:
Markup:
<div id="app">
<button v-on:click="addNewElement()">Add Element</button>
<div :is="element.component" v-for="element in elements" v-bind="element.props"></div>
</div>
View Model:
new Vue({
el: '#app',
methods: {
addNewElement: function() {
this.elements.push({component: 'my-button', props: {text: 'Test'}});
}
},
data: {
elements: []
}
});
Here's the JSFiddle for that: http://jsfiddle.net/cxo5eto0/