Vue3 how to implement dynamic components with Composition API? - vue.js

I want to use dynamic component functionality with Composition API in Vue3. I have no problem with Options API. I followed this instruction: link
There is my code:
<script setup>
import { ref } from "vue";
import General from "../components/ContractGeneral.vue";
import Networks from "../components/ContractDetails.vue";
const tabs = {
General,
Networks,
};
let currentTab = ref("Networks");
</script>
<template>
<main>
<nav>
<ul>
<li v-for="tab in tabs" :key="tab" #click="currentTab = tab">
<div>
<p>{{ tab }}</p>
</div>
</li>
</ul>
<div></div>
</nav>
<section>
<keep-alive>
<component :is="tabs[currentTab]"></component>
</keep-alive>
</section>
</main>
</template>
and the problem is, that {{tab}} in my case shows a full component object, not just a name like 'General'. It shows something like this: "src/components/ContractGeneral.vue", "__hmrId": "f8a99314" }
How can I fix this error?

You could get the tab key from the v-for loop as follows :
<li v-for="(tab,key,index) in tabs" :key="tab" #click="currentTab = key">
<div>
<p>{{ key }}</p>
</div>
</li>

Related

VueJs 3 checkbox v-model on array of objects not working properly

I am having a hard time understanding why my v-model isn't working correctly
I have an 'service' object which contains a property 'actions' of type IAction[]
I also declared an object actions which is an array of IAction and am currently trying to bind checkBoxes to the actions array, but it is not working.
I feel like i am missing something obvious here but would need a little help understanding what it is.
Here is the relevant code
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
I would appreciate any feedback as I am relatively new to VueJs,
Thank you
I think you might not understand what you are doing in code, so I wrote examples.
Bad Code:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
// here you iterate thro array and assign to action variable
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
// Here you using actions with "s" on end so you using empty array declered in script
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If you are getting some data from service.Actions use them! v-model will override those actions if they are ref() or `reactive().
Example:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="item in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<input v-model="item.is" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If service.Actions is only array actions you want to add to array in script actions v-model is not a way you do that!
Probably code you need:
<script lang="ts">
const actions = ref([]) // Use refs everywhere !!! A specially in forms.
function deleteItem() {
// ToDo delete item from actions array
}
</script>
<template>
<div v-for="item in service.Actions" :key="item.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<button #click="actions = [...actions, item]">ADD</button>
</div>
</div>
</div>
<div>
<div v-for="{ item, index } in actions" :key="item.id">
<span>{{ item.EnumName }}</span><button #click="deleteItem(index)">X</button>
</div>
</div>
</template>
As Mises pointed out, the v-model has to be a part of the same object as the v-for, so i just put my services and the actions array in an object
let foo = { services: serviceStore.services, actions: [] as IAction[] }

Is this the right approach to create a component?

I made a view to show some contact information for the user:
<template>
<div v-for="user in users" class="user">
<div class="userInformation">
<img :src="user.photo" />
<div class="userName">
<h3>{{ user.age }}</h3>
<p>{{ user.gender }}</p>
</div>
</div>
<div class="button-wrapper">
<a href="#">
<button #click="$router.push(`/user/${user.id}`)">User Profile</button>
</a>
</div>
</div>
</template>
<style>
</style>
users is an array that holds all users which I fetch from the backend.
I want to create a component so that I can re-use the user card in other classes and don´t have to include the markup. I tried it the following way but I'm stuck at the button to redirect the user and the img because I don´t know how to use named slots there.
<template>
<div class="user">
<div class="userInformation">
<img />
<div class="userName">
<h3>{{ age }}</h3>
<p>{{ gender }}</p>
</div>
</div>
<div class="button-wrapper">
<a href="#">
<button>User Profile</button>
</a>
</div>
</div>
</template>
<script>
export default {
name: "UserCard",
props: [
"age",
"gender"
]
};
</script>
Another problem is that I have to re-create the fetch method for my users in other classes to access the user information. Would there be a better way of doing this?
// fetch user data from backend and create users array
...
<div v-for="user in users" :key="user.name">
<UserCard
:age="`${user.age}`"
:gender="`${user.gender}`"
/>
</div>
Is this the right approach to create a reusable component?
You're headed in the right direction for your component. If you wanted a named slot for the button you could use something like this.
Child Component
<template>
...
<slot name="button">
<!-- default/fallback content can be provided, if the parent does
not provide slot content the button-wrapper div will appear -->
<div class="button-wrapper">
<a href="#">
<button>Default Button</button>
</a>
</div>
</slot>
</div>
</template>
Parent
<div v-for="user in users" :key="user.name">
<UserCard
:age="user.age"
:gender="user.gender">
<template v-slot:button>
<div>some custom button here {{ user.phone }}</div>
</template>
</UserCard>
</div>
Also compilation scope (Vuejs v2 guide) is an important thing to keep in mind with slots - "Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope."
In terms of fetching your users, that's a separate issue. Look into something like Vuex or other ways of managing shared state if you find yourself constantly having to fetch users in various components

Dynamically Change image when navigating to different routes

I have a Banner component. I want to change the image in there when i am navigating to different routes. Its like using different banner images for separate inner pages. I want to use that banner component as a nested component in other components.
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav m-auto custom-nav">
<li class="nav-item">
<router-link to="/">home</router-link>
</li>
<li class="nav-item">
<router-link to="/about-Us">about us</router-link>
</li>
<li class="nav-item">
<router-link to="/characters">characters</router-link>
</li>
<li class="nav-item">
<router-link to="/our-comics">comics</router-link>
</li>
</ul>
</div>
Banner component
<template>
<section class="banner-outer">
<img src="../assets/banner.jpg" alt class="img-fluid" />
<div class="banner-content-outer">
<div class="container">
<div class="banner-content-inner">
<h1>EXPLORE THE UNIVERSE</h1>
<h5>Dive into the dazzling domain of all the classic characters you love ...</h5>
</div>
</div>
</div>
</section>
</template>
About Us Component, Contact us Component
<template>
<banner />
</template>
In each page component, pass the image in as a prop to the banner component...
<template>
<banner which-banner="//path/to/img.png"></banner>
<div>component page content...</div>
</template>
In the banner Component...
<script>
...
{
props: ['whichBanner']
}
..
</script>
<template>
<section class="banner-outer">
<img :src="whichBanner" alt class="img-fluid" />
<div class="banner-content-outer">
<div class="container">
<div class="banner-content-inner">
...
</div>
</div>
</div>
</section>
</template>
Vue Codeply
you can use beforeEach :
router.beforeEach((to, from, next) => {
// check current route to set the valid image
// in vuex or localstorage or even a global window variable
if(to === 'about-us') {
// using global variable
window.bannerImagePath = 'path/to/about-us/banner.png';
// localstorage
localStorage.setItem('bannerImagePath', 'path/to/about-us/banner.png');
} else if( ... ) { ... }
return next();
});
Then you can get it in BannerComponent inside mounted() or set a method for that
Example :
<img :src="getCurrentBannerImage()" alt class="img-fluid" />
methods: {
getCurrentBannerImage() {
// global variable example
return require(window.bannerImagePath) // or any storage you choose
// localStorage
let _item = localStorage.getItem('bannerImagePath');
return require(_item) // or any storage you choose
}
},

vuejs show html in component like reactjs

suppose we have a component like this.
<template>
<div class=" ">
<div class="flex-grow">{{title}}</div>
<div class=" p-5">
<!-- want to show here -->
</div>
</div>
<script>
export default {
props: ['title'],
mounted() {
console.log('Component mounted.')
}
}
</script>
i try this
<comp :title="'here'">
<h1> this is </h1>
</comp>
i want to show html b.w vue component tag, like we do in react.
how we can achieve this
If I understand the question, you're looking to create a component that can accept child elements. In order to do this, use <slot></slot> in your template where you'd like child elements to be inserted. Child elements can be other components or HTML.
See the reference for more thorough details.
In your example:
<template>
<div class=" ">
<div class="flex-grow">{{title}}</div>
<div class=" p-5">
<slot></slot>
</div>
</div>
</template>

Vue js, footer not displaying

I have installed vue cli, when i try to show my navbar(component) it shows but when i try to show my footer(component)it shows.
When i show other cms pages the navbar and footer are together the text is not displaying between...
Find the image, Output:
This is my navbar where it is a component under component folder
<template>
<div>
<ul class="navbar-nav">
<li class="nav-item">
<router-link to="/" class="nav-link">Home</router-link>
</li>
<li class="nav-item">
<router-link to="/contacts" class="nav-link">Contacts Us</router-link>
</li>
<li class="nav-item">
<router-link to="/login" class="nav-link">Login</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Navbar'
}
</script>
This is my HomePage where it is a component under component folder
<template>
<div>
test
</div>
</template>
<script>
export default {
name: 'HomePage'
}
</script>
This is App.vue Where it is inside the Src/ Folder
<template>
<div id="app">
<app-header></app-header>
<app-footer></app-footer>
<router-view></router-view>
</div>
</template>
<script>
import Navbar from '#/components/Navbar'
import Footer from '#/components/Footer'
export default {
name: 'App',
components: {
'app-header': Navbar,
'app-footer' : Footer
}
}
</script>
This the Footer Component
<template>
<div class="hello">
<footer class="text-muted">
<div class="container">
<p class="float-right">
Back to top
</p>
<p>Album example is © Bootstrap, but please download and customize it for yourself!</p>
<p>New to Bootstrap? Visit the homepage or read our getting started guide.</p>
</div>
</footer>
</div>
</template>
<script>
export default {
name: 'Footer',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
In your App.vue template you have the order of the elements wrong.
Move the <app-footer> below rhe <router-view>:
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
<app-footer></app-footer>
</div>
</template