Advanced Frontend Webapp Architecture With Laravel and Vue JS

Advanced Frontend Webapp Architecture With Laravel and Vue JS

Advanced Frontend Webapp Architecture With Laravel and Vue JS

ATTENTION: Vue’s Vue-Router sub-project has come out with an update that fills most of the functionality in this article.

You can now use this.$router.app at any time inside a component to access the main component. Check it out here: http://router.vuejs.org/api/properties.html. And, Vue now has events built in. So I can say this.$dispatch(‘foo’) to run methods on other components. Haven’t played with it much but it looks really cool. http://vuejs.org/api/instance-methods.html#Events.

Vue JS 2 - The Complete Guide (incl. Vuex)

That said the rest of this article teaches important skills in front-end applications and is still a good read.

Do you use Laravel PHP and Vue JS? Do you wish there was a better way to structure your applications you build? This tutorial is just for you! In it, we set up simple two-way communication between the different components of your front-end app.

Would you like to get the results of this tutorial without reading through it? Check out Laravue, the brand new boilerplate repo for starting off!

This tutorial is inspired by a video by Laracasts that sets up the basic app architecture. You don’t have to watch it, but here’s a link if you’re curious: https://laracasts.com/series/learning-vuejs/episodes/9

To sum up the result of the video, you have two directories and one main file: resources/assets/js/components/ , resources/assets/js/views/ , and resources/assets/js/app.js. In the components folder you have two files for each component: Component.js and component.template.html . Component.js is the model while component.template.html is the view. You do the same for views/, with about.js and about.template.html . You export all of this using module.exports to package the functioanlity. If you use coffeescript, name them Component.coffee and view.coffee. Simple stuff.

In app.js, you include all of these into the components object of your main View-Model. The views are named like ‘name-view’ and the components like ‘name’. You also have a currentView variable in the data object, which represents the view that should be displayed. To display all that, you use .

Alright, now that you’ve finished that, you should have a great architecture to start off with. You have your main app.js file (which will now be called app), individual views, and components. However, there’s a few things that are missing:

  1. a standard way to communicate from app -> view
  2. a standard way to communicate from view -> app
  3. a standard way to communicate from components -> view
  4. a standard way to communicate from components -> app

There’s a few important things missing from basic Vue and Laravel integrations.
Why would we want these features? Suppose we want to change the currentView from within a view. Right now, there’s no easy way to do that. Using my setup, we can just run this.app.currentView = ‘awesome-view’; .

Another example is if you want to have one user object that can be accessed application-wide. Just add it to the data object of your main app and it can be accessed from views using this.app.user !

To make components easier to understand, let’s think of vue components just like classes in PHP: each view and component is like a class that is then instantiated into objects through ’s or a .

There’s a few important things missing from basic Vue and Laravel integrations.
A quick note on components: when I say it sometimes I mean a component in general, other times I mean non-view, new DOM element components. Like the ones in the components/ folder. It should be pretty clear when I’m talking about which one, contact me if you have any ways I can make it more clear.

Now that we understand the basics of components, let’s try and get a (even) better architecture for setting them up and addressing the problems above. Let’s think about Laravel: there’s a main IoC container, which is accessed through the App facade, which contains various smaller classes called Service Containers. This is starting to sound really similiar to the front end: we have a main app that contains our smaller components & views (which are really just glorified components).

In Laravel, each service provider can access the IoC container through $this->app. What if we could do the same thing in views, saying this.app? Well it turns out you can!

There’s a few important things missing from basic Vue and Laravel integrations.
With this technique, the app variable is passed easily into each view using two-way binding. This means you can change the master application from a view, and the changes are reflected application-wide on the app view-model itself, every view, and every component.

Part 1: using this.app in each view

There’s a few important things missing from basic Vue and Laravel integrations.
First, let’s go to the main HTML file that contains the <component is=“{{ currentView}}”>. We need to add the app as a prop to that. So, change that to <component is=“{{currentView}}” app=“{{@ app }}”> .

If you’ve used Vue before you’ll be familiar with the mustache syntax, but the {{@ might leave you a little confused. Basically, it says pass the app into the component, but if the component makes a change to the app, reflect it in the scope that contains the component too. In other words it enforces two-way binding. If you didn’t have the @, you could do something to the app in the view and then nothing would happen anywhere else!

Of course, if you are in a .blade.php file, change that to <component is=“@{{currentView}}” app=“@{{@ app }}”> so Laravel doesn’t get confused and try to look for a currentView and app constant.

Also, in order to make things run better, you may want to add keep-alive to the end of the tag. It basically keeps the current view alive in the background instead of deleting it, so if you change currentView back to it Vue doesn’t have to do more work. However, if you want to get updated data anytime the user changes views, it would be very easy to just remove keep-alive so that the ready method is recalled on the component.

Next, we need to make sure that app is added a valid props array for each view. In the module.exports array, go add an array called props if it does not already exist. Next, add a string called ‘app’ to it. Here’s an example:

gistfile1.js

module.exports = {
    props: [
        'app'
    ],
    template: require('./analytics.template.html')
};

Finally, we need to configure our app.js file to have an app variable that points to itself. Add app: {} to its data object. Then, add a createdmethod to the app. In it, say this.app = this; . You’re done! You now have access to the master app in each view.

Why a created method, instead of a ready method, you may ask? Because created is called before the DOM is evaluated, meaning Vue doesn’t even know the components have been instantiated yet. What this all amounts to is that the app variable is ready the moment the views are!

If you need access to the master app in a component, you need to pass it in as a prop. In the component’s module.exports, add ‘app’ to the props array. Then, just like before, whenever you instantiate the component using app=“{{@ app }}” . You can use this for any component.

Part 2: setting data for a particular view from other views or the main app

Since components are only loaded if you have component is set to them at one point in the app’s lifecycle, you can’t reliably access them directly from this.app. Instead, you can set data for them in this.app then pull it in the views. This is easier than it sounds! In app.js, add a new object to the data object, and call it viewData. In it add a name of each view you want to set data for and point it to an empty object. Example:

gistfile1.js

data: {
	currentView: 'edit-view',
	app: {},
	viewData: {
		'edit-view': {},
		'about-view': {}
	}
}

To set that data from a view, just go this.app.viewData[‘about-view’][‘foo’] = ‘bar’; . Then, to get it from the about-view, just go this.app.viewData[‘about-view’][‘foo’]. You could also shorten that by going into the ready method and adding: this.external = this.app.viewData[‘about-view’]. Be sure to add external as an empty object to the data function. Now you can just say this.external.foo to access it. Example:

gistfile1.js

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        this.external = this.app.viewData['analytics-view'];

        //Access variable "downloads" that has been set in about-view
        alert(this.external.downloads);

        //Access the user variable from the main app
        alert(this.app.user.name);
    }
};

This part really is just an example of taking advantage of the global access to the app’s data, nothing more. So take it with a grain of salt, because the real power is in Part 1 and 3.

Part 3: calling view functions

This is undoubtedly the most involved yet most useful part of this tutorial. For example, if I have a search bar in my navbar component, yet want to call a function in the search-results view, there is no good way to do it until now.

We need to be able to call functions if a view has been instantiated and if it hasn’t been. If it has been, we can just say viewModelfunctionName. If it hasn’t, doing so will result in an error. So we need to account for both situations…

Let’s add an setting to each view’s data in app.js called ready. Make it to default to false. Then, in the ready() function in each view, say this.external.ready = true; . Next, add an array called funcs_to_call . If the view has not been instantiated yet, we can add the name of the function to the array so the view can call it later in its ready() method.

To call the function on another view, we can say t_his.app.call(‘search-view’, ‘search’);_ This will call search-view.search() . If the view is instantiated, we can just call it directly. Otherwise, we have a little bit of a harder job.

Let’s deal with the first scenario first. Each view in viewData we need to have ready, funcs_to_call, and model. In each view’s ready() function, we need to set ready to true and model to this. Example:

gistfile1.js

ready: function() {
    this.app.viewData['analytics-view'].ready = true;
    this.app.viewData['analytics-view'].model = this;
}

This allows us to call the functions easily. In app.js, lets add a new method to the methods object called call(). Look at the gist below to see how it works:

gistfile1.js

methods: {
    call: function(view, name) {
    	if(this.viewData[view].ready == true) {
    		this.viewData[view].model[name]();
    	}
    }	
}

Basically, it just tests if the view is ready. If it is, then it calls it using array syntax magic. Let’s do a test of our own: go to analytics-view.js and replace it to the following code:

gistfile1.js

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        
        this.app.viewData['analytics-view'].ready = true;
        this.app.viewData['analytics-view'].model = this;

        this.app.call('analytics-view', 'al');
    },

    methods: {
        al: function() {
            alert('i got called!');
        }
    }
};

When you load the page, it should instantly pop up with ‘i got called!’ as soon as the view is set in currentView!

Now we need to figure out what to do if ready is set to false. First, we just need to add the method name to the array, right? Add an else statement to the call method in app.js to push the function name onto an array:

gistfile1.js

else {
	this.viewData[view].funcs_to_call.push(name);
}

Now we just need to check on ready() functions if there’s anything on the funcs_to_call array and call them if they exist. Add this to the end of the ready() function:

gistfile1.js

var methods = this.app.viewData[view].funcs_to_call;
for(var i in methods)
{
    this[methods[i]]();
}

Now, when the view is loaded, it runs the method that was called!!!

Closing

To close it up, here’s the two files we’ve been working on, completed and done.

gistfile1.html

...
<body id="mainApp">
	<component is="@{{ currentView }}" app="@{{@ app }}" keep-alive></component>
</body>
...
var Vue = require('vue');

var app = new Vue({
	el: 'body#mainApp',

	data: {
		currentView: 'loading-view',
		app: {},
		viewData: {
			'edit-view': {
				ready: false,
				funcs_to_call: [],
				model: {}
			},
			'analytics-view': {
				ready: false,
				funcs_to_call: [],
				model: {}
			}
		}
	},

	components: {
	    // Views
		'analytics-view': require('./views/analytics'),
		'edit-view': require('./views/edit'),
		
		// Components
		'piece': require('./components/Piece')
	},

	methods: {
		call: function(view, name) {
			if(this.viewData[view].ready == true) {
				this.viewData[view].model[name]();
			} else {
				this.viewData[view].funcs_to_call.push(name);
			}
		}	
	},

	ready: function() {
	this.app = this;
		
        this.app.call('analytics-view', 'al'); //Test caling the al function
	}
});

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        var view = 'analytics-view';
        this.app.viewData[view].ready = true;
        this.app.viewData[view].model = this;

        var methods = this.app.viewData[view].funcs_to_call;
        for(var i in methods)
        {
            this[methods[i]]();
        }
    },

    methods: {
        al: function() {
            alert('i got called!');
        }
    }
};

You can use these two files as templates when using this design architecture in the future! I know I will.

To the left is the file structure I have for the Laravue repo. There are a couple of differences though: 1) I have a home view not an analytics view, and 2) I use coffeescript instead of vanilla JS. These aren’t biggies though!

What now?

In the future, I’ll no doubt be optimizing this and making it better. If you have any suggestions, just leave a response below! Your input is essential because the only way this can thrive is through community involvement.

There’s a few important things missing from basic Vue and Laravel integrations.
In the future I may make a way to combine the ready and model variables in viewData functionally into one. But the additional code required may be more than I actually save, so I’m not sure on this one.

Something else I feel like this needs is a CLI. How about maybe php artisan make:view analytics and it will add all this boilerplate? Just an idea. Let me know if you’re interested in doing anything like this, because I think might be a bit beyond me.

For more info on how calling functions on objects using a string check out http://stackoverflow.com/questions/9854995/javascript-dynamically-invoke-object-method-from-string . It helped me out allot.

UPDATE: allow for arguments passed into view methods!

// app.js call method.
call: function(view, name, args = []) {
	if(this.viewData[view].ready == true) {
		this.viewData[view].model[name](args);
	} else {
		this.viewData[view].funcs_to_call.push({name: name, args: args});
	}
}

// updated for loop inside each view
for(var i in methods)
{
    this[methods[i].name](methods[i].args);
}

Now you can do this.app.call(‘foo’, ‘bar’, [‘arg1’, ‘arg2’, ‘arg3’]) and it will pass the array into the method. To retrieve them in a method just do args[0], args[1], args[2].

If you’re using coffeescript, it’s even easier! You can just use the … syntax for arguments. Yes, it’s technically possible without coffee, but it would look pretty ugly… Anyways here’s the coffeescript example:

call: (view, funcName, args...) ->
	if   @views[view].ready then @views[view]['model'][funcName].apply(null, args)
	else @views[view].funcs.push({name: funcName, args: args}) 

Any input is highly welcome!

30s ad

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

Build A Web App with VueJS, Spring Framework and MongoDB

Vue JS 2.0 - Mastering Web Apps

Vue JS 2: From Beginner to Professional (includes Vuex)

The Ultimate Vue JS 2 Developers Course

ATTENTION: Vue’s Vue-Router sub-project has come out with an update that fills most of the functionality in this article.

You can now use this.$router.app at any time inside a component to access the main component. Check it out here: http://router.vuejs.org/api/properties.html. And, Vue now has events built in. So I can say this.$dispatch(‘foo’) to run methods on other components. Haven’t played with it much but it looks really cool. http://vuejs.org/api/instance-methods.html#Events.

Vue JS 2 - The Complete Guide (incl. Vuex)

That said the rest of this article teaches important skills in front-end applications and is still a good read.

Do you use Laravel PHP and Vue JS? Do you wish there was a better way to structure your applications you build? This tutorial is just for you! In it, we set up simple two-way communication between the different components of your front-end app.

Would you like to get the results of this tutorial without reading through it? Check out Laravue, the brand new boilerplate repo for starting off!

This tutorial is inspired by a video by Laracasts that sets up the basic app architecture. You don’t have to watch it, but here’s a link if you’re curious: https://laracasts.com/series/learning-vuejs/episodes/9

To sum up the result of the video, you have two directories and one main file: resources/assets/js/components/ , resources/assets/js/views/ , and resources/assets/js/app.js. In the components folder you have two files for each component: Component.js and component.template.html . Component.js is the model while component.template.html is the view. You do the same for views/, with about.js and about.template.html . You export all of this using module.exports to package the functioanlity. If you use coffeescript, name them Component.coffee and view.coffee. Simple stuff.

In app.js, you include all of these into the components object of your main View-Model. The views are named like ‘name-view’ and the components like ‘name’. You also have a currentView variable in the data object, which represents the view that should be displayed. To display all that, you use .

Alright, now that you’ve finished that, you should have a great architecture to start off with. You have your main app.js file (which will now be called app), individual views, and components. However, there’s a few things that are missing:

  1. a standard way to communicate from app -> view
  2. a standard way to communicate from view -> app
  3. a standard way to communicate from components -> view
  4. a standard way to communicate from components -> app

There’s a few important things missing from basic Vue and Laravel integrations.
Why would we want these features? Suppose we want to change the currentView from within a view. Right now, there’s no easy way to do that. Using my setup, we can just run this.app.currentView = ‘awesome-view’; .

Another example is if you want to have one user object that can be accessed application-wide. Just add it to the data object of your main app and it can be accessed from views using this.app.user !

To make components easier to understand, let’s think of vue components just like classes in PHP: each view and component is like a class that is then instantiated into objects through ’s or a .

There’s a few important things missing from basic Vue and Laravel integrations.
A quick note on components: when I say it sometimes I mean a component in general, other times I mean non-view, new DOM element components. Like the ones in the components/ folder. It should be pretty clear when I’m talking about which one, contact me if you have any ways I can make it more clear.

Now that we understand the basics of components, let’s try and get a (even) better architecture for setting them up and addressing the problems above. Let’s think about Laravel: there’s a main IoC container, which is accessed through the App facade, which contains various smaller classes called Service Containers. This is starting to sound really similiar to the front end: we have a main app that contains our smaller components & views (which are really just glorified components).

In Laravel, each service provider can access the IoC container through $this->app. What if we could do the same thing in views, saying this.app? Well it turns out you can!

There’s a few important things missing from basic Vue and Laravel integrations.
With this technique, the app variable is passed easily into each view using two-way binding. This means you can change the master application from a view, and the changes are reflected application-wide on the app view-model itself, every view, and every component.

Part 1: using this.app in each view

There’s a few important things missing from basic Vue and Laravel integrations.
First, let’s go to the main HTML file that contains the <component is=“{{ currentView}}”>. We need to add the app as a prop to that. So, change that to <component is=“{{currentView}}” app=“{{@ app }}”> .

If you’ve used Vue before you’ll be familiar with the mustache syntax, but the {{@ might leave you a little confused. Basically, it says pass the app into the component, but if the component makes a change to the app, reflect it in the scope that contains the component too. In other words it enforces two-way binding. If you didn’t have the @, you could do something to the app in the view and then nothing would happen anywhere else!

Of course, if you are in a .blade.php file, change that to <component is=“@{{currentView}}” app=“@{{@ app }}”> so Laravel doesn’t get confused and try to look for a currentView and app constant.

Also, in order to make things run better, you may want to add keep-alive to the end of the tag. It basically keeps the current view alive in the background instead of deleting it, so if you change currentView back to it Vue doesn’t have to do more work. However, if you want to get updated data anytime the user changes views, it would be very easy to just remove keep-alive so that the ready method is recalled on the component.

Next, we need to make sure that app is added a valid props array for each view. In the module.exports array, go add an array called props if it does not already exist. Next, add a string called ‘app’ to it. Here’s an example:

gistfile1.js

module.exports = {
    props: [
        'app'
    ],
    template: require('./analytics.template.html')
};

Finally, we need to configure our app.js file to have an app variable that points to itself. Add app: {} to its data object. Then, add a createdmethod to the app. In it, say this.app = this; . You’re done! You now have access to the master app in each view.

Why a created method, instead of a ready method, you may ask? Because created is called before the DOM is evaluated, meaning Vue doesn’t even know the components have been instantiated yet. What this all amounts to is that the app variable is ready the moment the views are!

If you need access to the master app in a component, you need to pass it in as a prop. In the component’s module.exports, add ‘app’ to the props array. Then, just like before, whenever you instantiate the component using app=“{{@ app }}” . You can use this for any component.

Part 2: setting data for a particular view from other views or the main app

Since components are only loaded if you have component is set to them at one point in the app’s lifecycle, you can’t reliably access them directly from this.app. Instead, you can set data for them in this.app then pull it in the views. This is easier than it sounds! In app.js, add a new object to the data object, and call it viewData. In it add a name of each view you want to set data for and point it to an empty object. Example:

gistfile1.js

data: {
	currentView: 'edit-view',
	app: {},
	viewData: {
		'edit-view': {},
		'about-view': {}
	}
}

To set that data from a view, just go this.app.viewData[‘about-view’][‘foo’] = ‘bar’; . Then, to get it from the about-view, just go this.app.viewData[‘about-view’][‘foo’]. You could also shorten that by going into the ready method and adding: this.external = this.app.viewData[‘about-view’]. Be sure to add external as an empty object to the data function. Now you can just say this.external.foo to access it. Example:

gistfile1.js

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        this.external = this.app.viewData['analytics-view'];

        //Access variable "downloads" that has been set in about-view
        alert(this.external.downloads);

        //Access the user variable from the main app
        alert(this.app.user.name);
    }
};

This part really is just an example of taking advantage of the global access to the app’s data, nothing more. So take it with a grain of salt, because the real power is in Part 1 and 3.

Part 3: calling view functions

This is undoubtedly the most involved yet most useful part of this tutorial. For example, if I have a search bar in my navbar component, yet want to call a function in the search-results view, there is no good way to do it until now.

We need to be able to call functions if a view has been instantiated and if it hasn’t been. If it has been, we can just say viewModelfunctionName. If it hasn’t, doing so will result in an error. So we need to account for both situations…

Let’s add an setting to each view’s data in app.js called ready. Make it to default to false. Then, in the ready() function in each view, say this.external.ready = true; . Next, add an array called funcs_to_call . If the view has not been instantiated yet, we can add the name of the function to the array so the view can call it later in its ready() method.

To call the function on another view, we can say t_his.app.call(‘search-view’, ‘search’);_ This will call search-view.search() . If the view is instantiated, we can just call it directly. Otherwise, we have a little bit of a harder job.

Let’s deal with the first scenario first. Each view in viewData we need to have ready, funcs_to_call, and model. In each view’s ready() function, we need to set ready to true and model to this. Example:

gistfile1.js

ready: function() {
    this.app.viewData['analytics-view'].ready = true;
    this.app.viewData['analytics-view'].model = this;
}

This allows us to call the functions easily. In app.js, lets add a new method to the methods object called call(). Look at the gist below to see how it works:

gistfile1.js

methods: {
    call: function(view, name) {
    	if(this.viewData[view].ready == true) {
    		this.viewData[view].model[name]();
    	}
    }	
}

Basically, it just tests if the view is ready. If it is, then it calls it using array syntax magic. Let’s do a test of our own: go to analytics-view.js and replace it to the following code:

gistfile1.js

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        
        this.app.viewData['analytics-view'].ready = true;
        this.app.viewData['analytics-view'].model = this;

        this.app.call('analytics-view', 'al');
    },

    methods: {
        al: function() {
            alert('i got called!');
        }
    }
};

When you load the page, it should instantly pop up with ‘i got called!’ as soon as the view is set in currentView!

Now we need to figure out what to do if ready is set to false. First, we just need to add the method name to the array, right? Add an else statement to the call method in app.js to push the function name onto an array:

gistfile1.js

else {
	this.viewData[view].funcs_to_call.push(name);
}

Now we just need to check on ready() functions if there’s anything on the funcs_to_call array and call them if they exist. Add this to the end of the ready() function:

gistfile1.js

var methods = this.app.viewData[view].funcs_to_call;
for(var i in methods)
{
    this[methods[i]]();
}

Now, when the view is loaded, it runs the method that was called!!!

Closing

To close it up, here’s the two files we’ve been working on, completed and done.

gistfile1.html

...
<body id="mainApp">
	<component is="@{{ currentView }}" app="@{{@ app }}" keep-alive></component>
</body>
...
var Vue = require('vue');

var app = new Vue({
	el: 'body#mainApp',

	data: {
		currentView: 'loading-view',
		app: {},
		viewData: {
			'edit-view': {
				ready: false,
				funcs_to_call: [],
				model: {}
			},
			'analytics-view': {
				ready: false,
				funcs_to_call: [],
				model: {}
			}
		}
	},

	components: {
	    // Views
		'analytics-view': require('./views/analytics'),
		'edit-view': require('./views/edit'),
		
		// Components
		'piece': require('./components/Piece')
	},

	methods: {
		call: function(view, name) {
			if(this.viewData[view].ready == true) {
				this.viewData[view].model[name]();
			} else {
				this.viewData[view].funcs_to_call.push(name);
			}
		}	
	},

	ready: function() {
	this.app = this;
		
        this.app.call('analytics-view', 'al'); //Test caling the al function
	}
});

module.exports = {
    data: function() {
        return {
            external: {}
        };
    },

    props: [
        'app'
    ],

    template: require('./analytics.template.html'),

    ready: function() {
        var view = 'analytics-view';
        this.app.viewData[view].ready = true;
        this.app.viewData[view].model = this;

        var methods = this.app.viewData[view].funcs_to_call;
        for(var i in methods)
        {
            this[methods[i]]();
        }
    },

    methods: {
        al: function() {
            alert('i got called!');
        }
    }
};

You can use these two files as templates when using this design architecture in the future! I know I will.

To the left is the file structure I have for the Laravue repo. There are a couple of differences though: 1) I have a home view not an analytics view, and 2) I use coffeescript instead of vanilla JS. These aren’t biggies though!

What now?

In the future, I’ll no doubt be optimizing this and making it better. If you have any suggestions, just leave a response below! Your input is essential because the only way this can thrive is through community involvement.

There’s a few important things missing from basic Vue and Laravel integrations.
In the future I may make a way to combine the ready and model variables in viewData functionally into one. But the additional code required may be more than I actually save, so I’m not sure on this one.

Something else I feel like this needs is a CLI. How about maybe php artisan make:view analytics and it will add all this boilerplate? Just an idea. Let me know if you’re interested in doing anything like this, because I think might be a bit beyond me.

For more info on how calling functions on objects using a string check out http://stackoverflow.com/questions/9854995/javascript-dynamically-invoke-object-method-from-string . It helped me out allot.

UPDATE: allow for arguments passed into view methods!

// app.js call method.
call: function(view, name, args = []) {
	if(this.viewData[view].ready == true) {
		this.viewData[view].model[name](args);
	} else {
		this.viewData[view].funcs_to_call.push({name: name, args: args});
	}
}

// updated for loop inside each view
for(var i in methods)
{
    this[methods[i].name](methods[i].args);
}

Now you can do this.app.call(‘foo’, ‘bar’, [‘arg1’, ‘arg2’, ‘arg3’]) and it will pass the array into the method. To retrieve them in a method just do args[0], args[1], args[2].

If you’re using coffeescript, it’s even easier! You can just use the … syntax for arguments. Yes, it’s technically possible without coffee, but it would look pretty ugly… Anyways here’s the coffeescript example:

call: (view, funcName, args...) ->
	if   @views[view].ready then @views[view]['model'][funcName].apply(null, args)
	else @views[view].funcs.push({name: funcName, args: args}) 

Any input is highly welcome!

30s ad

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

Build A Web App with VueJS, Spring Framework and MongoDB

Vue JS 2.0 - Mastering Web Apps

Vue JS 2: From Beginner to Professional (includes Vuex)

The Ultimate Vue JS 2 Developers Course

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.

How To Publish Your Vue.js Component On NPM - Vue.js Developers

How To Publish Your Vue.js Component On NPM - Vue.js Developers

How To Publish Your Vue.js Component On NPM. You’ve made an awesome component with Vue.js that you think other developers could use in their projects. How can you share it with them?

How to build Vue.js JWT Authentication with Vuex and Vue Router

How to build Vue.js JWT Authentication with Vuex and Vue Router

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication. I will show you:

  • JWT Authentication Flow for User Signup & User Login
  • Project Structure for Vue.js Authentication with Vuex & Vue Router
  • How to define Vuex Authentication module
  • Creating Vue Authentication Components with Vuex Store & VeeValidate
  • Vue Components for accessing protected Resources
  • How to add a dynamic Navigation Bar to Vue App

Let’s explore together.

Contents

Overview of Vue JWT Authentication example

We will build a Vue application in that:

  • There are Login/Logout, Signup pages.
  • Form data will be validated by front-end before being sent to back-end.
  • Depending on User’s roles (admin, moderator, user), Navigation Bar changes its items automatically.

Screenshots

– Signup Page:

– Login Page & Profile Page (for successful Login):

– Navigation Bar for Admin account:

Demo

This is full Vue JWT Authentication App demo (with form validation, check signup username/email duplicates, test authorization with 3 roles: Admin, Moderator, User). In the video, we use Spring Boot for back-end REST APIs.

Flow for User Registration and User Login

For JWT Authentication, we’re gonna call 2 endpoints:

  • POST api/auth/signup for User Registration
  • POST api/auth/signin for User Login

You can take a look at following flow to have an overview of Requests and Responses Vue Client will make or receive.

Vue Client must add a JWT to HTTP Authorization Header before sending request to protected resources.

Vue App Component Diagram with Vuex & Vue Router

Now look at the diagram below.

Let’s think about it.

– The App component is a container with Router. It gets app state from Vuex store/auth. Then the navbar now can display based on the state. App component also passes state to its child components.

Login & Register components have form for submission data (with support of vee-validate). We call Vuex store dispatch() function to make login/register actions.

– Our Vuex actions call auth.service methods which use axios to make HTTP requests. We also store or get JWT from Browser Local Storage inside these methods.

Home component is public for all visitor.

Profile component get user data from its parent component and display user information.

BoardUser, BoardModerator, BoardAdmin components will be displayed by Vuex state user.roles. In these components, we use user.service to get protected resources from API.

user.service uses auth-header() helper function to add JWT to HTTP Authorization header. auth-header() returns an object containing the JWT of the currently logged in user from Local Storage.

Technology

We will use these modules:

  • vue: 2.6.10
  • vue-router: 3.0.3
  • vuex: 3.0.1
  • axios: 0.19.0
  • vee-validate: 2.2.15
  • bootstrap: 4.3.1
  • vue-fontawesome: 0.1.7
Project Structure

This is folders & files structure for our Vue application:

With the explaination in diagram above, you can understand the project structure easily.

Setup Vue App modules

Run following command to install neccessary modules:

npm install vue-router
npm install vuex
npm install [email protected]
npm install axios
npm install bootstrap jquery popper.js
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome

After the installation is done, you can check dependencies in package.json file.

"dependencies": {
  "@fortawesome/fontawesome-svg-core": "^1.2.25",
  "@fortawesome/free-solid-svg-icons": "^5.11.2",
  "@fortawesome/vue-fontawesome": "^0.1.7",
  "axios": "^0.19.0",
  "bootstrap": "^4.3.1",
  "core-js": "^2.6.5",
  "jquery": "^3.4.1",
  "popper.js": "^1.15.0",
  "vee-validate": "^2.2.15",
  "vue": "^2.6.10",
  "vue-router": "^3.0.3",
  "vuex": "^3.0.1"
},

Open src/main.js, add code below:

import Vue from 'vue';
import App from './App.vue';
import { router } from './router';
import store from './store';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import VeeValidate from 'vee-validate';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import {
  faHome,
  faUser,
  faUserPlus,
  faSignInAlt,
  faSignOutAlt
} from '@fortawesome/free-solid-svg-icons';

library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt);

Vue.config.productionTip = false;

Vue.use(VeeValidate);
Vue.component('font-awesome-icon', FontAwesomeIcon);

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

You can see that we import and apply in Vue object:
store for Vuex (implemented later in src/store)
router for Vue Router (implemented later in src/router.js)
bootstrap with CSS
vee-validate
vue-fontawesome for icons (used later in nav)

Create Services

We create two services in src/services folder:


services

auth-header.js

auth.service.js (Authentication service)

user.service.js (Data service)


Authentication service

The service provides three important methods with the help of axios for HTTP requests & reponses:

  • login(): POST {username, password} & save JWT to Local Storage
  • logout(): remove JWT from Local Storage
  • register(): POST {username, email, password}
import axios from 'axios';

const API_URL = 'http://localhost:8080/api/auth/';

class AuthService {
  login(user) {
    return axios
      .post(API_URL + 'signin', {
        username: user.username,
        password: user.password
      })
      .then(this.handleResponse)
      .then(response => {
        if (response.data.accessToken) {
          localStorage.setItem('user', JSON.stringify(response.data));
        }

        return response.data;
      });
  }

  logout() {
    localStorage.removeItem('user');
  }

  register(user) {
    return axios.post(API_URL + 'signup', {
      username: user.username,
      email: user.email,
      password: user.password
    });
  }

  handleResponse(response) {
    if (response.status === 401) {
      this.logout();
      location.reload(true);

      const error = response.data && response.data.message;
      return Promise.reject(error);
    }

    return Promise.resolve(response);
  }
}

export default new AuthService();

If login request returns 401 status (Unauthorized), that means, JWT was expired or no longer valid, we will logout the user (remove JWT from Local Storage).

Data service

We also have methods for retrieving data from server. In the case we access protected resources, the HTTP request needs Authorization header.

Let’s create a helper function called authHeader() inside auth-header.js:

export default function authHeader() {
  let user = JSON.parse(localStorage.getItem('user'));

  if (user && user.accessToken) {
    return { Authorization: 'Bearer ' + user.accessToken };
  } else {
    return {};
  }
}

It checks Local Storage for user item.
If there is a logged in user with accessToken (JWT), return HTTP Authorization header. Otherwise, return an empty object.

Now we define a service for accessing data in user.service.js:

import axios from 'axios';
import authHeader from './auth-header';

const API_URL = 'http://localhost:8080/api/test/';

class UserService {
  getPublicContent() {
    return axios.get(API_URL + 'all');
  }

  getUserBoard() {
    return axios.get(API_URL + 'user', { headers: authHeader() });
  }

  getModeratorBoard() {
    return axios.get(API_URL + 'mod', { headers: authHeader() });
  }

  getAdminBoard() {
    return axios.get(API_URL + 'admin', { headers: authHeader() });
  }
}

export default new UserService();

You can see that we add a HTTP header with the help of authHeader() function when requesting authorized resource.

Define Vuex Authentication module

We put Vuex module for authentication in src/store folder.


store

auth.module.js (authentication module)

index.js (Vuex Store that contains also modules)


Now open index.js file, import auth.module to main Vuex Store here.

import Vue from 'vue';
import Vuex from 'vuex';

import { auth } from './auth.module';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    auth
  }
});

Then we start to define Vuex Authentication module that contains:

  • state: { status, user }
  • actions: { login, logout, register }
  • mutations: { loginSuccess, loginFailure, logout, registerSuccess, registerFailure }

We use AuthService which is defined above to make authentication requests.

auth.module.js

import AuthService from '../services/auth.service';

const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
  ? { status: { loggedIn: true }, user }
  : { status: {}, user: null };

export const auth = {
  namespaced: true,
  state: initialState,
  actions: {
    login({ commit }, user) {
      return AuthService.login(user).then(
        user => {
          commit('loginSuccess', user);
          return Promise.resolve(user);
        },
        error => {
          commit('loginFailure');
          return Promise.reject(error.response.data);
        }
      );
    },
    logout({ commit }) {
      AuthService.logout();
      commit('logout');
    },
    register({ commit }, user) {
      return AuthService.register(user).then(
        response => {
          commit('registerSuccess');
          return Promise.resolve(response.data);
        },
        error => {
          commit('registerFailure');
          return Promise.reject(error.response.data);
        }
      );
    }
  },
  mutations: {
    loginSuccess(state, user) {
      state.status = { loggedIn: true };
      state.user = user;
    },
    loginFailure(state) {
      state.status = {};
      state.user = null;
    },
    logout(state) {
      state.status = {};
      state.user = null;
    },
    registerSuccess(state) {
      state.status = {};
    },
    registerFailure(state) {
      state.status = {};
    }
  }
};

You can find more details about Vuex at Vuex Guide.

Create Vue Authentication Components

Define User model

To make code clear and easy to read, we define the User model first.
Under src/models folder, create user.js like this.

export default class User {
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }
}

Let’s continue with Authentication Components.
Instead of using axios or AuthService directly, these Components should work with Vuex Store:
– getting status with this.$store.state.auth
– making request by dispatching an action: this.$store.dispatch()


views

Login.vue

Register.vue

Profile.vue


Vue Login Page

In src/views folder, create Login.vue file with following code:

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="username">Username</label>
          <input
            type="text"
            class="form-control"
            name="username"
            v-model="user.username"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('username')"
          >Username is required!</div>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input
            type="password"
            class="form-control"
            name="password"
            v-model="user.password"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('password')"
          >Password is required!</div>
        </div>
        <div class="form-group">
          <button class="btn btn-primary btn-block" :disabled="loading">
            <span class="spinner-border spinner-border-sm" v-show="loading"></span>
            <span>Login</span>
          </button>
        </div>
        <div class="form-group">
          <div class="alert alert-danger" role="alert" v-if="message">{{message}}</div>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'login',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', ''),
      loading: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleLogin() {
      this.loading = true;
      this.$validator.validateAll();

      if (this.errors.any()) {
        this.loading = false;
        return;
      }

      if (this.user.username && this.user.password) {
        this.$store.dispatch('auth/login', this.user).then(
          () => {
            this.$router.push('/profile');
          },
          error => {
            this.loading = false;
            this.message = error.message;
          }
        );
      }
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

This page has a Form with username & password. We use [VeeValidate 2.x](http://<a href=) to validate input before submitting the form. If there is an invalid field, we show the error message.

We check user logged in status using Vuex Store: this.$store.state.auth.status.loggedIn. If the status is true, we use Vue Router to direct user to Profile Page:

created() {
  if (this.loggedIn) {
    this.$router.push('/profile');
  }
},

In the handleLogin() function, we dispatch 'auth/login' Action to Vuex Store. If the login is successful, go to Profile Page, otherwise, show error message.

Vue Register Page

This page is similar to Login Page.

For form validation, we have some more details:

  • username: required|min:3|max:20
  • email: required|email|max:50
  • password: required|min:6|max:40

For form submission, we dispatch 'auth/register' Vuex Action.

src/views/Register.vue

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleRegister">
        <div v-if="!successful">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              type="text"
              class="form-control"
              name="username"
              v-model="user.username"
              v-validate="'required|min:3|max:20'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('username')"
            >{{errors.first('username')}}</div>
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              class="form-control"
              name="email"
              v-model="user.email"
              v-validate="'required|email|max:50'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('email')"
            >{{errors.first('email')}}</div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              class="form-control"
              name="password"
              v-model="user.password"
              v-validate="'required|min:6|max:40'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('password')"
            >{{errors.first('password')}}</div>
          </div>
          <div class="form-group">
            <button class="btn btn-primary btn-block">Sign Up</button>
          </div>
        </div>
      </form>

      <div
        class="alert"
        :class="successful ? 'alert-success' : 'alert-danger'"
        v-if="message"
      >{{message}}</div>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'register',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', '', ''),
      submitted: false,
      successful: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleRegister() {
      this.message = '';
      this.submitted = true;
      this.$validator.validate().then(valid => {
        if (valid) {
          this.$store.dispatch('auth/register', this.user).then(
            data => {
              this.message = data.message;
              this.successful = true;
            },
            error => {
              this.message = error.message;
              this.successful = false;
            }
          );
        }
      });
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

Profile Page

This page gets current User from Vuex Store and show information. If the User is not logged in, it directs to Login Page.

src/views/Profile.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>
        <strong>{{currentUser.username}}</strong> Profile
      </h3>
    </header>
    <p>
      <strong>Token:</strong>
      {{currentUser.accessToken.substring(0, 20)}} ... {{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}}
    </p>
    <p>
      <strong>Id:</strong>
      {{currentUser.id}}
    </p>
    <p>
      <strong>Email:</strong>
      {{currentUser.email}}
    </p>
    <strong>Authorities:</strong>
    <ul>
      <li v-for="(role,index) in currentUser.roles" :key="index">{{role}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'profile',
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    }
  },
  mounted() {
    if (!this.currentUser) {
      this.$router.push('/login');
    }
  }
};
</script>

Create Vue Components for accessing Resources

These components will use UserService to request data.


views

Home.vue

BoardAdmin.vue

BoardModerator.vue

BoardUser.vue


Home Page

This is a public page.

src/views/Home.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'home',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getPublicContent().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Role-based Pages

We have 3 pages for accessing protected data:

  • BoardUser page calls UserService.getUserBoard()
  • BoardModerator page calls UserService.getModeratorBoard()
  • BoardAdmin page calls UserService.getAdminBoard()

This is an example, other Page are similar to this Page.

src/views/BoardUser.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'user',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getUserBoard().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Define Routes for Vue Router

Now we define all routes for our Vue Application.

src/router.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';

Vue.use(Router);

export const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/home',
      component: Home
    },
    {
      path: '/login',
      component: Login
    },
    {
      path: '/register',
      component: Register
    },
    {
      path: '/profile',
      name: 'profile',
      // lazy-loaded
      component: () => import('./views/Profile.vue')
    },
    {
      path: '/admin',
      name: 'admin',
      // lazy-loaded
      component: () => import('./views/BoardAdmin.vue')
    },
    {
      path: '/mod',
      name: 'moderator',
      // lazy-loaded
      component: () => import('./views/BoardModerator.vue')
    },
    {
      path: '/user',
      name: 'user',
      // lazy-loaded
      component: () => import('./views/BoardUser.vue')
    }
  ]
});

Add Navigation Bar to Vue App

This is the root container for our application that contains navigation bar. We will add router-view here.

src/App.vue

<template>
  <div id="app">
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a href="#" class="navbar-brand">bezKoder</a>
      <div class="navbar-nav mr-auto">
        <li class="nav-item">
          <a href="/home" class="nav-link">
            <font-awesome-icon icon="home" /> Home
          </a>
        </li>
        <li class="nav-item" v-if="showAdminBoard">
          <a href="/admin" class="nav-link">Admin Board</a>
        </li>
        <li class="nav-item" v-if="showModeratorBoard">
          <a href="/mod" class="nav-link">Moderator Board</a>
        </li>
        <li class="nav-item">
          <a href="/user" class="nav-link" v-if="currentUser">User</a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="!currentUser">
        <li class="nav-item">
          <a href="/register" class="nav-link">
            <font-awesome-icon icon="user-plus" /> Sign Up
          </a>
        </li>
        <li class="nav-item">
          <a href="/login" class="nav-link">
            <font-awesome-icon icon="sign-in-alt" /> Login
          </a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="currentUser">
        <li class="nav-item">
          <a href="/profile" class="nav-link">
            <font-awesome-icon icon="user" />
            {{currentUser.username}}
          </a>
        </li>
        <li class="nav-item">
          <a href class="nav-link" @click="logOut">
            <font-awesome-icon icon="sign-out-alt" /> LogOut
          </a>
        </li>
      </div>
    </nav>

    <div class="container">
      <router-view />
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    },
    showAdminBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_ADMIN');
      }

      return false;
    },
    showModeratorBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_MODERATOR');
      }

      return false;
    }
  },
  methods: {
    logOut() {
      this.$store.dispatch('auth/logout');
      this.$router.push('/login');
    }
  }
};
</script>

Our navbar looks more professional when using font-awesome-icon.
We also make the navbar dynamically change by current User’s roles which are retrieved from Vuex Store state.

Handle Unauthorized Access

If you want to check Authorized status everytime a navigating action is trigger, just add router.beforeEach() at the end of src/router.js like this:

router.beforeEach((to, from, next) => {
  const publicPages = ['/login', '/home'];
  const authRequired = !publicPages.includes(to.path);
  const loggedIn = localStorage.getItem('user');

  // try to access a restricted page + not logged in
  if (authRequired && !loggedIn) {
    return next('/login');
  }

  next();
});

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our Vue application, and apply it in your project at ease. Now you can build a front-end app that supports JWT Authentication with Vue.js, Vuex and Vue Router.

Happy learning, see you again!