Loading dynamic images in a Vue Component
Tags: vue, components, javascript, webdev
Published: 2019-11-05 14:09:29

When I first started using Vue, one thing I got continually wrong was how to load an image into a Vue component dynamically. At first, I found that using an absolute URL worked, but that was only useful if I was storing the images on a CDN or other external site. If I included the images in my project, as either lightweight icons or static images, then using an absolute URL, with hostname and all, didn't really work. What about when I wanted to test some new images locally or on a dev server? Linking directly to the images in production just wasn't going to cut it.

When researching this, the [Vue CLI documentation for static assets](https://cli.vuejs.org/guide/html-and-static-assets.html#static-assets-handling) was, frankly, a little less than helpful. They didn't give a good example for what I was trying to do.

What I was building was a little form control to take credit card numbers. It consisted of a single file Vue component and looked something like this:

``` HTML
<template>
	<div class="form-group">
    <label>Credit Card Number</label>
    <input type="text" name="creditCardNumber" v-model="creditCardNumber"
           placeholder="1111111111111111">
    <img class="creditLogo" v-bind:src="creditCardLogoSrc">
  </div>
</template>

<script>
export default {
  name: 'credit-card-field',
  computed: {
    creditCardLogoSrc(vm) {
      if(vm.creditCardNumber.startsWith('4')) {
        // Return the Visa image
      } else if (vm.creditCardNumber.startsWith('5')) {
        // Return the Mastercard image
      } else if (vm.creditCardNumber.startsWith('6')) {
        // Return the Discover image
      } else {
        // Return the generic image
      }
    }
  },
  data() {
    return {
      creditCardNumber: ''
    }
  }
}
</script>
```

What I wanted to happen was, as the user is typing in their credit card number, I will look at it and swap out the `img` tag's source to show the credit card type that they were entering. I had a Visa image, a Mastercard image and a Discover image as well as the image of a generic credit card if it didn't match any of those. Since all Visas start with `4`, all Mastercards start with `5`, and all Discover cards start with `6`, this would be a pretty easy check to do.

The logic ended up being the easy part. The hard part was loading the images. I had the images in the Vue CLI provided `assets` folder, but how do I load them in?

Looking at the documentation, there are a lot of "in templates, do this" and "only in templates!" kinds of things. But I wasn't in a template. I was in the code part of my component.

After much research, I found the answer was to `require()` the images from the asset folder. Intuitive! 😒

So, when I need to use an image that is in the `assets` folder, I can `require()` the relative path to that image in my computed method:

``` JavaScript
creditCardLogoSrc(vm) {
  if(vm.creditCardNumber.startsWith('4')) {
    return require('../assets/visa.png');
  } else if (vm.creditCardNumber.startsWith('5')) {
    return require('../assets/mastercard.png');
  } else if (vm.creditCardNumber.startsWith('6')) {
    return require('../assets/discover.png');
  } else {
    return require('../assets/credit.png');
  }
}
```

One nice plus to doing it this way is, if the image is small enough, `require()` will return a dataurl instead of a URL path, which will save an extra call to the server and make the component a little more self contained.

So if you need to load images from inside a Vue CLI project's assets folder from outside the template of your component, now you know how.