How to include Vue Components in Laravel without losing Blade Engine
This article is very old. Today, if you are using Vue and Laravel, I would recommend just using https://inertiajs.com/. This means you have for each page a vue file and no blades anymore (except the layout file app.blade.php).
If you don’t want to use Inertia.js for any reason, the article may still be useful. Although it’s pretty old. Instead of mix and Vue 2.0, it’s now recommended to use Vite and Vue 3.0.
Before continue reading, please be aware, with Inertia.js you can:
- Define routes still in Laravel
- You can use localization packages such as https://github.com/mcamara/laravel-localization, however, you need to create your own __(..) function on the front-end. There are mayn tutorials out there.
- @Foreach, @component @inject etc. have their own methods in VUE
- Inertia comes with a great form helper https://medium.com/@iwasherefirst2/creating-and-editing-forms-without-duplication-in-laravel-using-inertia-vue-e4212631eec3
- Easy components available for multiselct https://iwasherefirst2.medium.com/vue-select-tipps-with-inertia-laravel-e58deb10fe28
- Easy to do file-upload https://iwasherefirst2.medium.com/upload-files-and-other-input-together-with-inertia-vue-laravel-1a776aa72fd1
The Setup for Vue in Laravel
The first starting point is to have a file, lets call it resources/assets/js/app.js
where you load your Vue application. It could look like this:
import Vue from 'vue'import DragNDrop from './components/UploadDragnDrop.vue';
import Trombowyg from './components/Trombowyg.vue';Vue.component('nie-upload', DragNDrop);
Vue.component('nie-table', Trombowyg);const app = new Vue({
el: '#main-content',
});
Now inside your Webpack.mix.js
file you would need to specify this file. It could look like this:
mix.js('resources/assets/js/app.js', 'public/js');if (mix.inProduction() || true) {
mix.version()
}
Finally, after you compiled it using npm install
and npm run watch
you should reference your blade like this:
@extends('layouts.master')@push('scripts')
<script type="text/javascript" src="/js/all.js" defer> </script>
@endpush@section('content')
<h1>Hallo!</h1><div class="container" id="main-content">
<section>
<p>How are you?</p>
</section>
</div>
@endsection
Well done, you got the setup! 💃 Let's discuss now the different ways we can include Vue components.
Bad idea: The main script
Imagine we want to have a button and a table. The table is prefilled with some data given by our controller, and when we click on the button we add a row.
@extends('layouts.master')@push('scripts')
<script type="text/javascript" src="/js/all.js" defer> </script>
@endpush@section('content')
<h1>Hallo!</h1><div class="container" id="main-content">
<section>
<b-button type="is-success is-warning"
icon-left="icon-mail-alt" icon-pack="icon-"
type="is-info"
size="is-medium"
@click="addUser()">
@lang('Add User')
</b-button> <nie-table :contentrows =" {{ $data }}"></nie-table>
</section>
</div>@endsection
Where could we specify the method `addUser` now? One may think having it in resources/assets/js/app.js
is a good idea, but it won’t work, because how would you possibly access the table data that you passed from your controller to your table?
import Vue from 'vue'import DragNDrop from './components/UploadDragnDrop.vue';
import Trombowyg from './components/Trombowyg.vue';Vue.component('nie-upload', DragNDrop);
Vue.component('nie-table', Trombowyg);const app = new Vue({
el: '#main-content',
methods: addUser() {
// How to access data of nie-table here?
}
});
And even if you didn’t had such a table, it would not be a good idea to put any methods in your resources/assets/js/app.js
file, because you would need a separate app.js
file for every blade that uses a vue component plus another entry in your webpack file - that is just not practical.
Good Idea: Own Component
A better idea would be to place it into its own component TableWrapper.vue
:
<template>
<div>
<b-button type="is-success is-warning"
icon-left="icon-mail-alt" icon-pack="icon-"
type="is-info"
size="is-medium"
@click="addUser()">
@lang('Add User')
</b-button> <nie-table :contentrows ="contentrows"></nie-table>
</div>
</template>
<script>
export default {
name: "addUser",
props: ['initrows', 'label'],
data() {
return {
contentrows: this.initrows,
};
},
methods: {
addUser(){
this.contentrows.push({name: 'Max Mustermann'});
}
}
</script>
Note: 👉 We added the property directly to a data
field, so we can customize it. Usually, in Vue the parents pass data to the components, and the components emit changes. However, here we have no parent who passed the data, it's just passed by the blade from the controller, so we need to make this little trick here.
Register only the component in our universal app.js
file. Do not add methods here:
import Vue from 'vue'import DragNDrop from './components/UploadDragnDrop.vue';
import Trombowyg from './components/Trombowyg.vue';
import TableWrapper from './components/Nie/TableWrapper.vue';Vue.component('nie-upload', DragNDrop);
Vue.component('nie-table', Trombowyg);
Vue.component('nie-table-wrapper', TableWrapper);const app = new Vue({
el: '#main-content',
});
And finally, use the Vue table component like this in your blade file:
@extends('layouts.master')@push('scripts')
<script type="text/javascript" src="/js/all.js" defer> </script>
@endpush@section('content')
<h1>Hallo!</h1><div class="container" id="main-content">
<section>
<nie-table-wrapper :initrows="{{$data}}" label="@lang('Add Users')">
</nie-table-wrapper>
</section>
</div>
@endsection
How to use Blade and Components?
As you may have noticed, we needed to pass the translation from the blade into the component. This can lead to a lot of props, if you have a lot of translated strings. Also moving everything into its own Vue component means also, that we cannot make use of Laravels Blade Engine. An alternative to getting the best of both worlds, Vue component, and Laravels Blade Engine, we can use the scope approach:
<template>
<div>
<scope :addUser="addUser" :contentrows="contentrows"></scope>
</div>
</template>
<script>
export default {
name: "addUser",
props: ['initrows'],
data() {
return {
contentrows: this.initrows,
};
},
methods: {
addUser(){
this.contentrows.push({name: 'Max Mustermann'});
}
}
</script>
And now we do not need to pass the translation label to the component through props anymore:
@extends('layouts.master')@push('scripts')
<script type="text/javascript" src="/js/all.js" defer> </script>
@endpush@section('content')
<h1>Hallo!</h1><div class="container" id="main-content">
<section>
<nie-table-wrapper :initrows="{{$data}}">
<template slot-scope="parent">
<b-button type="is-success is-warning"
icon-left="icon-mail-alt" icon-pack="icon-"
type="is-info"
size="is-medium"
@click="parent.addUser()">
@lang('Add User')
</b-button><nie-table :contentrows ="parent.contentrows">
</nie-table>
</template>
</nie-table-wrapper>
</section>
</div>
@endsection
Note:
👉 We needed to surround the content with the template-tag to allow the subcomponents could access the methods and data of the parent component.
👉 We needed to pass the data and method from the parent through the scope