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

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"))
);
};

Related

How to access a vue root property that have been loaded in created life hook?

My app is a Vue 2 front end app that load a configuration file since it is a generic front end and each client has its own configuration file.
The configuration is a Yaml file that is loaded when the application starts.
The configuration file has properties like:
urlBtn: https://www.youtube.com/
This is my main.js:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import axios from "axios";
import Vuelidate from "vuelidate";
import yaml from "js-yaml";
import fs from "fs";
import { BootstrapVue } from "bootstrap-vue";
import {
BIcon,
BIconChevronDoubleDown,
BIconChevronDoubleUp,
BIconEyeFill,
BIconEyeSlashFill
} from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
Vue.use(Vuelidate);
Vue.use(BootstrapVue);
Vue.component("BIcon", BIcon);
Vue.component("BIconChevronDoubleDown", BIconChevronDoubleDown);
Vue.component("BIconChevronDoubleUp", BIconChevronDoubleUp);
Vue.component("BIconEyeFill", BIconEyeFill);
Vue.component("BIconEyeSlashFill", BIconEyeSlashFill);
Vue.prototype.$axios = axios;
Vue.config.productionTip = false;
export const app = new Vue({
data() {
return {
ebaConfig: null,
publicPath: process.env.BASE_URL
};
},
methods: {
async loadEbaConfig() {
const config = await axios.get(`${this.publicPath}config.yaml`);
const doc = yaml.load(config.data);
this.ebaConfig = doc;
}
},
router,
store,
created() {
this.loadEbaConfig();
},
render: h => h(App)
}).$mount("#app");
When i try to access the ebaConfig property in a the mounted life hook of another component, it's null.
Example:
mounted() {
if (this.$root.ebaConfig.urlBtn.trim()) {
this.showCecomaWebBtn = true;
}
}
In the example above, I get the error:
[Vue warn]: Error in mounted hook: "TypeError: Cannot read properties of null (reading 'urlBtn')"
found in
---> <App> at src/App.vue
<root>
I don't understand why it happens since the property load is executed in the created lifecycle. Therefore it should be available to the other components in the mounted lifecycle. How can I solve that? What am I doing wrong?
Note:
The config file is a valid yaml file since everything works correctly when I use that property in a normal way, that is, outside of the life cycles.
It seems that, despite being in the created life cycle, the data is loaded at the end of the complete loading of the application.

Create Vanilla JS-compatible library with a Vue UI

I've got a Vue 3 project using Vite. When run in development mode, it opens a modal which makes some API calls.
main.js
import { createApp } from 'vue'
import { VTooltip } from 'floating-vue'
import store from '#/store'
import App from './App.vue'
import 'floating-vue/dist/style.css'
import './tailwind.css'
const app = createApp(App)
app.use(store)
app.directive('tooltip', VTooltip)
app.mount('#app')
App.vue
<template>
<Modal />
</template>
<script setup>
import Modal from './components/Modal.vue'
</script>
I want to make this project available as a library which can be installed with a <script> tag, or via NPM install, exposing a function which can be called to mount the modal, for example:
import myApi from '#me/myapi'
import '#me/myapi/dist/style.css'
myApi.show({ value: 'something' })
or
<link rel="stylesheet" href="https://unpkg.com/#me/myapi/dist/main.css">
<script src="https://unpkg.com/#me/myapi/dist/main.umd.js" />
myApi.show({ value: 'something' })
This call would initialise the library, mount it to the DOM and show the modal. I've found Vite has a library mode, which should support what I want.
My current thinking is to refactor main.js to export an initialiser function, and change the bundle entry to a class which calls the initializer:
main.js
// ...
export const initialize = (el, args) => {
const app = createApp(App)
app.use(store)
app.directive('tooltip', VTooltip)
app.mount(el)
return app
}
entrypoint.js
import { initialize } from './main'
class MyApi {
show(args) {
const element = document.createElement('div')
this.app = initialize(element, args)
}
}
export default new MyApi()
But I'm stuck at how you'd set the global variable for <script> integration (other than window.myApi = new MyApi()).
Is this the right way to go about this?

How to register a global directive in Vue for entire app

How can i register a Vue directive so it's available in all of my components of my Vue application? I'm using Vue 2
The docs say you can register global but where in my project do I add this Vue.directive(....)
src/directives/TestMe.js
import Vue from 'vue'
export default Vue.directive('test-me', {
inserted(el) {
el.style.backgroundColor = 'red'
},
})
src/components/SignInForm.vue
<template>
<div>
<code v-test-me>
Hello World
</code>
</div>
</template>
</script>
I know the current way of doing this by doing the following. However it's not practical if I plan on using my directive in multiple components and pages of my application.
<template>
<div>
<code v-test-me>
Hello World
</code>
</div>
</template>
<script>
import TestMe from '../../directives/TestMe'
export default {
directives: {
TestMe
}
}
</script>
Updated
Updated post based on answers below.
I've created a different files for each directive and then have an index.js file where i import all my directives. How can i initiate that index.js file in the directives folder at the start of my application?
// src/directives/TestMe.js
export default testMe = {
inserted(el) {
el.style.backgroundColor = 'red'
},
}
// src/directives/index.js
import testMe from './TestMe'
export default {
install(Vue) {
Vue.directive('test-me', testMe)
// Vue.directive('other-directive', myOtherDirective)
},
}
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import ???? from './directives'
const app = createApp(App)
app.use(????)
You have globalDirectives...where does that name come from?
EDIT: I didn't read that OP is using Vue 2.
To call .use(), you need to export object with function
install(Vue)
If you want the directive as a separate file.
// src/directives/TestMe.js
const testMe = {
inserted(el) {
el.style.backgroundColor = 'red'
},
}
export default testMe
Here is where you register all the directives. You can import files or just define your directives here.
// src/directives/index.js
import testMe from './TestMe'
// const myOtherDirective = {
// ...
// }
export default {
install (Vue) {
Vue.directive('test-me', testMe)
// Vue.directive('other-directive', myOtherDirective)
}
}
Here is how to register directives in Vue 2
You can register the global directives in main.js with Vue.use
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import globalDirectives from './directives'
Vue.use(globalDirectives)
const app = new Vue({
router,
store,
render: h => h(App),
})
app.$mount('#app')
Here is how to register directives in Vue 3
You can register the global directives in main.js after createApp
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import globalDirectives from './directives'
const app = createApp(App)
app.use(globalDirectives)

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>

How to load quasar framework Globally

I am new to Quasar framework. Could someone explains how load quasar-components in Globally use. (every where in my application)
My main.js is like:
import Vue from 'vue'
import Quasar from 'quasar'
import router from './router'
require(`quasar/dist/quasar.${__THEME}.css`)
Vue.config.productionTip = false
Vue.use(Quasar) // Install Quasar Framework
if (__THEME === 'mat') {
require('quasar-extras/roboto-font')
}
import 'quasar-extras/material-icons'
// import 'quasar-extras/ionicons'
// import 'quasar-extras/fontawesome'
// import 'quasar-extras/animate'
Quasar.start(() => {
/* eslint-disable no-new */
new Vue({
el: '#q-app',
router,
render: h => h(require('./App').default)
})
})
Unknown custom element: <q-btn> - did you register the component correctly?
For recursive components, make sure to provide the "name" option.
found in
---> <App> at src\App.vue
<Root>
Whenever you're using any Quasar elements (eg. q-btn, q-select), you need to import and export it in your .vue file.
Example, for a <q-btn> to display, you might use
<q-btn > Confirm </q-btn>
But to display that, you need to include following into your .vue file. Like:
import {
QSelect,
QBtn
} from 'quasar'
export {
QSelect,
QBtn
} from 'quasar'
Like this, you will be registering all your components.
In my projects I import and use with components like this
import Quasar, { QBtn, QSelect } from 'quasar-framewok';
Vue.use(Quasar, {
components: { QBtn, QSelect }
});
Only for test case import all
import Quasar, * as All from 'quasar';
Vue.use(Quasar, {
components: All,
directives: All
});
See Quasar docs

Categories