File Upload Progress Indicator with Axios and VueJS

Part 2 of 7 in Your Guide To Uploading Files with VueJS and Axios
Dan Pastori avatar
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:

<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:

<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:

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:

/*
  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:

/*
  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:

/*
  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:

/*
  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:

/*
  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:

/*
  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:

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!

Keep Reading
View the course View the Course Your Guide To Uploading Files with VueJS and Axios
Up Next → Drag and Drop File Uploads with VueJS and Axios

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Want to learn more about API Driven App Development?

We're writing a book on API Driven App Development. Be the first to know once it is ready!