As you start growing your application and have more users, you will want to add some access control to some part of your application. It's a pretty common problem to stumble upon when you are building sophisticated applications
Let's say you are building an application for a retail store and you will have different levels access.
- Salesperson
- Manager
The salesperson will be able to checkout people, but won't be able to provide a discount to a customer who needs a discount. The manager can come in, authenticate themselves and be able to see the user interface to allow them to give a discount.
This type of a use-case can happen in any environment that we build user interfaces for. So coming up with a solid solution will help you delivery more effective apps.
How would we do something like this in Vue?
v-if
Let's say we have a component that applies a discount:
<customer-discount :order="order" @discount-applied="onDiscountApplied" />
This component could include an <input>
field with a <button>
to apply the discount.
A basic solution might be to add a v-if
check to hide this component if the user is not allowed to give discounts.
<customer-discount
v-if="user.canApplyDiscount"
:order="order"
@discount-applied="onDiscountApplied" />
This will work fine for most use-cases. If you are already following this pattern, continue doing it. There is nothing wrong with it.
Using v-if
everywhere you need access control could become cumbersome. You will end up repeating the logic in multiple places. This will be worse once you want to share the behavior across different Vue apps. For modularity and re-usability, we will need to do it differently.
What is a better solution then?
<slot />
With <slot>
s in Vue, you can create some really awesome components. They allow you to compose and add features to your apps using templates.
In this example, we will create a component called <access-control />
. This component can accept a role that a user has to have in order to see what's inside the component. What's inside the component will be toggled based on a logic inside <access-control />
.
Let's look at the implementation:
<template>
<div v-if="hasRole">
<slot />
</div>
</template>
<script>
export default {
props: {
roles: { type: Array, default: () => [] },
role: { type: String, default: '' }
},
computed: {
hasRole () {
return this.roles.length === 0 || this.roles.includes(this.role)
}
}
}
</script>
All the magic happens in the hasRole
computed property. Everything is powered by the props
that are passed into this component. You can drop this component anywhere in your app and reuse with other roles.
<access-control :roles="['salesperson']" role="manager">
<customer-discount :order="order" @discount-applied="onDiscountApplied" />
</access-control>
Let's say, you want to now show a component based on multiple roles. You could refactor the props to accept an array of roles as the requirement. Instead of doing that, you can compose <access-control />
.
Here is an example of a component that allows the user to order inventory:
<access-control :roles="userRoles" role="manager">
<access-control :roles="userRoles" role="team-lead">
<order-inventory />
</access-control>
</access-control>
That looks pretty cool. One downside is the repeating of :roles="userRoles"
. If you are using Vuex, you can use a mapGetter
to get the user roles. So that will clean up your templates and just allow you to pass the required role
.
Now, I can already hear functional component enthusiasts typing below about how I could have used a functional component here instead. And they are right!
Enter functional: true
I created the same example with a functional component. It's shorter as well. Since it's functional
, Vue doesn't create a new component object for functional
components. It also makes it an even better candidate for composing for multiple roles.
<script>
export default {
functional: true,
props: {
roles: { type: Array, default: () => [] },
role: { type: String, default: '' }
},
render (_, { props: { roles, role }, children }) {
if (roles.length === 0 || roles.includes(role)) return children
}
}
</script>
Believe or not, that's the whole component. You can import it and use it the same way as the non-functional version. This one will be super light-weight and powerful.
Here is Codesandbox that shows both versions:
So here you go. A simple component you can use in any Vue app that requires some access control. It's easy to use and light-weight.
I hope you enjoyed reading this post. Let me know if you have done something like this! It's a pretty common problem. I stumbled upon it recently and create a component for it at work.
Top comments (3)
Hey, nice post!
What do you think if I do functional component as you do but in the template, if declarate 'funcional'.
It works well and I think it's an easy way to make it reusable no?
I'm new in vue and I`m learning this great framework π
imgur.com/a/c2NYZKc
Yeah, that could work as well. It's the same once Vue compiles the template. It will look like what I wrote.
Ahh okay, nice.
Thanks!!