0

Im working on a website with vue where i have to get images with axios from a server and put them as background images of divs. The case is that i have the urls but not all of them are correct. So im trying to make a function that makes an http request and returns me the url if that request is successful.

I thought of something like this:

Template:

<div class="element" :style="bgImg(url)"/>

Script:

methods: {
  bgImg(url) {
    axios.get(url)
      .then(response => {
        return `background-image: url(${response.config.url})`;
      })
      .cath(e) {
        throw new Error(e);
      }
  }
}

I was expecting to get the url returned from that function, but nothing happens.

Carlos Pisarello
  • 1,254
  • 5
  • 20
  • 39
  • You cannot simply return a value from an asynchronous function like `axios.get`. Take some time to read how asynchronous functions and Promises work. – djfdev Feb 28 '19 at 20:14
  • Possible duplicate of [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – zero298 Feb 28 '19 at 20:25
  • re: duplicate, not exactly, since the OP is looking for a vue solution – Daniel Feb 28 '19 at 20:27

4 Answers4

1

You could use a custom vue directive for that job:

Vue.directive('validate-bgimg', function (el, binding) {  
  el.style.backgroundImage = null  // reset 
  var url = binding.value
  if (!url) {
    return
  }
  axios.get(`https://cors-anywhere.herokuapp.com/${url}`)  // get around CORS limitations. Idealy you should call your own api to validate image urls
  .then(response => {
    if (response.status != 200) {
      console.warn("Invalide image url", url, response)
    } else {
      el.style.backgroundImage = `url(${url})`
    }
  })
  .catch(e => {
    console.error("Could not validate image", url, e)
  })
})


new Vue({
  el: "#app",
  data: {
    imgUrl: undefined
  }
})
#app {
  background: lightgray;
  border-radius: 4px;
  padding: 20px;
  margin: 20px;
  transition: all 0.2s;
}

.element {
  margin: 8px 0;
  width: 200px;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <b>test case:</b>
  <button @click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+1'">1</button>
  <button @click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+2'">2</button>
  <button @click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+3'">3</button>
  <button @click="imgUrl='https://httpbin.org/status/404'">HTTP 404</button>
  <button @click="imgUrl='https://httpbin.org/status/503'">HTTP 503</button>
  <button @click="imgUrl='https://invalid.tld'">cannot resolve domain name</button>
  
  <div class="element" v-validate-bgimg="imgUrl"/>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Thomasleveil
  • 95,867
  • 15
  • 119
  • 113
0
You should use setAttribute method.

 //add id to the element
<div id="element" class="element" />

var url = url(${response.config.url});

// select the element using id and set image
document.getElementById("element").setAttribute("style", "background-image:"+ url); 
Rohit Shetty
  • 494
  • 3
  • 8
0

Rather than returning from a then() callback (which will pass the result of the block to the next then()), you can store the data that you are seeking from the response object for use outside of the promise.

evaline
  • 246
  • 3
  • 8
0

Use a computed or temp value, that will make it easier.

In this solution, you would set a placeholder image to show while loading (or leave empty).

Then use a mounted() hook to load data, and then assign to the style

Template:

<div class="element" :style="imgStyle"/>

Script:

data:{
  return () => {
    imgStyle: {
       'background-image': 'url(my-placeholder.png)' // or not included at all
    }
  }
},
mounted() {
  axios.get(url)
    .then(response => {
      this.$set(this.imgStyle, 'background-image', `url(${response.config.url})`);
    })
    .cath(e) {
      throw new Error(e);
    }
}

As a side note, try to stay away from functions in templates for displaying values that can be done using a computed.

Daniel
  • 34,125
  • 17
  • 102
  • 150
  • This would not be useful due to the fact that each element is the result of an iteration. And each one has a different url. – Carlos Pisarello Feb 28 '19 at 20:35
  • But it would be useful in a component that has only one item. Maybe you can elaborate in your question. – Daniel Feb 28 '19 at 20:36
  • that's true, maybe i can make one component and iterate that one so i can use this answer. – Carlos Pisarello Feb 28 '19 at 20:42
  • You can, but if you are using an array, I would likely use a temp array to create a copy the array and add the `imgStyle` or something like that on every item that needs it. The array would be copied when the original array is received, and then I'd get all the images (in series or parallel) – Daniel Feb 28 '19 at 20:49