VueJS + TS; Component property not binding - javascript

I am trying to bind a component property by setting the related component attribute to a value but it is not binding the value when inspecting with Vue devtools or when outputting the value into the HTML. The value remains to be set to the default value that is set on the component.
I event set a string attribute to just a static string and even that is not binding.
The component also isn't outputted into the html at all, besides the top level div, but the Vue devtools do detect the component in the dom.
Code:
Component HTML:
<style scoped lang="sass">
#import './discord-widget.scss';
</style>
<template>
<div>
<b-card bg-variant="dark" :header="`Currently online: ${widgetData.members.length}`" text-variant="white">
<div v-for="user in widgetdata.members" class="discord-member">
<img :src="user.avatar_url" alt="" class="d-inline-block">
<div class="d-inline-block align-top has-game" v-if="user.game">
<span>{{ user.username }}#{{user.discriminator}}</span>
<span><br />Playing <b>{{ user.game.name }}</b></span>
</div>
<div class="d-inline-block" v-else>
<span>{{ user.username }}#{{user.discriminator}}</span>
</div>
</div>
</b-card>
</div>
</template>
<script src="./discord-widget.ts"></script>
Component ts:
import Vue from "vue";
import { DiscordWidgetResult } from "../../models/discord";
import Component from "vue-class-component";
import { Prop } from "vue-property-decorator";
#Component
export default class DiscordWidgetComponent extends Vue {
#Prop(Object) public widgetdata: DiscordWidgetResult = {} as DiscordWidgetResult;
#Prop(String) public test: string = "";
async mounted() {
this.widgetdata.members = this.widgetdata.members.sort((a, b) => a.game ? -1 : b.game ? -1 : 0);
}
}
Parent HTML using the component:
<discord-widget :widgetdata="widgetdata" v-on:load="getWidgetData" :test="'test'" class="pull-right ml-auto p-2 d-none d-sm-none d-md-none d-lg-block sticky-top" />
Parent ts:
import Vue from "vue";
import { Provide } from "vue-property-decorator";
import { DiscordWidgetResult } from "../../models/discord";
import { discordWidgetService } from "../../boot";
export default class NopeGamingView extends Vue {
#Provide()
public widgetdata: DiscordWidgetResult = {} as DiscordWidgetResult;
async created() {
}
async getWidgetData() {
this.widgetdata = await discordWidgetService.GetGuildData();
console.log("get data");
}
}

So, turned out my error was quite a simple one but easy to overlook.
I had forgotten to put the '#Component' decorator on my 'NopeGamingView' which caused it to not be an actual component. If you encounter as similar problem make sure you have the decorator on your view.

Related

Pass data attribute to vue 3 root instance [duplicate]

I am terribly new to Vue, so forgive me if my terminology is off. I have a .NET Core MVC project with small, separate vue pages. On my current page, I return a view from the controller that just has:
#model long;
<div id="faq-category" v-bind:faqCategoryId="#Model"></div>
#section Scripts {
<script src="~/scripts/js/faqCategory.js"></script>
}
Where I send in the id of the item this page will go grab and create the edit form for. faqCategory.js is the compiled vue app. I need to pass in the long parameter to the vue app on initialization, so it can go fetch the full object. I mount it with a main.ts like:
import { createApp } from 'vue'
import FaqCategoryPage from './FaqCategoryPage.vue'
createApp(FaqCategoryPage)
.mount('#faq-category');
How can I get my faqCategoryId into my vue app to kick off the initialization and load the object? My v-bind attempt seems to not work - I have a #Prop(Number) readonly faqCategoryId: number = 0; on the vue component, but it is always 0.
My FaqCategoryPAge.vue script is simply:
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Prop } from 'vue-property-decorator'
import Card from "#/Card.vue";
import axios from "axios";
import FaqCategory from "../shared/FaqCategory";
#Options({
components: {
Card,
},
})
export default class FaqCategoryPage extends Vue {
#Prop(Number) readonly faqCategoryId: number = 0;
mounted() {
console.log(this.faqCategoryId);
}
}
</script>
It seems passing props to root instance vie attributes placed on element the app is mounting on is not supported
You can solve it using data- attributes easily
Vue 2
const mountEl = document.querySelector("#app");
new Vue({
propsData: { ...mountEl.dataset },
props: ["message"]
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Vue 3
const mountEl = document.querySelector("#app");
Vue.createApp({
props: ["message"]
}, { ...mountEl.dataset }).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Biggest disadvantage of this is that everything taken from data- attributes is a string so if your component expects something else (Number, Boolean etc) you need to make conversion yourself.
One more option of course is pushing your component one level down. As long as you use v-bind (:counter), proper JS type is passed into the component:
Vue.createApp({
components: {
MyComponent: {
props: {
message: String,
counter: Number
},
template: '<div> {{ message }} (counter: {{ counter }}) </div>'
}
},
}).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app">
<my-component :message="'Hello from HTML'" :counter="10" />
</div>
Just an idea (not a real problem)
Not really sure but it can be a problem with Props casing
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you're using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents
Try to change your MVC view into this:
<div id="faq-category" v-bind:faq-category-id="#Model"></div>
Further to Michal LevĂ˝'s answer regarding Vue 3, you can also implement that pattern with a Single File Component:
app.html
<div id="app" data-message="My Message"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const mountEl = document.querySelector("#app");
Vue.createApp(MyComponent, { ...mountEl.dataset }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
Or you could even grab data from anywhere on the parent HTML page, eg:
app.html
<h1>My Message</h1>
<div id="app"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const message = document.querySelector('h1').innerText;
Vue.createApp(MyComponent, { message }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
To answer TheStoryCoder's question: you would need to use a data prop. My answers above demonstrate how to pass a value from the parent DOM to the Vue app when it is mounted. If you wanted to then change the value of message after it was mounted, you would need to do something like this (I've called the data prop myMessage for clarity, but you could also just use the same prop name message):
<template>
{{ myMessage }}
<button #click="myMessage = 'foo'">Foo me</button>
</template>
<script>
export default {
props: {
message: String
},
data() {
return {
myMessage: this.message
}
}
};
</script>
So I'm not at all familiar with .NET and what model does, but Vue will treat the DOM element as a placeholder only and it does not extend to it the same functionality as the components within the app have.
so v-bind is not going to work, even without the value being reactive, the option is not there to do it.
you could try a hack to access the value and assign to a data such as...
const app = Vue.createApp({
data(){
return {
faqCategoryId: null
}
},
mounted() {
const props = ["faqCategoryId"]
const el = this.$el.parentElement;
props.forEach((key) => {
const val = el.getAttribute(key);
if(val !== null) this[key] = (val);
})
}
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="12">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
where you get the value from the html dom element, and assign to a data. The reason I'm suggesting data instead of props is that props are setup to be write only, so you wouldn't be able to override them, so instead I've used a variable props to define the props to look for in the dom element.
Another option
is to use inject/provide
it's easier to just use js to provide the variable, but assuming you want to use this in an mvc framework, so that it is managed through the view only. In addition, you can make it simpler by picking the exact attributes you want to pass to the application, but this provides a better "framework" for reuse.
const mount = ($el) => {
const app = Vue.createApp({
inject: {
faqCategoryId: {
default: 'optional'
},
},
})
const el = document.querySelector($el)
Object.keys(app._component.inject).forEach(key => {
if (el.getAttribute(key) !== null) {
app.provide(key, el.getAttribute(key))
}
})
app.mount('#app')
}
mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="66">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
As i tried in the following example
https://codepen.io/boussadjra/pen/vYGvXvq
you could do :
mounted() {
console.log(this.$el.parentElement.getAttribute("faqCategoryId"));
}
All other answers might be valid, but for Vue 3 the simple way is here:
import {createApp} from 'vue'
import rootComponent from './app.vue'
let rootProps = {};
createApp(rootComponent, rootProps)
.mount('#somewhere')

TypeScript VueJS: Using watcher with computed property

I'm building a custom component that will allow me to use arbitrary <div>s as radio buttons. Currently my code looks like so:
<template>
<div
class="radio-div"
:class="selected ? 'selected' : ''"
#click="handleClick"
>
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
.radio-div {
display: inline-block;
border-radius: 5px;
border: 1px solid #000;
padding: 5px;
margin: 5px;
}
.radio-div.selected {
box-shadow: 0 0 10px rgba(255, 255, 0, 0.5);
border: 2px solid #000;
}
</style>
<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
#Component
export default class RadioDiv extends Vue {
#Prop()
val!: string;
#Prop({
required: true
})
value!: string;
selected = false;
mounted() {
this.selected = this.value === this.val;
}
#Watch("value")
onChange() {
this.selected = this.value === this.val;
}
handleClick(e: MouseEvent) {
this.$emit("input", this.val);
}
}
</script>
To utilize this I can put it in a template like so:
<template>
<div class="q-pa-md">
<q-card>
<q-card-section class="bg-secondary">
<div class="text-h6">Political Affiliation</div>
</q-card-section>
<q-separator />
<q-card-section>
<radio-div v-model="politicalParty" val="Republican">
<div class="text-h6">Republican</div>
<p>Wrong answer</p>
</radio-div>
<radio-div v-model="politicalParty" val="Democrat">
<div class="text-h6">Democrat</div>
<p>Wrong answer</p>
</radio-div>
<radio-div v-model="politicalParty" val="Independent">
<div class="text-h6">Independent</div>
<p>These people clearly know what's up</p>
</radio-div>
</q-card-section>
</q-card>
</div>
</template>
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
import RadioDiv from "../components/RadioDiv.vue";
#Component({
components: { RadioDiv }
})
export default class Profile extends Vue {
politicalParty = "Independent";
}
</script>
This works as expected. I can click on the <div>s and it switches which one is selected and updates the variable appropriately.
But now I want to tie this into a global state manager. So instead of a local politicalParty variable, I have a computed property like so:
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
import RadioDiv from "../components/RadioDiv.vue";
import globalState from "../globalState";
#Component({
components: { RadioDiv }
})
export default class Profile extends Vue {
get politicalParty() {
return globalState.politicalParty;
}
set politicalParty(val) {
globalState.politicalParty = val;
}
}
</script>
Putting a console.log statement in the setter I can see that it is getting called, and the variable is being updated. But putting a console.log statement in my value watcher (in the RadioDiv component) shows it's no longer being called now that I'm using computed properties.
What's the secret to get my RadioDiv reactive again, now that I'm using global state?
Update
The issue doesn't seem to be specific to my custom components, or to watchers. I decided to ignore this and move on while waiting for an answer from StackOverflow and ran into the issue again with Quasar's components:
<template>
...
<q-card-section>
<div class="row">
<div class="col">
<q-slider v-model="age" :min="0" :max="100" />
</div>
<div class="col">
{{ ageText }}
</div>
</div>
</q-card-section>
...
</template>
<script lang="ts">
...
get age() {
return globalState.age;
}
set age(val) {
globalState.age = val;
this.ageText = "You are " + val + " years old";
}
...
</script>
This led me to try using no custom components whatsoever:
<template>
...
<input type="text" v-model="test" />
<p>Text: {{ test }}</p>
...
</template>
<script lang="ts">
let testVal = "";
...
get test() { return testVal; }
set test(val) { testVal = val; }
...
</script>
Once again: No reactivity. When I use a computed property with v-model nothing seems to change after the call to set
If globalState were just an Object, then it would not be reactive, so computed is only going to read its value once. Same for testVal, which is just a String (also not reactive itself).
To make the test computed prop reactive to testVal, create testVal with Vue.observable():
const testVal = Vue.observable({ x: '' })
#Component
export default class Profile extends Vue {
get test() { return testVal.x }
set test(val) { testVal.x = val }
}
Similarly for globalState, exporting a Vue.observable() would allow your computed props to be reactive:
// globalState.js
export default Vue.observable({
politicalParty: ''
})

Vue Class Component property not defined on instance

I have the problem that my UI is somehow not updating after the applications fetches some data from the sever backend.
I have the following code:
<template>
<div v-if="restaurant">
<div class="center logo-container">
<img class="img-fit" v-bind:src="'/api/restaurant/logo/' + restaurant.id" alt=""/>
</div>
<h2 class="title center dm-text-header">{{ restaurant.name }}</h2>
<h4 class="subheading center">{{ restaurant.address.street }}, {{ restaurant.address.city }}</h4>
</div>
</template>
<script lang="ts">
import axios from 'axios';
import { Options, Vue } from "vue-class-component";
import { Tag } from "./Tag";
import { Restaurant } from "./Restaurant";
#Options({
props: {
}
})
export default class Menu extends Vue {
// hardcoded for testing
restaurantId = "8ykw9ljq";
tagUrl = "/api/menu/" + this.restaurantId + "/tags";
restaurantUrl = "/api/restaurant/" + this.restaurantId;
restaurant!: Restaurant;
tags: Tag[] = [];
mounted() {
// get tags
this.getTags();
// get restaurant
this.getRestaurant();
}
getRestaurant(): void {
axios.get<Restaurant>(this.restaurantUrl)
.then(res => {
this.restaurant = res.data;
});
}
getTags(): void {
axios.get(this.tagUrl)
.then(res => {
this.tags = res.data;
});
}
}
</script>
I verified that the backend actually serves the correct restaurant and logged the results after the axios call finishes. The problem is that the DOM is not updated. If I add the following to the the DOM is upadated:
<template>
...
<div>
{{tags}}
</div>
<template>
It seems to me that vue somehow only updated the DOM if it recognizes changes to the already initialized empty array but not the currently uninitialized restaurant object.
I further get a warning:
[Vue warn]: Property "restaurant" was accessed during render but is not defined on instance. on the v-if what I kind of find strange because that is the exact reason it is there. How do I need to initialize the restaurant, such that the update through axios is correctly recognized by vue?
Try a Typescript union with null:
restaurant: Restaurant | null = null;
From the Vue Class Component docs:
Note that if the initial value is undefined, the class property will not be reactive which means the changes for the properties will not be detected
and
To avoid this, you can use null value or use data hook instead:
import Vue from 'vue'
import Component from 'vue-class-component'
#Component
export default class HelloWorld extends Vue {
// `message` will be reactive with `null` value
message = null
// See Hooks section for details about `data` hook inside class.
data() {
return {
// `hello` will be reactive as it is declared via `data` hook.
hello: undefined
}
}
}

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).
(To make reading this easier and faster, look for this comment: This is the part you should pay attention to)...
<template>
<div id="app">
<div>
<div>
<CommandBar />
</div>
<div>
<Navigation />
</div>
</div>
<div id="lowerContent">
<!-- This is the part you should pay attention to -->
<template v-if="showLeftContent">
<div id="leftPane">
<div id="leftContent">
<router-view name="LeftSideBar"></router-view>
</div>
</div>
</template>
<!-- // This is the part you should pay attention to -->
<div id="mainPane">
<div id="mainContent">
<router-view name="MainContent"></router-view>
</div>
</div>
</div>
</div>
</template>
And then in the same App.vue file, here's the script portion
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CommandBar from './components/CommandBar.vue';
import Navigation from './components/Navigation.vue';
#Component({
components: {
CommandBar,
Navigation,
}
})
export default class App extends Vue {
data() {
return {
showLeftContent: true // <--- This is the part you should pay attention to
}
}
}
</script>
Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent">.
Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:
{
path: '/home',
name: 'Home',
components: {
default: Home,
MainContent: Home, // load the Home compliment the main content
LeftSideBar: UserSearch // load the UserSearch component in the left side bar area
}
},
So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false. That's the part I can't figure out.
Let's say we have a "Notes" component that looks like this.
<template>
<div class="notes">
Notes
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view.
Thanks!
EDIT:
I changed the script section of the Notes component from:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
to:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
mounted() {
this.$root.$data.showLeftContent = false;
}
}
</script>
And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes, the left side bar still shows.
EDIT 2:
If I put an alert in the script section of the Notes component:
export default class Notes extends Vue {
mounted() {
alert(this.$root.$data.showLeftContent);
//this.$root.$data.showLeftContent = false;
}
}
The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".
EDIT 3:
Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)
Edit 4:
Inching along!
export default class App extends Vue {
data() {
return {
showLeftContent: true
}
}
leftContent(value: boolean) {
alert('clicked');
this.$root.$emit('left-content', value);
}
}
This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.
As it says on #lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.
Assuming your Navigation component looks like below, you can do something like that:
#Navigation.vue
<template>
<div>
<router-link to="/home" #click.native="leftContent(true)">Home</router-link> -
<router-link to="/notes" #click.native="leftContent(false)">Notes</router-link>
</div>
</template>
<script>
export default {
methods: {
leftContent(value) {
this.$emit('left-content', value)
}
}
}
</script>
And in your main App you listen the emit on Navigation:
<template>
<div id="app">
<div>
<Navigation #left-content="leftContent" />
</div>
<div id="lowerContent">
<template v-if="showLeftContent">
//...
</template>
<div id="mainPane">
//...
</div>
</div>
</div>
</template>
<script>
//...
data() {
return {
showLeftContent: true
}
},
methods: {
leftContent(value) {
this.showLeftContent = value
}
}
};
</script>
A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.
However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6
You might also want to look into creating a simple event bus using a vue instance, or using vuex.
If you'd like to access the data property (or props, options etc) of the root instance, you can use this.$root.$data. (Check Vue Guide: Handling Edge)
For your codes, you can change this.$root.$data.showLeftContent to true/false in the hook=mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.
Below is one demo:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div :style="{'background-color':color}" style="padding: 10px">
Reach to root: <button #click="changeRootData()">Click me!</button>
<hr>
<slot></slot>
</div>`,
props: ['color'],
methods: {
changeRootData() {
this.$root.$data.testValue += ' :) '
}
}
})
new Vue({
el: '#app',
data() {
return {
testValue: 'Puss In Boots'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>{{testValue}}</h2>
<child color="red"><child color="gray"><child color="green"></child></child></child>
</div>

Is it possible to pass a component as props and use it in a child Component in Vue?

In a Vue 2.0 app, let's say we have components A, B and C.
A declares, registers and uses B
Is it possible to pass C from A to B?
Something like this:
<template>
<div class="A">
<B :child_component="C" />
</div>
</template>
And use C in B somehow.
<template>
<div class="B">
<C>Something else</C>
</div>
</template>
The motivation: I want to create a generic component B that is used in A but receives from A its child C. Actually A will use B several times passing different 'C's to it.
If this approach is not correct, what is the proper way of doing it in Vue?
Answering #Saurabh
Instead of passing as props, I tried the suggestion inside B.
<!-- this is where I Call the dynamic component in B -->
<component :is="child_component"></component>
//this is what I did in B js
components: {
equip: Equipment
},
data () {
return {
child_component: 'equip',
_list: []
}
}
Basically I'm trying to render Equipment, but the dynamic way
I get 3 errors in console and a blank page
[Vue warn]: Error when rendering component at /home/victor/projetos/tokaai/public/src/components/EquipmentFormItem.vue:
Uncaught TypeError: Cannot read property 'name' of undefined
TypeError: Cannot read property 'setAttribute' of undefined
Apparently I'm doing something wrong
Summing up:
<!-- Component A -->
<template>
<div class="A">
<B>
<component :is="child_component"></component>
</B>
</div>
</template>
<script>
import B from './B.vue';
import Equipment from './Equipment.vue';
export default {
name: 'A',
components: { B, Equipment },
data() {
return { child_component: 'equipment' };
}
};
</script>
<!-- Component B -->
<template>
<div class="B">
<h1>Some content</h1>
<slot></slot> <!-- Component C will appear here -->
</div>
</template>
You can use special attribute is for doing this kind of thing. Example of dynamic component and its usage can be found here.
You can use the same mount point and dynamically switch between multiple components using the reserved element and dynamically bind to its is attribute.
Here's how is can be used with either an imported component or one passed as a prop:
<template>
<div class="B">
<component :is="myImportedComponent">Something</component>
--- or ---
<component :is="myPassedComponent">Something else</component>
</div>
</template>
<script>
import myImportedComponent from "#/components/SomeComponent.vue"
export default {
props: {
myPassedComponent: Object
},
components: {
myImportedComponent
},
}
</script>
Here's solution to forward custom component through props of another component
:is is special attribute and it will be used to replace your actual component and it will be ignored if you try to use it as a prop in your component. Luckily you can use something else like el and then forward this to component like so:
<template>
<div>
<component :is="el">
<slot />
</component>
</div>
</template>
<script>
export default {
name: 'RenderDynamicChild',
props: {
el: {
type: [String, Object],
default: 'div',
},
},
}
</script>
Any valid element you use in el attribute will be used as a child component. It can be html or reference to your custom component or div by default as specified in component declaration.
Passing custom component to prop is little bit tricky. One would assume you declare in a components property of parent component and then use it for el attribute but this doesn't work. Instead you need to have your dynamic component in data or computed property so you can use it in a template as a prop. Also note AnotherComponent doesn't need to be declared in components property.
<template>
<RenderDynamicChild :el="DynamicComponent">
Hello Vue!
</RenderDynamicChild>
</template>
<script>
import RenderDynamicChild from './DynamicChild';
import AnotherComponent from './AnotherComponent';
export default {
name: "ParentComponent",
components: { DynamicChild },
data() {
return {
DynamicComponent: AnotherComponent,
};
},
};
</script>
Using computed property for your dynamic component allows you to switch between components easily:
<script>
import DynamicChild from './DynamicChild';
import AnotherComponent from './AnotherComponent';
export default {
name: "ParentComponent",
components: { DynamicChild },
data() { return { count: 0 } },
computed: {
DynamicComponent() {
return this.count % 2 > 1 ? AnotherComponent : 'article';
},
},
};
</script>
Increase this.count to alternate between AnotherComponent and simple article html element.
Maybe it's too late to answer this question. But I think it could help others with this same issue.
I've been looking for a way to pass components throw others in vue, but it looks that VUE3 have a approach for that using named slots:
Here it's the documentation about that:
https://v3.vuejs.org/guide/component-slots.html#named-slots
Basically you can have:
<template>
<div class="A">
<slot name="ComponentC"></slot> <!-- Here will be rendered your ComponentC -->
</div>
<div class="A">
<slot name="ComponentD"></slot> <!-- Here will be rendered your ComponentD -->
</div>
<div class="A">
<slot></slot> <!-- This is going to be children components -->
</div>
</template>
And from your B component
<template>
<div class="B">
<A>
<template v-slot:ComponentC>
<h1>Title of ComponentC </h1>
</template>
<template v-slot:ComponentD>
<h1>Title of ComponentD </h1>
</template>
<template v-slot:default>
<h1>Title of child component </h1>
</template>
</A>
</div>
</template>
If you would like to use another component within your functional component you can do the following:
<script>
import Vue from 'vue'
import childComponent from './childComponent'
Vue.component('child-component')
export default {}
</script>
<template functional>
<div>
<child-component/>
</div>
</template>
Reference:
https://github.com/vuejs/vue/issues/7492#issue-290242300
If you mean Dynamically importing a component in a parent component, so yes, you can do that in Vue3 using:
<component :is="child_component" />
but to render "child_component" itself dynamically, you can use
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
Let me give you an example:
let's say you have several multiple child components (ChildA, ChildB, ChildC) that you want to load dynamically based on what you pass to the parent component (Parent), so the Parent component will be something like this:
Parent
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const props = defineProps<{
childComponent?: string;
}>();
const AsyncComp = defineAsyncComponent(() =>
import(`./${props.childComponent}.vue`)
)
</script>
<template>
<component :is="AsyncComp"/>
</template>
and then you can call the Parent component dynamically wherever you want like this:
<Parent :childComponent="child-a"/>
<Parent :childComponent="child-b"/>
<Parent :childComponent="child-c"/>
For a better explanation, you can check this article:
https://medium.com/#pratikpatel_60309/dynamic-importing-component-templates-with-vue-js-78d2167db1e7

Categories

Resources