I want to dynamically set the title of the window for each route, so in each routes: [] child object I have a meta: { title: ... } object. For example:
routes: [
{
path: 'profile/:id',
name: 'Profile',
component: Profile,
meta: {
title: function (to, cb) {
const profileId = parseInt(to.params.id);
// ... do stuff ...
}
}
}
]
I call this title function in an afterEach hook:
router.afterEach((to) => {
document.title = 'My Site';
if (to.meta && to.meta.title) {
to.meta.title(router.app, to, (result) => { document.title += ' | ' + result; });
}
});
In the ... do stuff ... portion I want to call a method from my mixin GetAndStore.js called loadProfile(profileId). I added GetAndStore into the router's mixins, but loadProfile is not available (this.loadProfile is undefined). I loaded GetAndStore globally and tried again with the same results. I've tried every configuration I can think of for the past hour I've not found any way at all to access the methods from GetAndStore from within this setup.
Any ideas of what I'm missing or what I'd need to restructure in order to access mixin methods from within routes->element->meta->title ?
The issue is that...
Mixins are a flexible way to distribute reusable functionalities for Vue components
Vue-router is not a component, nor do you have access to the component loaded for the route.
What I would suggest is making loadProfile a named export from your GetAndStore mixin. Assuming your mixin is exported like
import axios from 'axios' // just an example
export default {
methods: {
loadProfile (profileId) {
return axios.get(...)
}
}
}
you can move your function out of the default export and name it...
export function loadProfile (profileId) {
return axios.get(...)
}
export default {
methods: {
loadProfile
}
}
then you can import just the loadProfile function in your route definitions...
import { loadProfile } from 'GetAndStore'
Of course, you could also import your mixin as it is and just use
import GetAndStore from 'GetAndStore'
// snip
GetAndStore.methods.loadProfile(to.params.id).then(...)
Maybe you can try do it on beforeRouteEnter inside Profile component. So there you can grab meta title and set title of page and there you will have access to mixin methods:
beforeRouteEnter (to, from, next) {
if (to.meta && to.meta.title) {
to.meta.title(router.app, to, (result) => { document.title += ' | ' + result; });
}
},
Docs: https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
Related
I have function inside user.js with default export like this
export default {
var getListFriends = async (accessToken) =>{
}
....other function
return {
getListFriends,
...other function...
}
}
then I import it to index.js
import userService from './user';
Then I will add only index.js to plugin.
I can call this.$userService (it shows as anonymous function) but this.$userService.getListFriends return undefined.
How can I call function getListFriends from import.
Thank you very much.
where is user.js?
if its inside plugins directory, you have to use inject to add it to your nuxt.
then you can access it via nuxt object inside your code.
see this example from nuxt docs:
export default ({ app }, inject) => {
// Inject $hello(msg) in Vue, context and store.
inject('hello', msg => console.log(`Hello ${msg}!`))
}
you can see the full document here
ok now for this to work the way you want you need to change your user.js file,
export default {
friends: () => {
console.log('hi bro');
},
notFriends: () => {
console.log('go away man im not your bro');
}
};
something like this.
you dont need inject or app in your user.js.
and your code problem is that you are defining a function and trying to access it's inner values from the outside like:
function boo() {
var b = 0;
}
you can not access the value of b with boo.b. its a local variable to the boo function
instead create a object and have multiple functions inside your object
I've created a mixin to change the page titles, using document.title and global mixins.
My mixin file (title.ts):
import { Vue, Component } from 'vue-property-decorator'
function getTitle(vm: any): string {
const title: string = vm.title
if (title) {
return `${title} | site.com`
}
return 'Admin panel | site.com'
}
#Component
export default class TitleMixin extends Vue {
public created(): void {
const title: string = getTitle(this)
if (title) {
document.title = title
}
}
}
Then i registered this mixin globally in main.ts:
import titleMixin from '#/mixins/title'
Vue.mixin(titleMixin)
Then setting up the title in a Vue component:
#Component
export default class Login extends Vue {
public title: string = 'New title'
}
I have like 5 components in my project, if i use console.log in a mixin, i can see that it fired in every component, step by step, thus document.title is set by a last component created() hook.
How to correctly set a title for a CURRENT page?
As you said, a global mixin will affect every component in your Vue app, which means that the logic to set the document.title will fire in the created hook of every component in your app.
I think what you're looking for is VueRouter's beforeRouteEnter hook, which is one of the navigation guards that the library makes available to any of your components. A component's beforeRouteEnter hook fires immediately before the route changes to whichever one it's associated with.
In your case it would look like this:
#Component
export default class TitleMixin extends Vue {
public beforeRouteEnter(to, from, next): void {
next(vm => {
const title: string = getTitle(vm)
if (title) {
document.title = title
}
})
}
}
You'll notice that the next function (which needs to be called for the route to resolve) is being passed a callback which has a reference to the component's instance (vm), which we're passing to getTitle instead of this. This is necessary because the beforeRouteEnter hook does not have a reference to this. You can read the docs I linked to for more info.
Instead of creating a global mixin, try using a route meta field along with a global resolve guard.
First, we'll start by adding a meta field to each RouteConfig in the /router/routes.ts file:
import { RouteConfig } from 'vue-router'
export default [
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: 'login-view' */ '#views/Login.vue'),
meta: {
title: 'Login', // Set the view title
},
},
// ... Add the title meta field to each `RouteConfig`
] as RouteConfig[]
Then, we'll create a global resolve guard, to set the title meta field as the document title, in the /router/index.ts file:
import Vue from 'vue'
import Router, { Route } from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({
// ... RouterOptions
})
// Before each route resolves...
// Resolve guards will be called right before the navigation is confirmed,
// after all in-component guards and async route components are resolved.
router.beforeResolve((routeTo, routeFrom, next) => {
const documentTitle = getRouteTitle(routeTo)
// If the `Route` being navigated to has a meta property and a title meta field,
// change the document title
if (documentTitle ) {
document.title = documentTitle
}
// Call `next` to continue...
next()
function getRouteTitle(route: Route): string {
const title: string = route.meta && route.meta.title
if (title) {
return `${title} | site.com`
}
return 'Admin panel | site.com'
}
})
export default router
You should use the mixin only in the parent component for your page (the one that holds all the page itself).
Using your vue-property-decorator should be in this way:
import { Vue, Component, Mixins } from 'vue-property-decorator';
#Component
export default class Login extends Mixins(titleMixin) {
public title: string = 'New title'
}
And do not import it globally with Vue.mixin(titleMixin). In this way it is imported for all the components.
Within a Vue component, I am calling a function from a separate JS file. I then need to call a method in my component just after this first function is completed:
My .vue component:
import myFunction from '#/components/functions';
export default {
name: 'test',
components: {
myFunction,
},
created(){
if (....) {
myFunction.function1(myParam)
.then((response) => {
this.method2();
});
},
methods:{
method2(){
something;
},
}
};
My separate functions.js file:
export default {
function1(myParam) {
...
return true;
},
};
I tried several things such as the last one shown in my code which gives me a
.function1(...).then is not a function
I am sure it is not that complicated but can not find the correct syntax.
The function in your other file could return a Promise or it can except a callback from your view component. Also, if you set this equal to self/vm and then use vm.method2(), this is because in the then callback this is defined in scope of that function not the Vue component.
I am building a simple website in which I have a route to category pages. I want to use a single dynamic route to move between pages.I am using vue-router for this project and the routes need to load different component
These are the desired routes for the website
example: '/shop/men' , '/shop/women','/shop/kids'
This my index.js file for router in which gender is appended in the last deciding which component to load the issue I am facing is how to handle this and load different component on depending on it
router-> index.js:
{
name: 'shop',
path: '/shop/:gender',
component: menCategoryViewsHandler('mencategory')
}
views -> viewHandler -> mencategory.js:
'use strict'
import Handle from '../mencategory.vue'
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
// This is a factory function for dynamically creating root-level views,
// since they share most of the logic except for the type of items to display.
// They are essentially higher order components wrapping the respective vue file.
export default function ViewsHandler (type) {
console.log('1',type)
return {
name: `${type}-mencategory-view`,
asyncData ({store, route}) {
//#todo : add the ssr and routerbefore load change script here
return Promise.resolve({})
},
title: camelize(type),
render (h) {
return h(Handle,
{
props: {type},
},
)
},
}
}
You need to use dynamic route matching along with a wrapper component which renders the correct Category component. This would handled by passing props to components.
// CategoryResolver.vue
import menCategory from './mencategory'
import womenCategory from './womencategory'
import kidsCategory from './kidscategory'
const components = {
menCategory,
womenCategory,
kidsCategory
}
export default {
functional: true,
props: ['category'],
render(h, ctx) {
return h(`components[${category}Category`], ctx.data, ctx.children)
}
}
Then your router would be defined as such:
const router = new VueRouter({
routes: [
{ path: '/shop/:category', component: CategoryResolver, props: true }
]
})
Say menCategoryViewsHandler('mencategory') returns a component called MenCat. It must have a prop that matches the route above, in this example category. In MenCat you would define:
export default {
props: ['category'],
...
}
Vue router will pass the matching url prop into your component for you.
I'm trying to pass the store module namespace via props to a component. When I try and map to getters with the prop, it throws this error,
Uncaught TypeError: Cannot convert undefined or null to object
If I pass the name as a string it works.
This Works
<script>
export default {
props: ['store'],
computed: {
...mapGetters('someString', [
'filters'
])
}
}
</script>
This does not work
this.store is defined
this.store typeof is a String
<script>
export default {
props: ['store'],
computed: {
...mapGetters(this.store, [
'filters'
])
}
}
</script>
I used this style utilising beforeCreate to access the variables you want, I used the props passed into the component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import { createNamespacedHelpers } from "vuex";
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above with no need to drop namespaces everywhere
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
})
}
}
}
Hope you find this useful.
I tackled this problem for hours, too. Then I finally came up with one idea.
Add attachStore function in a child vue component. A function nama is not important. Any name is ok except vue reserved word.
export default {
:
attachStore (namespace) {
Object.assign(this.computed, mapGetters(namespace, ['filters']))
}
}
When this vue component is imported, call attachStore with namespace parameter. Then use it at parent components attributes.
import Child from './path/to/child'
Child.attachStore('someStoresName')
export default {
name: 'parent',
components: { Child }
:
}
The error you're encountering is being thrown during Vue/Vuex's initialization process, this.store cannot be converted because it doesn't exist yet. I haven't had to work with namespacing yet, and this is untested so I don't know if it will work, but you may be able to solve this problem by having an intermediary like this:
<script>
export default {
props: ['store'],
data {
namespace: (this.store !== undefined) ? this.store : 'null',
},
computed: {
...mapGetters(this.namespace, [
'filters'
])
}
}
</script>
That ternary expression will return a string if this.store is undefined, if it isn't undefined then it will return the value in this.store.
Note that there is also a discussion about this on Vue's Github page here: https://github.com/vuejs/vuex/issues/863
Until Vue formally supports it, I replaced something like
...mapState({
foo: state => state.foo
})
with
foo () {
return this.$store.state[this.namespace + '/foo'] || 0
}
Where namespace is passed to my child component using a prop:
props: {
namespace: { type: String, required: true }
}