I have a custom directive for checking user roles for some component will be shown or not.
Vue.directive("permission", {
bind(el, binding) {
Vue.nextTick(() => {
el.vFillMarkerNode = document.createComment('');
el.parentNode.insertBefore(el.vFillMarkerNode, el.nextSibling);
if (Auth.canSee(binding.value)) {
el.textContent = binding.value;
} else {
el.parentNode.removeChild(el);
}
});
},
This will work but in the component which is not shown, It will get the data from api (on component created hook) even user does not have permission.
<mycomp v-permission="['Admin']"></mycomp>
Is there any way to tell the vue, if my directive condition does not fit, do not fire related component 'created' hook, so do not get data from api?
I don't think this is the right way to do what you are trying to do. Since you are depending on the root-element on the component in order to do your checks. You are doing your checks after the component has already mounted. hence the Vue.nexTick().
There are a two ways you could solve this issue.
Run your logic in the Vuex Store and use mapGetters in the parent component to get the permissions in all of your components.
Bind your permissions to the root element and do v-if="$root.permissions.includes('admin')" on your component
Related
I'm trying to understand the solution in this SO post. The solution allows the user to keep track of the previous route in the current route.
Below is the snippet of Vue code that I'm trying to understand. If I understand correctly, next accepts a callback function that receives the current component's vue instance. We then set the prevRoute data property of this vue instance to from. Is this interpretation correct? If not, what is actually happening?
If someone could also add a brief explanation as to what the Vue API is doing behind the scenes that would also be very helpful for me to actually understand the snippet of code.
...
data() {
return {
...
prevRoute: null
}
},
beforeRouteEnter(to, from, next) {
next(vm => {
vm.prevRoute = from
})
},
...
As per the documentation...
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument
So vm is the component instance assigned to the destination route.
From your question...
We then set the prevRoute data property of this vue instance to from. Is this interpretation correct?
Almost. All you're doing is setting a direct object property on the Vue component which is after all, just a JavaScript object at heart. For example
const vm = { name: 'I am totally a Vue component' }
vm.prevRoute = from
This property will not be reactive but you can certainly access it within your component via this, just as you can other non-data properties like $el, $refs, etc.
I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.
I've searched a lot, but there is no clear answer to that. Basically, what should be the best practice to automatically change a route after a mutation?
Ex: I click a button to login() -> action login that makes an http call -> mutation LOGIN_SUCCESSFUL -> I want to redirect the user to the main page $router.go()
Should I wrap the action in a Promise, and then listen to the result to call the route change from the component?
Should I do it directly from the $store?
Does vuex-router-sync helps in any way?
Thanks a lot!
The answer to this questions seems to be somewhat unclear in the Vue community.
Most people (including me) would say that the store mutation should not have any effects besides actually mutating the store. Hence, doing the route change directly in the $store should be avoided.
I have very much enjoyed going with your first suggestion: Wrapping the action in a promise, and changing the route from withing your component as soon as the promise resolves.
A third solution is to use watch in your component, in order to change the route as soon as your LOGGED_IN_USER state (or whatever you call it) has changed. While this approach allows you to keep your actions and mutations 100% clean, I found it to become messy very, very quickly.
As a result, I would suggest going the promise route.
Put an event listener on your app.vue file then emit en event by your mutation function. But I suggest you wrapping the action in a promise is good way
App.vue:
import EventBus from './eventBus';
methods: {
redirectURL(path) {
this.$router.go(path)}
},
created() {
EventBus.$on('redirect', this.redirectURL)
}
mutation:
import EventBus from './eventBus';
LOGIN_SUCCESSFUL() {
state.blabla = "blabla";
EventBus.$emit('redirect', '/dashboard')
}
As of now (mid 2018) API of Vuex supports subscriptions. Using them it is possible to be notified when a mutation is changing your store and to adjust the router on demand.
The following example is an excerpt placed in created() life-cycle hook of a Vue component. It is subscribing to mutations of store waiting for the first match of desired criteria to cancel subscriptions and adjust route.
{
...
created: function() {
const unsubscribe = this.$store.subscribe( ( mutation, state ) => {
if ( mutation.type === "name-of-your-mutation" && state.yourInfo === desiredValue ) {
unsubscribe();
this.$router.push( { name: "name-of-your-new-route" } );
}
} );
},
...
}
I'm using the vue-cli scaffold for webpack
My Vue component structure/heirarchy currently looks like the following:
App
PDF Template
Background
Dynamic Template Image
Static Template Image
Markdown
At the app level, I want a vuejs component method that can aggregate all of the child component's data into a single JSON object that can be sent off to the server.
Is there a way to access child component's data? Specifically, multiple layers deep?
If not, what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values? I'm trying to avoid hard dependencies between components, so as of right now, the only thing passed using component attributes are initialization values.
UPDATE:
Solid answers. Resources I found helpful after reviewing both answers:
Vuex and when to use it
Vuex alternative solution for smaller apps
In my child component, there are no buttons to emit changed data. It's a form with somewhat 5~10 inputs. the data will be submitted once you click the process button in another component. so, I can't emit every property when it's changing.
So, what I did,
In my parent component, I can access child's data from "ref"
e.g
<markdown ref="markdowndetails"></markdown>
<app-button #submit="process"></app-button>
// js
methods:{
process: function(){
// items is defined object inside data()
var markdowns = this.$refs.markdowndetails.items
}
}
Note: If you do this all over the application I suggest move to vuex instead.
For this kind of structure It's good to have some kind of Store.
VueJS provide solution for that, and It's called Vuex.If you are not ready to go with Vuex, you can create your own simple store.
Let's try with this
MarkdownStore.js
export default {
data: {
items: []
},
// Methods that you need, for e.g fetching data from server etc.
fetchData() {
// fetch logic
}
}
And now you can use those data everywhere, with importing this Store file
HomeView.vue
import MarkdownStore from '../stores/MarkdownStore'
export default {
data() {
sharedItems: MarkdownStore.data
},
created() {
MarkdownStore.fetchData()
}
}
So that's the basic flow that you could use, If you dont' want to go with Vuex.
what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values?
The flow of props is one way down, a child should never modify its props directly.
For a complex application, vuex is the solution, but for a simple case vuex is an overkill. Just like what #Belmin said, you can even use a plain JavaScript object for that, thanks to the reactivity system.
Another solution is using events. Vue has already implemented the EventEmitter interface, a child can use this.$emit('eventName', data) to communicate with its parent.
The parent will listen on the event like this: (#update is the shorthand of v-on:update)
<child :value="value" #update="onChildUpdate" />
and update the data in the event handler:
methods: {
onChildUpdate (newValue) {
this.value = newValue
}
}
Here is a simple example of custom events in Vue:
http://codepen.io/CodinCat/pen/ZBELjm?editors=1010
This is just parent-child communication, if a component needs to talk to its siblings, then you will need a global event bus, in Vue.js, you can just use an empty Vue instance:
const bus = new Vue()
// In component A
bus.$on('somethingUpdated', data => { ... })
// In component B
bus.$emit('somethingUpdated', newData)
you can meke ref to child component and use it as this
this.$refs.refComponentName.$data
parent-component
<template>
<section>
<childComponent ref="nameOfRef" />
</section>
</template>
methods: {
save() {
let Data = this.$refs.nameOfRef.$data;
}
},
In my case I have a registration form that I've broken down into components.
As suggested above I used $refs, In my parent I have for example:
In Template:
<Personal ref="personal" />
Script - Parent Component
export default {
components: {
Personal,
Employment
},
data() {
return {
personal: null,
education: null
}
},
mounted: function(){
this.personal = this.$refs.personal.model
this.education = this.$refs.education.model
}
}
This works well as the data is reactive.
I have a vue.js app where I use the componenttag with :is="currentView" approach for changing the active view. I have a "state machine" that keeps track of valid transitions from a component to another component depending on some business logic. Sometimes this will tell the vue instance that the new value of currentViewis the same as the old one. If this happens, the component will not be reloaded. Is there any way I can force the component to reload even if the view is the same? That is I want the data to be reloaded, and the lifecycle hooks being executed.
You can add a unique key attribute to your component definition so that it correctly triggers all the component lifecycle methods.
Your component definition should look something like this:
<component :is="view" :key="unique_key"></component>
Helpful links:
Reloading a dynamic component
Vue 'key' api reference
This seems to do the trick, hower it causes the view to flash, is there a way I can avoid this?
const oldView = this.currentView
this.currentView = engine.advance()
if(this.currentView == oldView) {
//force reload!
this.currentView = ''
this.$nextTick(function () {
this.currentView = oldView
})
}