vue.js: create component from string - javascript

I have two components named component-1 and component-2, along with this vue.js code:
<template>
<div id="app">
<input class="input" #keyup="update()">
<div class="render"></div>
</div>
</template>
<script>
export default {
methods:{
update () {
document.querySelector(".render").innerHTML=`<component-${
document.querySelector(".input").value
} />`
}
}
}
</script>
Whenever I run the code, the component doesn't render. Is there a way to get this component to render?

This isn't the right way to render components dynamically in Vue - instead you should use :is
<template>
<div id="app">
<input class="input" #keyup="update($event.target.value)">
<component :is="myComponent"></component>
</div>
</template>
<script>
export default {
data() {
return {
myComponent: undefined
}
},
methods:{
update (component) {
this.myComponent = component
}
}
}
</script>
A sandbox demo is here

Related

Cannot receive event from child component vue3.0

I have a button component which is a child component
<template>
<div class="button-container">
<el-button #click="$emit('onClick')" type="primary">{{ text }}</el-button>
</div>
</template>
I want to emit the click event in order for parent component to receive.
Here is the parent component
<script>
import Button from "#/components/Button.vue";
export default {
components: { Button } ,
methods: {
toRegister() { this.$router.push('/register'); }
}
}
</script>
<template>
<Button #on-click="toRegister" text="Register here" textColor="#5fb878" textSize="8px"></Button>
</template>
But i don't receive any thing from here.
I thought the problem is on the event name, but after i changed the name from onClick to clicked, same problem still.
Try to rename your custom component from Button to something else :
const { ref } = Vue
const app = Vue.createApp({
methods: {
toRegister() {
console.log('clicked')
//this.$router.push('/register');
}
}
})
app.component('btn', {
template: `
<div class="button-container">
<button #click="$emit('onClick')" type="primary">text</button>
</div>
`
})
app.mount('#demo')
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<btn #on-click="toRegister" text="Register here" textColor="#5fb878" textSize="8px"></btn>
</div>

shallowMount behaves like mount when lazy loading a component

I'm testing a Nuxt app with vue-test-utils (version 1.0.0-beta.29), and I'm lazy loading the child component. When testing, I want to shallowMount the parent component to stub the childs (I don't want to render the child component because of its dependencies).
Instead of creating child stubs, the whole component tree is rendering in the shallowMount. If I load the components without lazy loading, shallowMount works as expected.
It seems that the problem has already been faced and solved here: https://github.com/vuejs/vue-test-utils/issues/959
I've tried to pass { shouldProxy: true } as a mounting option, or stubbing manually the component by passing { stubs: ['componentname'] }, but the problem is still happening.
parent component:
<template>
<div id="wrapper">
<div
v-for="item in markets"
class="item-wrapper">
<Child :market="item"/>
</div>
</div>
</template>
<script>
export default {
components: {
Child: () => import('./TimelineItem.vue')
},
props: {
markets: {
type: Array,
default: () => []
}
}
}
</script>
child component:
<template>
<div>
Child
</div>
</template>
<script>
export default {
props: {
market: {
type: Object,
default: () => {}
}
}
}
</script>
Snapshot:
<div id="wrapper">
<div class="item-wrapper">
<div>Child</div>
</div>
<div class="item-wrapper">
<div>Child</div>
</div>
</div>
I expected to have an snapshot like this:
<div id="wrapper">
<div class="item-wrapper">
<child-stub></child-stub>
</div>
<div class="item-wrapper">
<child-stub></child-stub>
</div>
</div>
I also had the same issue and I found working solution for me. Try to import components in your test file and add them in components sections..
shallowMount(Component, { components: { Child })
A workaround could be to use a dynamic component.
<div v-if='!Child'>Loading...</div>
<component v-if='Child' :is='Child' />
<script>
export default {
data: {
Child:null,
},
created(){
import('./TimelineItem.vue').then(it=>this.Child=it);
}
}
</script>

raising an event from a child component to a container component in Vue.js?

I have a child Vue component (SearchBox.Vue) that is a simple text box that will fire filterByEvent when the user hits the Enter key. In the container components, I should get the string in the text box and execute a function filterList to filter the customer's list based on the name entered. My question is how to pass the text entered in the text box to the parent component?
SearchBox.vue
<template>
<div class="SearchBox">
<input type="text" id="SearchBox" placeholder="Search"
v-on:keyup.13="$emit('FilterByEvent', $event.target.value)" :value="value" >
<font-awesome-icon icon="search" />
</div>
</template>
<script>
export default {
name: 'SearchBox',
data() {
return {
value: ''
}
}
}
</script>
<style scoped>
.SearchBox {
display: flex;
}
</style>
the container code
<template>
<div>
<div>
<SearchBox #FilterByEvent="filterList(value)"></SearchBox>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import SearchBox from '#/components/templates/SearchBox.vue'
#Component({
components: {
SearchBox
}
})
export default class ContactList extends Vue {
data() {
return {
filterString: ''
}
}
filterList(filterStr :string) {
this.$data.filterString = filterStr;
}
}
</script>
The event is not working because the component is listening on a camel case event. Here is the explanation on why this doesn't work. Plus the #FilterByEvent expects a function to execute. So use instead
<SearchBox #filter-by-event="filterList"></SearchBox>
and emit the event this way:
<div class="SearchBox">
<input type="text" id="SearchBox" placeholder="Search"
v-on:keyup.13="$emit('filter-by-event', $event.target.value)" :value="value" >
I have found that passing the $event itself will pass the text box value
<SearchBox #FilterByEvent="filterList($event)"></SearchBox>

Is there a vue.js equivalent of ngTemplateOutlet?

Does vue.js have an equivalent of Angular's *ngTemplateOutlet directive? Let's say I have some components defined like this:
<template>
<div id="independentComponent">
Hello, {{firstName}}!
</div>
</template>
<script>
export default {
name: "independentComponent",
props: ['firstName']
}
</script>
...
<template>
<div id="someChildComponent">
<slot></slot>
<span>Let's get started.</span>
</div>
</template>
<script>
export default {
name: "someChildComponent"
}
</script>
I want to be able to do something like this:
<template>
<div id="parentComponent">
<template #indepdentInstance>
<independentComponent :firstName="firstName" />
</template>
<someChildComponent>
<template #indepdentInstance></template>
</someChildComponent>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {
someChildComponent,
independentComponent
},
data() {
return {
firstName: "Bob"
}
}
}
</script>
In Angular, I could accomplish this with
<div id="parentComponent">
<someChildComponent>
<ng-container *ngTemplateOutlet="independentInstance"></ng-container>
</someChildComponent>
<ng-template #independentInstance>
<independentComponent [firstName]="firstName"></independentComponent>
</ng-template>
</div>
But it looks like Vue requires the element to be written to the DOM exactly where it is in the template. Is there any way to reference an element inline and use that to pass to another component as a slot?
You cannot reuse templates like ngTemplateOutlet, but can combine idea of $refs, v-pre and runtime template compiling with v-runtime-template to achieve this.
First, create reusable template (<ng-template #independentInstance>):
<div ref="independentInstance" v-show="false">
<template v-pre> <!-- v-pre disable compiling content of template -->
<div> <!-- We need this div, because only one root element allowed in templates -->
<h2>Reusable template</h2>
<input type="text" v-model="testContext.readWriteVar">
<input type="text" v-model="readOnlyVar">
<progress-bar></progress-bar>
</div>
</template>
</div>
Now, you can reuse independentInstance template:
<v-runtime-template
:template="$refs.independentInstance.innerHTML"
v-if="$refs.independentInstance">
</v-runtime-template>
But keep in mind that you cannot modify readOnlyVar from inside independentInstancetemplate - vue will warn you with:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "readOnlyVar"
But you can wrap it in object and it will work:
#Component({
components: {
VRuntimeTemplate
}
})
export default class ObjectList extends Vue {
reusableContext = {
readWriteVar: '...'
};
readOnlyVar = '...';
}
You could try Portal vue written by LinusBorg a core Vue team member.
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
Sample code:
<template>
<div id="parentComponent">
<portal to="independentInstance">
<!-- This slot content will be rendered wherever the <portal-target>
with name 'independentInstance' is located. -->
<independent-component :first-name="firstName" />
</portal>
<some-child-component>
<portal-target name="independentInstance">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
</some-child-component>
</div>
</template>
There is also a vue-simple-portal written by the same author that is smaller but that mounts the component to end of body element.
My answer from #NekitoSP gave me an idea for a solution. I have implemented the sample below. It worked for me. Perhaps you want to use it as a custom component with props.
keywords: #named #template #vue
<template>
<div class="container">
<div ref="templateRef" v-if="false">write here your template content and add v-if for hide in current place</div>
....some other contents goes here
<p v-html="getTemplate('templateRef')"></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
Vue.extend({
methods:{
getTemplate(tempRef){
return this.$refs[tempRef].innerHTML
}
}
})
</script>
X-Templates
Use an x-template. Define a script tag inside the index.html file.
The x-template then can be referenced in multiple components within the template definition as #my-template.
Run the snippet for an example.
See the Vue.js doc more information about x-templates.
Vue.component('my-firstname', {
template: '#my-template',
data() {
return {
label: 'First name'
}
}
});
Vue.component('my-lastname', {
template: '#my-template',
data() {
return {
label: 'Last name'
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-firstname></my-firstname>
<my-lastname></my-lastname>
</div>
<script type="text/x-template" id="my-template">
<div>
<label>{{ label }}</label>
<input />
</div>
</script>
Not really sure i understand your problem here, but i'll try to give you something that i will opt to do if i want to add two components in one template.
HeaderSection.vue
<template>
<div id="header_id" :style="'background:'+my_color">
welcome to my blog
</div>
</template>
<script>
export default {
props: ['my_color']
}
</script>
BodySection.vue
<template>
<div id="body_id">
body section here
</div>
</template>
<script>
export default {
}
</script>
home.vue
<template>
<div id="parentComponent">
<header-section :color="my_color" />
<body-section />
</div>
</template>
<script>
import HeaderSection from "./components/HeaderSection.vue"
import BodySection from "./components/BodySection.vue"
export default {
name: "home",
components: {
HeaderSection,
BodySection
},
data() {
return {
my_color: "red"
}
}
}
</script>

Vue.js add function to a component

I am using Vue.js and I would like to add a function to my component, Map.vue, so when I click the button it calls a function declared in the same file :
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button >Do stuff</button>
</div>
</template>
I have only seen examples when the function is declared in the App.vue. How do you do that in my Map.vue file ?
Events
Both App.vue and Map.vue are components so they work the same for calling functions (methods) "in the same file" (single file components).
Trigger an event by adding v-on:click="yourFunctionHere()" and then make sure to declare your function in the methods object of the script section:
Map.vue
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button v-on:click="doStuff()">Do stuff</button>
</div>
</template>
<script>
export default {
methods: {
doStuff () {
alert('Did something!')
}
}
}
</script>
Custom Events
Since it's a little unclear what confused you about the child component Map.vue (since you understand the parent App.vue), perhaps you are asking how to call a function in App.vue from a button in the child Map.vue?
If that's the case, then you need to use custom events for child to parent communication:
Map.vue
<template>
<div>
<div class="google-map" :id="mapName">
</div>
<button v-on:click="doStuff()">Do stuff</button>
</div>
</template>
<script>
export default {
methods: {
doStuff () {
this.$emit('childStuff', 'some value from child')
}
}
}
</script>
App.vue
<template>
<div>
<Map v-on:childStuff="value => { parentStuff(value) }"></Map>
</div>
</template>
<script>
import Map from './components/Map'
export default {
components: {
Map
},
methods: {
parentStuff (value) {
alert(value)
}
}
}
</script>

Categories