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
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>
Please see this minimum example
Test.vue
<template>
<div>
<slot name="this_is_not_scoped_slots"/>
</div>
</template>
<script >
import Vue from "vue";
export default Vue.extend({
mounted() {
console.log(Object.keys(this.$scopedSlots));
}
});
</script>
App.vue
<template>
<Test>
<template #this_is_not_scoped_slots>But it shows in this.$scopedSlots</template>
</Test>
</template>
<script>
import Test from "./Test.vue";
export default {
components: {
Test
}
};
</script>
In the above example, this console will log out ["this_is_not_scoped_slots"].
Why is this happening?
There are two properties in Vue instance
this.$slots
this.$scopedSlots
These two act really differently:
If you use v-slot:my_scope_name="{ someValue }", then my_scope_name won't show up in this.$slots
However, no matter what you define, your named slots will always show up in this.$scopedSlots
Why is this happening?
I'm building a library, I want to conditional rendering if the user has provided named slots or not, should I always use this.$scopedSlots to detect those things?
According to the official API :
....
All $slots are now also exposed on $scopedSlots as functions. If you work with render functions, it is now recommended to always access slots via $scopedSlots, whether they currently use a scope or not. This will not only make future refactors to add a scope simpler, but also ease your eventual migration to Vue 3, where all slots will be functions.
I'm building a website that is based on Nuxt TypeScript Starter template. I've created a dynamically routed page _id.vue inside of my pages folder and I want to have access to that id property inside of my TS class.
I can access it in my template by writing {{$route.params.id}} but when I try to reference $route inside of the class I get an error:
error TS2304: Cannot find name '$route'.
As a simple solution, try importing route from vue-router, like this:
<script lang="ts">
import Component from "vue-class-component"
import { Route } from "vue-router"
#Component({})
export default class RoutingExample extends Vue {
created() {
console.log(this.$route) // this should not throw TS errors now
}
}
</script>
Other solutions I think would require you to augment the Vue module, something similar to what you'd find here in the Vue docs.
More Vue + TypeScript examples can be found in this repo: https://github.com/jsonberry/vue-typescript-examples
better and the right solution for nuxtjs project to find current page URL or param just use
{{ $nuxt.$route.name }}
I was able to access route.params via fetch function, taking params from the context object that is passed to this function by default:
<script lang="ts">
import Component from "nuxt-class-component"
#Component({})
export default class RoutingExample extends Vue {
fetch ({ store, params }) {
console.log("params:", params.id);
...
}
}
</script>
but the caveat is that params would only be available in that fetch hook, not in other hooks such as created or mounted. So Jason answer is also valid
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 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