Create a single-page Vue application from Laravel

Keywords: PHP Vue Database Laravel

The article was forwarded from the professional Laravel developer community with the original link: https://learnku.com/laravel/t...

We will continue to build our Vue SPA using Laravel by demonstrating how to load data asynchronously before vue-router enters a route.

Before Create a Vue single page application from Laravel (2) The UsersIndex component is finished loading users asynchronously from the API in.It simplifies building a real back-end API from a database and chooses to simulate false data in API returns using Laravel's factory() method.

If you haven't read about building Vue single page apps from Laravel yet Part One And Part Two I suggest you go and see it before you come back here.I'll be here waiting for you.

In this tutorial, we'll replace the simulated /users return with a real database-supported one.I'm used to using MySQL, but you can use any database driver you want!

The UsersIndex.vue routing component loads data through the API in the life cycle created().The following is an example of the fetchData() method at the end of Part 2:

created() {
    this.fetchData();
},
methods: {
    fetchData() {
        this.error = this.users = null;
        this.loading = true;
        axios
            .get('/api/users')
            .then(response => {
                this.loading = false;
                this.users = response.data;
            }).catch(error => {
                this.loading = false;
                this.error = error.response.data.message || error.message;
            });
    }
}

I'll show you how to extract data from an API by using the component's pre-navigation, but before that we need to have the API output some real data.

Create a true client endpoint

We will create a UsersController to use Laravel 5.5 New API Resources To return JSON data.

Before creating controller and API resources, let's first set up a database and populate it with data to provide some test data for our SPA.

User Data Filling

We use the make:seeder command to create a user fill:

php artisan make:seeder UsersTableSeeder

UsersTableSeeder is very simple.We use a model factory to create 50 users:

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        factory(App\User::class, 50)->create();
    }
}

Next, we add the UsersTableSeeder to the database/seeds/DatabaseSeeder.php file:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $this->call([
            UsersTableSeeder::class,
        ]);
    }
}

We will not be able to populate with data without first creating and configuring the database.

Configuration database

It's time to connect our Vue SPA Laravel application to a real database.You can use similar TablePlus GUI tools for using SQLite or MySQL.If you're new to Laravel, you can check out the Getting started with databases A large number of documents on.

If you have a MySQL instance running on your device, you can create a new database fairly quickly using the following command line (assuming your local environment does not have a password set):

mysql -u root -e"create database vue_spa;"

# Or enter the password with the -p parameter
mysql -u root -e"create database vue_spa;" -p

When you have a database, add the configuration DB_DATABASE=vue_spa to the.env file.If you encounter problems, follow the documentation to make your database easier to work with.

Once you have configured your database connection, you can migrate your tables and add populated data.Laravel comes with a migration of the Users table that we use to populate our data:

# Ensure database seeders are automatically loaded
composer dump-autoload
php artisan migrate:fresh --seed

You can also use a separate artisan db:seed command if you want!Like this; you should have a database of 50 users that we can query and return through the api.

Users Controller

Chapter 2, Simulated/users in routes/api.php are as follows:

Route::get('/users', function () {
    return factory('App\User', 10)->make();
});

Let's create a new controller class that will benefit from using php artisan route:cache in a production environment that does not support closures.We create both Controller and User API resource classes on the command line:

php artisan make:controller Api/UsersController
php artisan make:resource UserResource

The first command creates a User Controller in the app/Http/Controllers/Api directory, and the second command creates a UserResource in the app/Http/Resources directory.

The following new routes/api.php code corresponds to the controller and the Api namespace:

Route::namespace('Api')->group(function () {
    Route::get('/users', 'UsersController@index');
});

Control is straightforward; return an Eloquent API with paging:

<?php

namespace App\Http\Controllers\Api;

use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;

class UsersController extends Controller
{
    public function index()
    {
        return UserResource::collection(User::paginate(10));
    }
}

The following is an example of a JSON response similar to the API format of the previous UserResource:

{
   "data":[
      {
         "name":"Francis Marquardt",
         "email":"schamberger.adrian@example.net"
      },
      {
         "name":"Dr. Florine Beatty",
         "email":"fcummerata@example.org"
      },
      ...
   ],
   "links":{
      "first":"http:\/\/vue-router.test\/api\/users?page=1",
      "last":"http:\/\/vue-router.test\/api\/users?page=5",
      "prev":null,
      "next":"http:\/\/vue-router.test\/api\/users?page=2"
   },
   "meta":{
      "current_page":1,
      "from":1,
      "last_page":5,
      "path":"http:\/\/vue-router.test\/api\/users",
      "per_page":10,
      "to":10,
      "total":50
   }
}

Amazingly, Laravel automatically adds paging data and assigns user information to the data property!

The following is the UserResource class:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class UserResource extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }
}

UserResource converts each User model in the collection into an array, providing the UserResource::collection() method to convert the user's collection into JSON format.

By now, you should have a / api/users interface for single-page applications, and if you continue to learn, you will notice that the new return does not satisfy the current components.

Modify UsersIndex component

We can quickly get the UsersIndex.vue component back to work by adjusting then() to invoke the data key where the user data resides.At first it looks a little new, but response.data is a response object, so we can set user data like this:

this.users = response.data.data;

The following is how fetchData() is adjusted with the new API:

fetchData() {
    this.error = this.users = null;
    this.loading = true;
    axios
        .get('/api/users')
        .then(response => {
            this.loading = false;
            this.users = response.data.data;
        }).catch(error => {
            this.loading = false;
            this.error = error.response.data.message || error.message;
        });
}

Read data before navigation

Our components work through our new API, and now is a great time to demonstrate how to get user information before navigating to the components.
By using this method, we can navigate to a new route after getting the data.We can do this before entering the component by using the beforeRouteEnter guard.Example Vue Routing Document The following:

beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },

There are complete examples of consulting documentation, but only if we say we will get user data asynchronously, and we will only trigger next() and set the data in the component (variable vm) when it is finished

Check the documentation for a complete example, but just say that we will get user data asynchronously, once completed, and only after that we will trigger next (, and set the data (variable vm) on the component.

The following is a getUsers function that may look like getting users from the API asynchronously and triggering callbacks to components:

const getUsers = (page, callback) => {
    const params = { page };

    axios
        .get('/api/users', { params })
        .then(response => {
            callback(null, response.data);
        }).catch(error => {
            callback(error, error.response.data);
        });
};

Note that instead of returning Promise, the method triggers a callback on completion or failure.The callback passes two parameters: an error and a response from an API call.

Our getUsers() method accepts a page variable that eventually appears in the request as a query string parameter.If it is empty (no page number is passed in the route), the API defaults to page=1.

Finally, I want to point out the const params value.It's actually like this:

{
    params: {
        page: 1
    }
}

Here's how our beforeRouteEnter guard uses the getUsers function to get asynchronous data and then calls next() on the component to set it:

beforeRouteEnter (to, from, next) {
    const params = {
        page: to.query.page
    };

    getUsers(to.query.page, (err, data) => {
        next(vm => vm.setData(err, data));
    });
},

This is the callback parameter in the getUsers() call after returning data from the API:

(err, data) => {
    next(vm => vm.setData(err, data));
}

Then when the API responds successfully, call this in getUsers():

callback(null, response.data);

beforeRouteUpdate

When the component is already rendered and the route changes, beforeRouteUpdate is called, and the Vue reuses the component in the new route.For example, when our user jumps from/users?page=2 to /users?page=3.

The beforeRouteUpdate call is similar to the beforeRouteEnter call.However, the former can be used in components, so the style is slightly different:

// When the route changes and the component has been rendered,
// The logic is slightly different.
beforeRouteUpdate (to, from, next) {
    this.users = this.links = this.meta = null
    getUsers(to.query.page, (err, data) => {
        this.setData(err, data);
        next();
    });
},

Since the components are rendered, we need to reset some data properties before getting the next set of users from the API.We can access components.So we can call this.setData() (I haven't shown you yet), then next() without a callback.

Finally, this is the setData method in the UsersIndex component:

setData(err, { data: users, links, meta }) {
    if (err) {
        this.error = err.toString();
    } else {
        this.users = users;
        this.links = links;
        this.meta = meta;
    }
},

The setData() method is obtained by using object destructors.
The data, links, and meta keys come from the API's response.We explicitly assign data to the new variable users using data: users.

Bundle UsersIndex Together

I've shown you the parts of this UsersIndex component, and we're ready to bundle all the components together and make some very basic paging.This tutorial doesn't show you how to build paging, so you can find (or create) fantastic paging yourself!

Paging is a good way to show you how to browse SPA programmatically in vue-router.

This is a complete component with our new hooks and methods that use router hooks to obtain asynchronous data:

<template>
    <div class="users">
        <div v-if="error" class="error">
            <p>{{ error }}</p>
        </div>

        <ul v-if="users">
            <li v-for="{ id, name, email } in users">
                <strong>Name:</strong> {{ name }},
                <strong>Email:</strong> {{ email }}
            </li>
        </ul>

        <div class="pagination">
            <button :disabled="! prevPage" @click.prevent="goToPrev">Previous</button>
            {{ paginatonCount }}
            <button :disabled="! nextPage" @click.prevent="goToNext">Next</button>
        </div>
    </div>
</template>
<script>
import axios from 'axios';

const getUsers = (page, callback) => {
    const params = { page };

    axios
        .get('/api/users', { params })
        .then(response => {
            callback(null, response.data);
        }).catch(error => {
            callback(error, error.response.data);
        });
};

export default {
    data() {
        return {
            users: null,
            meta: null,
            links: {
                first: null,
                last: null,
                next: null,
                prev: null,
            },
            error: null,
        };
    },
    computed: {
        nextPage() {
            if (! this.meta || this.meta.current_page === this.meta.last_page) {
                return;
            }

            return this.meta.current_page + 1;
        },
        prevPage() {
            if (! this.meta || this.meta.current_page === 1) {
                return;
            }

            return this.meta.current_page - 1;
        },
        paginatonCount() {
            if (! this.meta) {
                return;
            }

            const { current_page, last_page } = this.meta;

            return `${current_page} of ${last_page}`;
        },
    },
    beforeRouteEnter (to, from, next) {
        getUsers(to.query.page, (err, data) => {
            next(vm => vm.setData(err, data));
        });
    },
    // when route changes and this component is already rendered,
    // the logic will be slightly different.
    beforeRouteUpdate (to, from, next) {
        this.users = this.links = this.meta = null
        getUsers(to.query.page, (err, data) => {
            this.setData(err, data);
            next();
        });
    },
    methods: {
        goToNext() {
            this.$router.push({
                query: {
                    page: this.nextPage,
                },
            });
        },
        goToPrev() {
            this.$router.push({
                name: 'users.index',
                query: {
                    page: this.prevPage,
                }
            });
        },
        setData(err, { data: users, links, meta }) {
            if (err) {
                this.error = err.toString();
            } else {
                this.users = users;
                this.links = links;
                this.meta = meta;
            }
        },
    }
}
</script>

If it's easier to understand, here's as UsersIndex.vue for GitHub Gist.

There are many new things here, so I'll point out some more important points.The goToNext() and goToPrev() methods demonstrate how to use this.$router.push using the navigation vue-router:

this.$router.push({
    query: {
        page: `${this.nextPage}`,
    },
});

We are pushing a new page to the triggered query string beforeRouteUpdate.I also want to point out that I show you the elements of the <button>previous and next action, mainly for demonstration purposes Navigate programmatically The process vue-router, which you will most likely use to automatically navigate between paging routes.

I introduced three calculation properties (nextPage, prevPage, and paginatonCount) to determine page numbers for the next and previous pages, and paginatonCount shows the visual count and total number of pages for the current page number.

The next and previous buttons use computed attributes to determine whether they should be disabled, and the goTo method uses these computed attributes to push page query string parameters to the next or previous page.These buttons are disabled when the next or previous page is empty at the boundary between the first and last pages.

There may be some redundancy in the code, but this component illustrates the method that vue-router uses to get data before entering a route!

Don't forget to make sure you build the latest version of JavaScript by running Laravel Mix:

# NPM
npm run dev

# Watch to update automatically while developing
npm run watch

# Yarn
yarn dev

# Watch to update automatically while developing
yarn watch

Finally, this is the SPA result shown when we update the complete UsersIndex.vue component:

What's Next

We now have a valid API that retrieves real data from the database, and a simple paging component that uses Laravel's API model resources in the back end for simple paging links and packages data into data keys.

Next, we'll focus on creating, editing, and deleting users.A / users resource will be locked into a real application, but for now, we're just building the CRUD function to learn how to use it with vue-router to navigate asynchronously and extract data.

We can also abstract axios client code from components, but for now, it's simple, so we keep it in components until Part 4.Once additional API capabilities have been added, we will want to create modules for dedicated HTTP clients.

You can continue Part 4 - Editing existing users

Posted by davinci on Thu, 26 Sep 2019 19:07:27 -0700