Vue 3 dynamic component inside setup template - javascript

I have following problem.
<script setup lang="ts">
import { RouterView } from "vue-router";
import defaultLayout from "#/layouts/default.vue";
import { useDefaultStore } from "#/stores/default";
let { getLayout } = useDefaultStore();
</script>
<template>
<component :is="getLayout">
<RouterView />
</component>
</template>
I use Pinia as the store. I checked getLayout its getting defaultLayout
I know in Vue.js 2 you had to register it:
export default {
components: {
defaultLayout
}
}
How do i do it now?

You can't descructure the store like that, you have to use Pinias storeToRefs() (https://pinia.vuejs.org/core-concepts/#using-the-store). Maybe it has something to do with that, I can't tell without the code of the store.
I'd recommend using this plugin https://github.com/JohnCampionJr/vite-plugin-vue-layouts if you're using Vite and need layouts.

Use defineAsyncComponent
defineAsyncComponent(() => import("#/layouts/default.vue"))
or register the component globally in your main.js:
app.component('defaultLayout', defaultLayout)

Related

How to access "this" keyword in <script setup> vue SFC

I am working on a simple scaffold for vite, vue and vuetify with typescript and I wanted to use the script setup version of SFC vue
<script setup lang="ts">
One thing that I can't figure out is how to access "this" keyword properties?
for example in my old vue projects i could write this
this.$vuetify.themes.light.colors.primary
so i had the ability to access $vuetify anywhere in the component, but now in script setup mode "this" keyword is undefined;
How to access "this" properties?
The setup keyword in the script tag is a syntax sugar for:
const myComponent = createComponent({
setup() {
const foo = "may-the-force";
let bar = "be-with-you";
return {
foo,
bar
}
}
})
So naturally, in a setup function, you won't need this keyword because now you can just do:
bar = "be-not-with-you";
return {
foo,
bar
}
Now, when you initiated your Vuetify framework an instance is going to be kept somewhere. Something like this:
import Vue from "vue";
import { createVuetify } from 'vuetify'
Vue.use(Vuetify);
export const vuetify = createVuetify({ theme: {} });
Now that you have stored your vuetify instance somewhere you can import it just like you would do any other javascript/typescript file:
<script setup lang="ts">
import { vuetify } from "path/to/vuetify/instance";
console.log(vuetify.themes.light.colors.primary);
// You can even set dark mode if you prefer to
vuetify.framework.theme.dark = true;
</script>
Edit
I'm guessing that things are a little bit different in Vue 3. After researching a little bit you can get the current Vue instance by using getCurrentInstance
<script setup>
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
// it should be here in this instance the vuetify object with its properties
console.log(app);
</script>
Using provide and inject
For e.g. I am using marcoschulte/vue3-progress package to show a loading bar at the top whenever routing happens.
According to vue3-progress docs, I can use this.$progress inside the script tag, but the this keyword is unavailable inside .
So in this scenario, I had to use provide and inject for props drilling.
In main.js or app.js (in laravel)
require('./bootstrap');
import { createApp } from 'vue'
import App from './views/App.vue'
import store from './store/index'
import router from './router'
import Vue3Progress from 'vue3-progress'
const options = {
position: "fixed",
height: "3px",
color: "#9d5e32",
}
let app = createApp(App)
app.use(store)
.use(router)
.use(Vue3Progress, options)
// $progress is set automatically when I import vue3-progress at top
.provide('progressBar', app.config.globalProperties.$progress)
.mount('#app')
In any SFC
<template>
<vue3-progress />
<TopNav />
<router-view></router-view>
<Footer />
</template>
<script setup>
import Header from '../components/admin/Fixed/Header.vue'
import Footer from '../components/admin/Fixed/Footer.vue'
import { inject } from 'vue-demi'
import { useRouter } from 'vue-router'
let router = useRouter()
let progressBar = inject('progressBar')
router.beforeEach((to, from, next) => {
progressBar.start()
next()
})
router.afterEach((to, from) => {
progressBar.finish()
})
</script>

[Vue warn]: Failed to resolve component when tried to create global component

I new on Vue Typescript. I've tried to create global components but I got a warning and component not loaded on the template. This how I tried to create global components
App.vue
import { createApp } from "vue"
import App from "./App.vue"
import "./registerServiceWorker"
import "./globalComponents"
import router from "./router"
import store from "./store"
createApp(App)
.use(store)
.use(router)
.mount("#app")
globalComponents.ts
import { createApp } from "vue"
const app = createApp({})
// Forms
app.component("ui-input", () => import("#/components/core/ui/Input.vue"))
Input.vue
<template lang="pug">
.ui-input
input(v-model="$attrs" v-on="$listeners")
</template>
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({
inheritAttrs: false
})
</script>
Hope you all can help me, Thanks in advance
As of Vue 3, if you create an app using createApp, it will be a standalone Vue App instance. So if you need to add a global component then you will need to add it on the app object created from createApp, here's the code for that:
const app = createApp({});
app.component('my-component-name', MyComponent) // <-- here you can register.
app.mount("#app");
But if there are a lot of components then adding them in the main.ts file will be a mess, so we can create another file, like you did, so:
Your current globalComponents.ts
import { createApp } from "vue"
const app = createApp({})
// Forms
app.component("ui-input", () => import("#/components/core/ui/Input.vue"))
The Problem
But notice here's a mistake. What is it? You created another app using createApp. As I referred earlier that if you need to create a global component, you can only create on the same instance.
Fix
As we know the problem here is that we are creating another instance, which is again a new and standalone instance, so we need to figure out the way we can have the same app instance in globalComponents.ts as well, so we will pass the app from main-ts to globalComponents.ts, like:
globalComponents.ts
import { App } from "vue";
// register components
export const registerComponents = (app: App): void => {
app.component("ui-input", () => import("#/components/core/ui/Input.vue"));
}
And now you can call registerComponents in main.ts as:
main.ts
const app = createApp(App)
.use(store)
.use(router);
registerComponents(app); // <-- here you go
app.mount("#app");
You will still get something like:
[Vue warn]: Invalid VNode type: undefined (undefined).
You can read more here about how to define an async component in vue 3. To fix that error you will need to wrap your import in defineAsyncComponent as:
globalComponents.ts
import { defineAsyncComponent } from "vue";
// register components
export const registerComponents = (app) => {
app.component(
"ui-input",
defineAsyncComponent(() => import("#/components/Input.vue"))
);
};

VueJS nested components fail in an NPM package

The example below are using simplified examples.
Both components work separately, however when one is nested within the other neither render on the page.
index.js (entry point)
// Test components
import TestComponent from '../src/TestComponent.vue'
import Test2Component from '../src/Test2Component.vue'
export {
TestComponent,
Test2Component
}
Both TestComponent and Test2Component will render this way:
<template>
<div class="container">
<TestComponent></TestComponent>
<Test2Component></Test2Component>
</div>
</template>
<script>
import Vue from 'vue'
import { TestComponent, Test2Component } from 'myPackage'
Vue.component('TestComponent', TestComponent);
Vue.component('Test2Component', Test2Component);
However if I move the Test2Component tag into Test1Component.Vue:
<template>
<p>This is the TestComponent</p>
<Test2Component></Test2Component>
</template>
<script>
import Vue from 'vue'
import Test2Component from './Test2Component';
Vue.component('Test2Component', Test2Component);
console.log( Test2Component)
export default {
name: 'TestComponent',
components: {
Test2Component
}
}
</script>
Not even the TestComponent.vue parent component renders.
I found the solution. Do not import Vue from 'vue' and modify it, this is a duplicate of the Vue instance. This syntax seems especially restrictive to get it to work when packaging.
Instead add it as a component to export default:
<script>
import { TestComponent, Test2Component} from 'myPackage'
export default {
components: {
'test-component': TestComponent,
'test2-component': Test2Component
},
...
</script>

composition api doesn't work with Nuxt-TS

Created nuxt app using npx create-nuxt-app. Followed documentation at https://typescript.nuxtjs.org, however i have an issue using #vue/composition-api:
example component.vue:
<template>
<div>
{{ msg }}
</div>
</template>
<script lang="ts">
import { createComponent, ref } from '#vue/composition-api'
export default createComponent({
setup() {
const msg = ref('hello')
return {
msg
}
}
})
</script>
Doesn't work, throws an error "Property or method "msg" is not defined on the instance but referenced during render." because it doesn't see my ref. I've added composition API as plugin in "plugins/composition-api.ts":
import Vue from 'vue'
import VueCompositionApi from '#vue/composition-api'
Vue.use(VueCompositionApi)
Then in nuxt.config.ts:
plugins: ['#/plugins/composition-api']
My bad, forgot about TS-runtime in nuxt:
npm i -S #nuxt/typescript-runtime
Then you need to update package.json: https://typescript.nuxtjs.org/guide/runtime.html#installation

Where to import component in Vue?

New to Vue, working off a scaffolded project from the command line. Currently I have:
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home
from '../components/home'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
}
]
})
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
// default styles are here
}
</style>
And home.vue
<template>
<chart-component></chart-component>
</template>
// rest of home
I am trying to create and import chart-component.vue to use inside home.vue but not sure where to import it at. I've tried adding it in main.js and App.vue unsuccessfully.
chart-component.vue
<template>
<div>
<h1>Testing Chart Component</h1>
</div>
</template>
<script>
export default {
name: 'chart',
data() {
return {
}
}
}
</script>
This seems like a simple problem but unsure how to solve it. My best guess was importing it inside App.vue and adding a components { ChartComponent } underneath the name. Second attempt was importing it into main.js and putting ChartComponent inside components: { App, ChartComponent }.
I would rename your component to ChartComponent from chart-component, as the dash is not a supported character. Vue will properly convert the name to dashes as needed, but when you're searching your code for components, it helps to have consistent names.
anyway. for using a component, you need to import it and define it first
home.vue:
<template>
<ChartComponent></ChartComponent>
</template>
<script>
import ChartComponent from './ChartComponent';
export default {
components: {
ChartComponent
}
}
</script>
Got it to work. Since I only need it locally inside the home.vue component I imported it inside there under data I added
components: {
'chart-component': ChartComponent
}
Importing component becomes much easier in Vue 3 script setup SFCs
<script setup>
import ChartComponent from './components/ChartComponent.vue'
</script>
<template>
<ChartComponent />
</template>

Categories