Show image and when it doesn't exists, then show another image

Hi, I have a method which returns the URL for an image based on the “home” value. When the image doesn’t exist, it should create an URL based on the “league” value and if that one doesn’t exist, it should create an URL based on the “country” value:

<img :src="getLogoUrl(visit.home, visit.league, visit.country)" />

I have a sort of working method which uses the npm package “image-exists”, but I am struggling with promises. Is there a pure “Vue way” of doing this? The images are stored in the public folder of the application.

Regards,
John

You should provide the code for your method getLogoUrl so we can see what it’s doing. Also sample values for each variable.

This is the function:

getLogoUrl: function(club, league) {
  let imageExists = require('image-exists');

  // First check if club logo exists
  let src = "/images/logos/" + club + ".png";
  console.log("FIRST "+src);
  imageExists(src, function(exists) {
    // Then get league logo
    if (!exists) {
      src = "/images/leagues/" + league + ".png";
      console.log("SECOND "+src);
    }
  });
  console.log("THIRD "+src);
  return src;
}

This is the console output when the initial page is loaded (it loads the specific image which doesn’t exist):

FIRST /images/logos/maidenhead-united.png
THIRD /images/logos/maidenhead-united.png
SECOND /images/leagues/the-conference.png

And when the page is loaded from clicking a link (correct image shown):

FIRST /images/logos/maidenhead-united.png
SECOND /images/leagues/the-conference.png
THIRD /images/leagues/the-conference.png

This is all code from this component:

<template>
  <div class="visits row">
    <div class="col-sm-3 visit" v-for="visit in visits" :key="visit.nr">
      <div class="card">
        <div class="card-header">{{ visit.nr }}</div>
        <div class="card-body">
          <router-link :to="{ name : 'VisitDetails', params: {id: visit.id}}">
            <h5 class="card-title">{{ visit.home }}</h5>
          </router-link>
          <div class="logo">
            <img :src="getLogoUrl(visit.home, visit.league)" class="card-img-center img-responsive center-block"> 
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  name: 'home',
  data () {
    return {
      feedback: null,
      visits: [],
      errors: []
    }
  },
  methods: {
    getLogoUrl: function(club, league) {
      let imageExists = require('image-exists');

      // First check if club logo exists
      let src = "/images/logos/" + club + ".png";
      console.log("FIRST "+src);
      imageExists(src, function(exists) {
        // Then get league logo
        if (!exists) {
          src = "/images/leagues/" + league + ".png";
          console.log("SECOND "+src);
        }
      });
      console.log("THIRD "+src);
      return src;
    }
  },
  mounted() {
    axios
      .get(this.$baseURL + "/footmarks")
      .then(response => {
        console.log(response.data)
        this.visits = response.data
      })
      .catch(e => {
        console.log(this.errors)
      })
  }
}
</script>

<style scoped>

</style>

Not the cleanest solution but this worked for me. If image was not found I used onError image native event where I set a data property to show default image.
The default image for me was a username initials generated image from public API:

<template>
	<div class="avatar-picture">
		<picture v-if="!imgError">
			<source :srcset="`../static/images/avatars/avatar_${username}.webp`" type="image/webp">
			<img 
				:src="`../static/images/avatars/avatar_${username}.png`"
				:class="{'avatar-tile': settings.avatars.tile}"
				:alt="`${username} avatar`"
				:title="username"
				@error="onImgError()"
			>
		</picture>
		<img
			v-else
			:src="imageUrl(username)"
			:class="{'avatar-tile': settings.avatars.tile}"
			:alt="`${username} avatar`"
			:title="username"
		>
	</div>
</template>
methods: {
		onImgError() {
			this.imgError = true;
		},

		imageUrl(username) {
			return `https://ui-avatars.com/api/?name=${this.getInitials(username)}&size=${this.size ? this.size : this.settings.avatars.size}&background=${this.getBackgroundColor()}&color=${this.getForegroundColor()}`;
		}
}

The only thing I don’t like with this solution is that you will see error 404 in console which is something you as a developer cannot control (yet).

1 Like

Thanks for your solution, I will check it out.

Hi,

in this kind of cases I think computed properties are better, with a logic like this:

 computed: {
      avatarPicture() {
        const _ = this
        return ((_imgError) ? '../default_picture.png' : `../avatar_${_username}.png`)
      }
    }

and in your template:

<img :src="avatarPicture">

Hi,

This is what I’ve been using:

<template>
 <img :src="filename" @error="onImgError()"></img>
</template>

<script>
 export default {
  name: 'nameofthecomponent',
  props: {
  },
  data: function () {
   return {
    imgError: false
   }
  },
  methods: {
   onImgError() {
    this.imgError = true;
   }
  },
  computed: {
   filename() {
    return (this.imgError) ? "url-to-the-default-img-file" : "url-to-the-custom-img";
   }
  }
 }
</script>

<style scoped>
</style>
1 Like

when you have an array of images you can solve it this way.

<span v-if="product.thumbnails[0]">
                <img
                  :src="product.thumbnails[0].image"
                  height="120"
                  width="120"
                  class="mt-5"
                />
              </span>
              <span v-else>
                <v-img
                  height="120"
                  width="120"
                  src="FALLBACK IMAGE URL"
                ></v-img>
              </span>

It’s better to add a fallback image in your API itself.