Guide to Uploading Files with VueJS and Axios (Part 3 of 7)

File Upload Progress Indicator with Axios and VueJS

Dan Pastori

January 9th, 2018

So far, there's been two tutorials for dealing with file uploads with Axios and VueJS. The first tutorial is a basic overview of how to upload files using Axios and VueJS through an AJAX request: Uploading Files With VueJS and Axios - Server Side Up. The second adds a preview for uploaded images before they are submitted: Preview File Uploads with Axios and VueJS.

This tutorial will be a little quicker. We will be adding a progress indicator for file uploads. The last few tutorials covered the differences between single and multiple file scenarios. However, since Axios submits requests through an XMLHttpRequest, it submits all files at once. So our progress indicator will be for one or multiple files and work the same. You can adapt the code to each scenario since we get the value from the submission. It will be no different if you submit multiple files with the form data or a single file.

Let's get started!

🚨 UPDATE 10/14/2021

We've updated this tutorial to work with VueJS 3.0.0 and Axios 0.21.1. We've also added a GitHub repo where you can find components to implement into your own app. I'll explain the code updates below in the appropriate areas.

Create Vue Component

First we will need to create our Vue Component. To do that, I simply added a FileProgress.vue file for testing. In the file I added the following stub of code:

Basic Vue component structure

<template>

</template>

<script>
  export default {

  }
</script>

You can add a style tag too for styles if you want (recommended for production app to look nice), but for now we will keep it really simple. Next, we will add our template.

Add Template Code

Now we need to add our template code, so add the following to the template tag:

File upload template with progress bar

<template>
  <div class="container">
    <div class="large-12 medium-12 small-12 cell">
      <label>File
        <input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/>
      </label>
      <br>
      <progress max="100" :value.prop="uploadPercentage"></progress>
      <br>
      <button v-on:click="submitFile()">Submit</button>
    </div>
  </div>
</template>

There's a couple things to point out. Our file input has an attribute of ref="file". This makes the value accessible in the local $refs variable in our component. Next we listen to when the user uploads a file. When the user uploads a file, we copy the file uploaded to a local file variable (which we will add in the next step) so we can send it to the server.

UPDATE 10/14/2021: Remove $refs

You can also accomplish the same task by removing the refs attribute and passing the event to the handleFileUpload() method:

Alternative file upload handler without refs

handleFileUpload( event ){
  this.file = event.target.files[0];
},

The most important piece to the template is the <progress> element. This will be what we use to semantically display the upload progress for the file uploads. There's a couple attributes to point out on the element. The first is the max attribute. We set this to 100 since we want a compute a percentage based on 100 for how far along our file upload status is.

The next attribute on the <progress> element is the :value.prop="uploadPercentage" attribute. This is kind of unique. Since the <progress> element is not an input, the value is an attribute not an actual value like an input element would have. We bind the value as a prop (with :value.prop) to the uploadPercentage computed. In the next step, we will add the data for the uploadPercentage.

The final note about the template is the Submit button which, when clicked, runs the submitFile() method. This simply submits the file to the server.

All of these methods and props will be explained as we continue to build our component.

Add Data To Script

First we need to add the data variables used by the component to the data() method in the component. To do this, add the following to your component:

Component data properties

/*
  Defines the data used by the component
*/
data(){
  return {
    file: '',
    uploadPercentage: 0
  }
},

The file data will be used to store the file uploaded by the user. The uploadPercentage is defaulted to 0 and will be filled with the percentage uploaded through Axios.

Add handleFileUpload() Method

This method gets called whenever a user selects a file to upload. Simply add this to the methods object:

File upload handler method

/*
  Handles a change on the file upload
*/
handleFileUpload(){
  this.file = this.$refs.file.files[0];
}

The file uploaded is set to the local file variable so we can access it later when we submit the data to the server.

Add submitFile() Method

This method contains the core of our functionality. It should look like this:

File submission method with progress tracking

/*
  Submits the file to the server
*/
submitFile(){
  /*
    Initialize the form data
  */
  let formData = new FormData();

  /*
    Add the form data we need to submit
  */
  formData.append('file', this.file);

  /*
    Make the request to the POST /single-file URL
  */
  axios.post( '/file-progress',
    formData,
    {
      headers: {
          'Content-Type': 'multipart/form-data'
      },
      onUploadProgress: function( progressEvent ) {
        this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded / progressEvent.total ) * 100 ) );
      }.bind(this)
    }
  ).then(function(){
    console.log('SUCCESS!!');
  })
  .catch(function(){
    console.log('FAILURE!!');
  });
},

First, we initialize an instance of the FormData() object:

FormData initialization

/*
  Initialize the form data
*/
let formData = new FormData();

This allows us to append the data for our file to the data being passed to the form which happens next:

Appending file to FormData

/*
  Add the form data we need to submit
*/
formData.append('file', this.file);

Now we have our file added to the form data object. We can submit it to the server in the next part. If you are doing multiple files, make sure to check out either Uploading Files With VueJS and Axios - Server Side Up or Preview File Uploads with Axios and VueJS and you can get to this point with multiple files. The next functionality is how we compute the upload progress and with this method, we only do one progress bar so we only need to get the progress of the upload process.

The following code uploads the file to the server:

Axios file upload with progress tracking

/*
  Make the request to the POST /single-file URL
*/
axios.post( '/file-progress',
  formData,
  {
    headers: {
        'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: function( progressEvent ) {
      this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded / progressEvent.total ) * 100 ) );
    }.bind(this)
  }
).then(function(){
  console.log('SUCCESS!!');
})
.catch(function(){
  console.log('FAILURE!!');
});

We submit the data to the /file-progress endpoint which I am using for testing. We then pass the formData as the second parameter which is the data parameter. The third parameter is our configuration. This is where we add our header for 'Content-Type': 'multipart/form-data' so the server knows we could attach files.

Next, we add a method that attaches to the onUploadProgress event. The method attached contains the progressEvent as a parameter. We can use this parameter to determine how far along we are in uploading the files. First, we have to bind this to the method so we have access to our component's local variables. We then compute the upload percentage by taking what has been loaded, multiplying it by 100 to put it in a nice clean percentage form and then dividing it by the total to be uploaded to give us a percent:

Progress calculation

this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded / progressEvent.total ) * 100 ) );

Since we set it to the uploadPercentage on the local variable, VueJS takes care of the reactive qualities and sets our progress bar to represent the status. This is all we really need to do to show the progress! Of course you could use other elements and use the computed percentage to show a display as well, but the <progress> element is semantic and can be styled to fit your needs.

For now, I just have a listener on a successful request and catch any unsuccessful requests and print the status to the console.

Github Component

We've added a simple GitHub component where you can see the full code in action. Head over to our Github repo, download the component and let us know your thoughts!

We also handle file uploads in multiple ways in our book, the Ultimate Guide to Building APIs and SPAs with VueJS and Laravel. In the book, we provide full code examples for a ton of different functionality with VueJS and Laravel along with source code access to an app that we have deployed to production.

Conclusion

This little UX tweak can make a world of a difference to your users by showing them how far along their file upload progress is. This is especially helpful when building a single page application or an API driven application. If you are interested in how to process some of the files on the server side, I'm writing a book on API Driven Development. Be sure to sign up here Server Side Up General List to be the first to know when it's released!

If you have any questions, feel free to reach out or leave a comment below!

Want to work together?

Professional developers choose Server Side Up to ship quality applications without surrendering control. Explore our tools and resources or work directly with us.

Join our community

We're a community of 3,000+ members help each other level up our development skills.

Platinum Sponsors

Active Discord Members

We help each other through the challenges and share our knowledge when we learn something cool.

Stars on GitHub

Our community is active and growing.

Newsletter Subscribers

We send periodic updates what we're learning and what new tools are available. No spam. No BS.

Sign up for our newsletter

Be the first to know about our latest releases and product updates.

    Privacy first. No spam. No sharing. Just updates.