I found that transition is not firing on dynamic route with parameters. For exemple with the code below, when I am in /chapter/1 and I go to /chapter/2 there is no transition. But when I am in /chapter/1 and I go to /profile/1 there is one !
main.js file
require('normalize.css')
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App'
import Panel from './components/Panel'
import Profile from './components/Profile'
window.bus = new Vue()
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/chapter/1' },
{ name:'chapter', path: '/chapter/:id', component: Panel},
{ name:'profile', path: '/profile/:id', component: Profile}
]
})
new Vue({
router,
el: '#app',
render: h => h(App)
})
App.vue template
<template>
<div id="app">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
<div class="controls">
<router-link :to="{ name: 'chapter', params: { id: Math.max(1, parseInt($route.params.id) - 1) }}">
Prev
</router-link>
<router-link :to="{ name: 'chapter', params: { id: parseInt($route.params.id) + 1 }}">
Next
</router-link>
</div>
</div>
</template>
Maybe is due to the fact that vue-router doesn't destroy the parent component ? I didn't found a way to run the transition from the code. I tried this configuration on vue-router example pack and the behavior is the same.
quote from the doc
One thing to note when using routes with params is that when the user navigates from /user/foo to /user/bar, the same component instance will be reused. Since both routes render the same component, this is more efficient than destroying the old instance and then creating a new one. However, this also means that the lifecycle hooks of the component will not be called.
To react to params changes in the same component, you can simply watch the $route object
Should I post an issue ?
Thanks for your help !
Can you check this working example: https://jsfiddle.net/mani04/dLnz4rbL/
I attempted to use the method described under Transitioning Between Elements in the docs.
In my fiddle example, which mostly uses the code from your question description, I used a transition wrapper within component, as shown below:
<transition name="fade" mode="out-in">
<div class="page-contents" :key="$route.params.id">
This is my chapter component. Page: {{parseInt($route.params.id)}}
</div>
</transition>
The document specifies that we need to provide a key to make them distinct elements for Vue.js. So I added your chapter ID as key.
I don't know if this is a hack or a proper solution, I moved from Angular2 to Vue only 2 weeks ago. But till someone gives you a better solution, you may use this method to get your transitions for dynamic routes.
Regarding posting this as an issue in github page of vue-router, I am not sure if this qualifies to be addressed / fixed, but you may definitely bring it to their notice. Fix may involve not reusing components, which is also not ideal. So it is a tough call for them. But the discussion should definitely be interesting! Please post back the issue link here if you decide to create one :-)
Came here with the same problem and #Mani's answer works fine.
But I don't really like the idea of using two <transition>s.
So I tried putting the key in <router-view> instead. And it works!
Working example: https://codepen.io/widyakumara/details/owVYrW/
Not sure if this the proper vue-way of doing things, I'm kinda new using vue.
PS. I'm using Vue 2.4.1 & Vue Router 2.7.0
Related
I am trying to add a widget/plugin/extension system to my existing web ui written with NuxtJS. I have a pages/view.vue single-file component where I would like to implement the extension system. My idea so far is to load dynamically component into the single-file component indicated via a query parameter e.g. /view?extension=example-a.
Idea 1
So far the best i could find is something like this: Include external javascript file in a nuxt.js page. I am just not sure, how the compiled their component, because I tried to build a webpack resource from my example-a component, but couldn't import it in the end like the example above. This was the error message [Vue warn]: Unknown custom element: <example-a> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Idea 2
Thought I could do it with the http-vue-loader, but I do not know where to start
Idea 3
Maybe I am thinking to far and there is even a easier solution.
You need to directly load all your component into your code. Then you can find your parameter from url in this.$route.query.extension (if you use vue-router) and then load component you want by <component :is="..."/> putting into 'is' a component you want.
<template>
<div>
<component :is="loadedComponent" v-if="loadedComponent !== null"/>
</div>
</template>
<script>
import exampleA from "./exampleA.vue";
import exampleB from "./exampleB.vue";
export default {
data(){
return {components:{'example-a':exampleA , 'example-b':exampleB }}
},
computed:{
loadedComponent(){
return this.components[this.$route.query.extension] ?? null;
}
}
}
</script>
My Vue components work fine when declared in the top level HTML file, like this
<body>
<div class='app' id='app'>
<header-bar id='headerBar'></header-bar>
<journal-page></journal-page>
</div>
<script src="js/app.js"></script>
</body>
but using a <journal-card> component inside the <journal-page> component gives me the error:
[Vue warn]: Failed to resolve component: journal-card at <JournalPage>.
How do I fix this please?
Here's my top level code that loads the Vue components, app.js:
import * as _vue from 'vue';
import _headerBar from './widgets/headerBar.vue';
import _journalCard from './widgets/journalCard.vue';
import _journalPage from './widgets/journalPage.vue';
import _store from './data/store.js';
const app = _vue.createApp
({
components:
{
'headerBar': _headerBar,
'journalCard': _journalCard,
'journalPage': _journalPage
},
data : _store,
methods: {}
});
const mountedApp = app.mount('#app');
and here's my journal-page.vue container
<template>
<ul>
<journal-card v-for="item in journal" :key="item.id" :entry=item></journal-card>
</ul>
</template>
<script lang="js">
import _store from '../data/store.js';
export default {
'data': _store
};
</script>
and journal-card.vue component
<template>
<div>
hi imma journal entry
</div>
</template>
<script lang="js">
export default {
'data': null,
'props': [ 'entry' ]
};
</script>
Registering components in the root component's components option doesn't make them global. Doing that just makes them available to the root component itself, not its children.
To register components globally, use app.component in your top-level code:
main.js
import { createApp } from 'vue';
import App from './App.vue';
import MyGlobalComponent from './components/MyGlobalComponent.vue';
const app = createApp(App);
app.component('MyGlobalComponent', MyGlobalComponent); ✅
const mountedApp = app.mount('#app');
In my scenario issue was different. I was trying to render a similar multi word Vue component in a laravel blade file.
If you're referring a Vue component in a non .Vue file (like HTML / Laravel Blade etc), you should use kebab-cased format to refer the component name. Like my-global-component
Vue documentation - https://vuejs.org/guide/essentials/component-basics.html#dom-template-parsing-caveats
Also as a side note since this page shows up when looking in search engines for some problems with "Vue 3 Failed to resolve component", there are a few things that were deprecated with Vue 3 / Quasar 2:
eg. q-side-link
that silently disappeared (previous doc here)
as per this comment:
QSideLink -- no longer required! Simply use a QItem or whatever
component you want and bind an #click="$router.push(...)" to it.
Sorry if it's not exactly on topic but it will bite some other people, so I prefer to help one person with this comment ;-)
make sure the mounting code comes at last so
app.mount('#app')
app.component('list-view', ListView2)
is wrong and it will not work but
app.component('list-view', ListView2)
app.mount('#app')
is correct.
I want to develop a library with vue.js component inside. This component will have navigation elements inside. I want it to work with and without vue router. In router mode it should use <router-link> and if no router is used it should return standard <a> tags.
<template>
<div>
<div v-if="vueRouterIsUsed">
<router-link v-bind:to="url">Router link</router-link>
</div>
<div v-else>
<a :href="url">No router</a>
<div>
</div>
</template>
<script>
export default{
props: {
url: {
type: String,
}
}
}
</script>
How to detect if vue-router is used in current Vue instance?
Is there a better way that if/else for doing <router-link> to <a> fallback if no router is installed?
You can check in the created (or mounted) lifecycle hook what is in this.$router you can access all the routes and the router object, which means you can check what you need. Set the isRouter variable based on that.
If you are using Vue3, you can use getCurrentInstance like this :
const isVueRouterInstalled =
!!getCurrentInstance().appContext.config.globalProperties.$router;
However, as the documentation says :
getCurrentInstance is only exposed for advanced use cases, typically in libraries. Usage of getCurrentInstance is strongly discouraged in application code
I have the following files. All I want to do is to be able to create different components that are injected. How do I achieve this using require.js? Here are my files:
main.js
define(function(require) {
'use strict';
var Vue = require('vue');
var myTemplate = require('text!myTemplate.html');
return new Vue({
template: myTemplate,
});
});
myTemplate.html
<div>
<my-first-component></my-first-component>
</div>
MyFirstComponent.vue
<template>
<div>This is my component!</div>
</template>
<script>
export default {}
</script>
I'm going to assume you're using webpack as explained in the Vue.js docs, or else your .vue file is useless. If you're not, go check how to set up a webpack Vue app first, it's what lets you use .vue files.
import Menubar from '../components/menubar/main.vue';
Vue.component('menubar', Menubar);
That's how you add e.g. a menubar component to the global scope. If you want to add the component to just a small part of your app, here's another way of doing it (this is taken from inside another component, but can be used in exactly the same manner on your primary Vue object):
import Sidebar from '../../components/sidebar/main.vue';
export default {
props: [""],
components: {
'sidebar': Sidebar
},
...
You can load components without webpack, but I don't recommend it, if you're gonna keep using Vue (which I strongly suggest you do) it's worth it to look into using webpack.
Update
Once again, really, really, really consider using webpack instead if you're gonna be continuing with Vue.js, the setup may be slightly more annoying but the end result and development process is waaaay better.
Anyway, here's how you'd create a component without webpack, note that without webpack you can't use .vue files since the .vue format is part of their webpack plugin. If you don't like the below solution you can also use e.g. ajax requests to load .vue files, I believe there is a project somewhere out there that does this but I can't find it right now, but the end result is better with webpack than with ajax anyway so I'd still recommend going with that method.
var mytemplate = `<div>
<h1>This is my template</h1>
</div>`
Vue.component('mycomp1', {
template: mytemplate
});
Vue.component('mycomp2', {
template: `
<div>
Hello, {{ name }}!
</div>
`,
props: ['name'],
});
As you can see, this method is A LOT more cumbersome. If you want to go with this method I'd recommend splitting all components into their own script files and loading all those components separately prior to running your actual app.
Note that `Text` is a multi line string in javascript, it makes it a little easier to write your template.
And as I said, there is some project out there for loading .vue files using ajax, but I can't for the life of me find it right now.
I've just started working with Vue.JS and there's one small issue that's bugging me. My file structure similar to the following:
+ js
|--+ components
| |-- parent.vue
| |-- child.vue
|-- main.js
Then in my main.js I have the following:
window.Vue = require('vue');
require('vue-resource');
Vue.component('parent', require('./Components/parent'));
Vue.component('child', require('./Components/child'));
var app = new Vue({ el: "#app" });
(I'm not actually certain what vue-resource is, but this was set up for me by a fresh install of Laravel 5.3)
At a glance I immediately noticed that my main.js file was going to get unmanageable if I added too many components. I don't have this issue when working with ReactJS because main.js only needs to include the "parent" component, and the parent component includes the child component. I figured Vue.JS would have a similar trick to help me organize my components - but reading through the docs I didn't find one (maybe I missed it?)
Is there a way to either have a Vue component list its dependencies (for Browserify / Webpack to bundle) or recursively run a javascript statement on every file in a directory (so Browserify / Webpack just packs up the whole thing)?
I'm not concerned with async components at the moment - so if the solution breaks that functionality it will be okay. One day I would like to play around with using Webpack to create async components and only loading them as I need them, but today I'm more interested in just getting this up and running so I can play way Vuex.
The Vue.component syntax is for global components only, if you have a component that is being used inside another component use this:
import Parent from './components/Parent.vue';
import Child from './components/Child.vue';
new Vue({
el: "#app",
components: { Parent, Child }
});
Than inside this components you can use the other components.
The only advantage of using Vue.component(Parent) is that you can use this <parent></parent> component globaly in all your other components without declaring them implicitly.
Good Luck :)
You don't need to import everything at the top level.
In your main.js you can import the Parent component
import Parent from './components/Parent.vue'
new Vue({
el: "#app",
components: {
Parent
}
})
With your Parent.vue
<template>
<div>
<p>I am the parent</p>
<child></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
mounted() {
console.log('mounted parent')
}
}
</script>
<style scoped>
// ...
</style>
Then in your Child.vue
<template>
<p>I am the child</p>
</template>
<script>
export default {
mounted() {
console.log('mounted child')
}
}
</script>
<style scoped>
// ...
</style>
And you should end up with
<div>
<p>I am the parent</p>
<p>I am the child</p>
</div>
I found a way, not sure if it's the best in terms of performance and webpack chunk size. I created an index.js file in the components root:
export const HelloWorld = require('./HelloWorld.vue').default
So, inside the components I would use:
const { HelloWorld } = require('#/components')
Due to babel issues I need to make a mix of require and export, also the use of default attribute after require — as I read in some babel use discussions.