Vue.js dynamic images not working - javascript

I have a case where in my Vue.js with webpack web app, I need to display dynamic images. I want to show img where file name of images are stored in a variable. That variable is a computed property which is returning a Vuex store variable, which is being populated asynchronously on beforeMount.
<div class="col-lg-2" v-for="pic in pics">
<img v-bind:src="'../assets/' + pic + '.png'" v-bind:alt="pic">
</div>
However it works perfectly when I just do:
<img src="../assets/dog.png" alt="dog">
My case is similar to this fiddle, but here it works with img URL, but in mine with actual file paths, it does not work.
What should be correct way to do it?

I got this working by following code
getImgUrl(pet) {
var images = require.context('../assets/', false, /\.png$/)
return images('./' + pet + ".png")
}
and in HTML:
<div class="col-lg-2" v-for="pic in pics">
<img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>
But not sure why my earlier approach did not work.

Here is a shorthand that webpack will use so you don't have to use require.context.
HTML:
<div class="col-lg-2" v-for="pic in pics">
<img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>
Vue Method:
getImgUrl(pic) {
return require('../assets/'+pic)
}
And I find that the first 2 paragraphs in here explain why this works? well.
Please note that it's a good idea to put your pet pictures inside a subdirectory, instead of lobbing it in with all your other image assets. Like so: ./assets/pets/

You can try the require function. like this:
<img :src="require(`#/xxx/${name}.png`)" alt class="icon" />
The # symbol points to the src directory.
source: Vue URL transfrom rules

There is another way of doing it by adding your image files to public folder instead of assets and access those as static images.
<img :src="'/img/' + pic + '.png'" v-bind:alt="pic" >
This is where you need to put your static images:

Your best bet is to just use a simple method to build the correct string for the image at the given index:
methods: {
getPic(index) {
return '../assets/' + this.pics[index] + '.png';
}
}
then do the following inside your v-for:
<div class="col-lg-2" v-for="(pic, index) in pics">
<img :src="getPic(index)" v-bind:alt="pic">
</div>
Here's the JSFiddle (obviously the images don't show, so I've put the image src next to the image):
https://jsfiddle.net/q2rzssxr/

Vue.js uses vue-loader, a loader for WebPack which is set up to rewrite/convert paths at compile time, in order to allow you to not worry about static paths that would differ between deployments (local, dev, one hosting platform or the other), by allowing you to use relative local filesystem paths. It also adds other benefits like asset caching and versioning (you can probably see this by checking the actual src URL being generated).
So having a src that would normally be handled by vue-loader/WebPack set to a dynamic expression, evaluated at runtime, will circumvent this mechanism and the dynamic URL generated will be invalid in the context of the actual deployment (unless it's fully qualified, that's an exception).
If instead, you would use a require function call in the dynamic expression, vue-loader/WebPack will see it and apply the usual magic.
For example, this wouldn't work:
<img alt="Logo" :src="logo" />
computed: {
logo() {
return this.colorMode === 'dark'
? './assets/logo-dark.png'
: './assets/logo-white.png';
}
}
While this would work:
<img alt="Logo" :src="logo" />
computed: {
logo() {
return this.colorMode === 'dark'
? require('./assets/logo-dark.png')
: require('./assets/logo-white.png');
}
}
I just found out about this myself. Took me an hour but... you live, you learn, right? ๐Ÿ˜Š

I also hit this problem and it seems that both most upvoted answers work but there is a tiny problem, webpack throws an error into browser console (Error: Cannot find module './undefined' at webpackContextResolve) which is not very nice.
So I've solved it a bit differently. The whole problem with variable inside require statement is that require statement is executed during bundling and variable inside that statement appears only during app execution in browser. So webpack sees required image as undefined either way, as during compilation that variable doesn't exist.
What I did is place random image into require statement and hiding that image in css, so nobody sees it.
// template
<img class="user-image-svg" :class="[this.hidden? 'hidden' : '']" :src="userAvatar" alt />
//js
data() {
return {
userAvatar: require('#/assets/avatar1.svg'),
hidden: true
}
}
//css
.hidden {display: none}
Image comes as part of information from database via Vuex and is mapped to component as a computed
computed: {
user() {
return this.$store.state.auth.user;
}
}
So once this information is available I swap initial image to the real one
watch: {
user(userData) {
this.userAvatar = require(`#/assets/${userData.avatar}`);
this.hidden = false;
}
}

Here is Very simple answer. :D
<div class="col-lg-2" v-for="pic in pics">
<img :src="`../assets/${pic}.png`" :alt="pic">
</div>

<img src="../assets/graph_selected.svg"/>
The static path is resolved by Webpack as a module dependency through loader.
But for dynamic path you need to use require to resolve the path. You can then switch between images using a boolean variable & ternary expression.
<img :src="this.graph ? require( `../assets/graph_selected.svg`)
: require( `../assets/graph_unselected.svg`) " alt="">
And of course toggle the value of the boolean through some event handler.

<div
v-for="(data, i) in statistics"
:key="i"
class="d-flex align-items-center"
>
<img :src="require('#/assets/images/'+ data.title + '.svg')" />
<div class="ml-2 flex-column d-flex">
<h4 class="text-left mb-0">{{ data.count }}</h4>
<small class="text-muted text-left mt-0">{{ data.title }}</small>
</div>
</div>

You can use try catch block to help with not found images
getProductImage(id) {
var images = require.context('#/assets/', false, /\.jpg$/)
let productImage = ''
try {
productImage = images(`./product${id}.jpg`)
} catch (error) {
productImage = images(`./no_image.jpg`)
}
return productImage
},

I also faced this problem.
Try it:
computed {
getImage () {
return require(`../assets/images/${imageName}.jpg`) // the module request
}
}
Here is a good article that clarifies this:
https://blog.lichter.io/posts/dynamic-images-vue-nuxt/

Tried all of the answers here but what worked for me on Vue2 is like this.
<div class="col-lg-2" v-for="pic in pics">
<img :src="require(`../assets/${pic.imagePath}.png`)" :alt="pic.picName">
</div>

As I am using Gridsome, this way worked for me.
**I also used toLowerCase() method
<img
:src="
require(`#/assets/images/flags/${tournamentData.address.country_name.toLowerCase()}.svg`)
"
/>

well the best and easiest way that worked for me was this of which i was fetching data from an API..
methods: {
getPic(index) {
return this.data_response.user_Image_path + index;
}
}
the getPic method takes one parameter which is the name of the file and it returns the absolute path of the file maybe from your server with the file name simple...
here is an example of a component where i used this:
<template>
<div class="view-post">
<div class="container">
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="" aria-describedby="helpId" placeholder="search here">
<small id="helpId" class="form-text user-search text-muted">search for a user here</small>
</div>
<table class="table table-striped ">
<thead>
<tr>
<th>name</th>
<th>email</th>
<th>age</th>
<th>photo</th>
</tr>
</thead>
<tbody>
<tr v-bind:key="user_data_get.id" v-for="user_data_get in data_response.data">
<td scope="row">{{ user_data_get.username }}</td>
<td>{{ user_data_get.email }}</td>
<td>{{ user_data_get.userage }}</td>
<td><img :src="getPic(user_data_get.image)" clas="img_resize" style="height:50px;width:50px;"/></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'view',
components: {
},
props:["url"],
data() {
return {
data_response:"",
image_path:"",
}
},
methods: {
getPic(index) {
return this.data_response.user_Image_path + index;
}
},
created() {
const res_data = axios({
method: 'post',
url: this.url.link+"/view",
headers:{
'Authorization': this.url.headers.Authorization,
'content-type':this.url.headers.type,
}
})
.then((response)=> {
//handle success
this.data_response = response.data;
this.image_path = this.data_response.user_Image_path;
console.log(this.data_response.data)
})
.catch(function (response) {
//handle error
console.log(response);
});
},
}
</script>
<style scoped>
</style>

I encountered the same problem.
This worked for me by changing '../assets/' to './assets/'.
<img v-bind:src="'./assets/' + pic + '.png'" v-bind:alt="pic">

The image needs to be transcribed.
What worked for me is putting the images in public folder. i.e public/assets/img
Dynamic Image Tag:
<div v-for="datum in data">
<img
class="package_image"
style="max-width:200px;"
alt="Vue logo"
:src="`./assets/img/${datum.image}`"
>
<div>

I have a solution you may want to try.
Define a method like below
methods: {
getFlagImage(flag){
return new URL('/resources/img/flags/'+flag+'.png', import.meta.url);
},
}
then images can be called with the established for loop
<li :class=" 'nav-item', {'active': language === key} " v-for="(value,
key) in locals" :key="value ">
<a class="dropdown-item" #click="switchLanguageTo(key)">
<img :src="getFlagImage(key)" /> {{value}}
</a>
</li>

I think I found the best solution to this problem by accident!
The only thing you have to do is to start addressing from the root.
Doesn't work
<img :src="'../assets/' + pic + '.png">
Work:
<img :src="'src/assets/' + pic + '.png">

As of today, working with VUE 3 + Typescript & composition API, what I have done is wrap require function in try catch to handle crash.
computed: {
getImage() {
let imgSrc = "";
try {
imgSrc = require(`../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg`);
} catch (error) {
console.error(`Image '../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg' not found!`);
}
return imgSrc;
}
}
and call this function in image tag:
<div class="weather-icon">
<img :src="getImage" :alt="weatherForecast.main" />
</div>

Related

Cant bind imageUrl in Angular

Please help guys! I'm getting datas from https://developers.giphy.com/, and passing them to a modal for viewing, every other data is showing except for imageUrl.
(what I'm I doing wrong, why wont the gif show)
Check below for my code
here is the method calling the api
data = new BehaviorSubject<any>([]);
search(event){
this.http
.get(
this.searchEndpointGiphy +
"api_key=" +
giphyKey +
"&q=" +
event.target.value +
"&limit =" +
limit +
"&offset=" +
offset +
"&rating=" +
rating +
"&lang=en",
)
.subscribe((gifData: any) => {
this.data.next(gifData.data);
}
);
this.modalPopUp();
}
here is the modalPopup am also passing data to my modal here
modalPopUp() {
const modalRef = this.modalService.open(ModalPopUpComponent);
modalRef.componentInstance.data = this.data;
}
finally my modal html file
<div class = "container" *ngFor="let d of data | async">
<div class = "row">
<div class = "col-12 col-6">
</div>
<div class = "col-12 col-6">
<div class="card">
<img [src]="d.url" class="card-img-top img-fluid">
<div class="card-body">
<h5 class="card-title">{{d.title}}</h5>
</div>
</div>
</div>
</div>
</div>
Please why is the imageUrl not displaying??,
Have tried all the questions/answers I saw here, but none was able to fix my problem
From the screenshot shown in this link (https://ibb.co/XSL1qWp), I can see an "images" object.
Also, the extract shown below from Giphy Docs shows you should look inside the "images" object for different versions of the gif you intend to display, I think you should access the images object instead of that URL field.
We provide various renditions of each GIF in the images object to give your users the best experience possible. Generally, itโ€™s best to use the smaller fixed_height or fixed_width renditions on your preview grid.
Try to add this in your index.html file
<meta name="referrer" content="no-referrer"/>

Problem with Vue /Javascript interpolation

I'm trying to interpolate a value of photo within Vue. Here is the components:
Explaination: We are defining a data set as null. We are then, performing a post method to a backend database where we get photo,email,name and id. Everything works fine until the photo value. As you can see I performed a slice method on the string which removed unnecessary data. The output of this is just the file name.
<script>
export default {
data() {
return {
photo: null,
email: null,
name: null,
id: null
};
},
created() {
this.axios
.post("http://127.0.0.1:8000/api/auth/me", "body", {
headers: axiosHeader
})
.then(response => {
console.log(response.data);
this.name = response.data.name;
this.email = response.data.email;
this.id = response.data.id;
this.photo = "#/photodatabase/" + response.data.photo.slice(37, 56);
console.log(this.photo);
})
.catch(error => {
console.log(error);
});
}
};
</script>
The value I got is #/photodatabase/041120_09_24_03.jpg when I console.log(this.photo) which is the correct value. However when I try to interpolate it like:
<img v-else-if="photo != null" :src="photo"
height="200px" width="200px" />
The image doesn't show. When I inspected element, you could see on the img tag that the value was #/photodatabase/041120_09_24_03.jpg which was the correct value.
I tried interpolating like this:
<img v-else-if="photo != null" :src="`${photo}`"
height="200px" width="200px" />
and it still doesn't work. However when I didn't interpolate the value of photo, didn't use the v-bind shorthand : and just copied and pasted the value of photo to the src prop like this:
<img
v-else-if="photo != null"
src="#/photodatabase/041120_09_24_03.jpg"
height="200px"
width="200px"
/>
Then it works. What am I missing here?
Because is dynamic image src
must photo be absolute URL Like: http://127.0.0.1:8000/photodatabase/041120_09_24_03.jpg
or
if in the same server
/photodatabase/041120_09_24_03.jpg
or you can easily send absolute URL on response.
I believe that this isn't the correct URL #/photodatabase/041120_09_24_03.jpg
You've wrote "it still doesn't work" so You get "404 Not found" error?
That link should lead to Your backend (static asset or Laravel controller).
# is rewritten by Webpack during compilation time to Your public url - it won't work as a dynamic path in runtime...
This code
<img src="#/photodatabase/041120_09_24_03.jpg" />
...should become something else after compilation. Inspect it.
It could be something like that:
<img src="/static/photodatabase/041120_09_24_03.jpg" />
So if You didn't map # in vue-router, nor in Laravels routes, then You should change # in that link. You can try simply /photodatabase/041120_09_24_03.jpg
Try using the require handler
<img v-else-if="photo != null" :src="require(${photo})"
height="200px" width="200px" />

Vue JS - Apply certain classes to elements in a v-for loop according to data from previous elements in the loop

Sorry for the lengthy title, I have a feeling this is an oddly specific edge case not many people have to deal with.
Some background, I'm working on a webapp to track PC repairs for our shop. We have one currently, which we purchased and have access to the source code thanks to the author's method of distribution. Each repair is signified by a Work Order, all of which have notes. In the old app, the notes have the users name, the date it was posted, and the edit and delete buttons (if you are either the admin or the author) on the left, and the note on the right. If the user that posted the note changes when going down the list, it swaps them around, so the note is on the left and user is on the right, i.e.
user1 - note text
note text - user2
user3 - note text
user3 - note text
note text - user1
The old app did this with plain PHP, in a php file filled to the brim with echo statements. The new app I was working on has a laravel backend (to make use of things like Eloquent) with a Vue JS frontend (to assist with live updates in websockets). So on the Work Order page, there is a component for the list of notes, which takes the list of notes assigned to that Work Order as a prop, and iterates over all the notes with a v-for. I wanted to mimic the orientation switching feature from the previous setup. I can acheive the switch by setting up each note as a grid container, and applying either order-first or order-last to the column containing the user info. What I'm struggling with is trying to find a way to toggle which class is applied when the user changes.
At first I had a data attribute keeping track of the current class, so when the user changed I could check what the class was currently and switch it to the opposite. However, this caused an infinite render loop, as the entire list of notes would re-render whenever that attribute was changed. It did accomplish what I wanted to do visually, but it caused severe performance issues. Then I tried using refs, so when the user changed I could get the previous entry in the list from the refs array and examine its classes to see what order class it had to set the next elements order class appropriately. However this didn't work because the refs array would not be populated until the list was done rendering, and I needed to set the class as it rendered. I tried using a computed property, but it can't take arguments (i.e. the index of the array I was currently on to compare with index-1) and even if it could there is no way I could find to check the current cached value of that property while calculating the new one.
Here is the code I am working with for reference, currently with any of the previous approaches I tried removed, so currently no user switching happens.
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<note-form-modal :modal-id="'note'+note.noteid+'editModal'" :populate-with="note"></note-form-modal>
<div class="col-md-1 d-flex flex-column mx-md-3">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
<div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
<div class="btn-group justify-content-center p-0 m-0">
<template v-if="authusername === note.noteuser || authusername === 'admin'">
<button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
<button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
</template>
</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
import dateMixin from '../mixins/dateMixin'
export default {
mixins:[dateMixin],
props: ['initialnotes', 'authusername', 'noteType', 'woid'],
data () {
return {
notes: Object.values(this.initialnotes),
currentOrder: 'order-first',
newNote: {
notetype: this.noteType,
thenote: '',
noteuser: this.authusername,
woid: this.woid
}
}
},
mounted () {
Echo.channel('wonotes.'+this.noteType+'.'+this.woid)
.listen('WorkOrderNoteAdded', (e) => {
this.notes.push(e.note)
})
.listen('WorkOrderNoteEdited', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.note.noteid
})
this.notes[index] = e.note
})
.listen('WorkOrderNoteDeleted', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.noteid
})
this.notes.splice(index, 1)
})
},
methods: {
createNote () {
axios.post('/api/workorders/notes', this.newNote)
.then((response) => {
$('#note'+this.noteType+'add').collapse('hide')
this.newNote.thenote = ''
})
}
}
}
</script>
noteType is there because we have two different types of notes, one that a customer can see, and one that only techs can see.
Is there something obvious I'm missing, have I just architected this thing wrong, am I trying to do something impossible? Any assistance would be greatly appreciated, I'm at the end of my rope here with this one.
I ended up figuring out a solution to this, not sure if its the best one but here goes.
Basically, I maintain an array parallel to the notes array that determines which order- class each array index should have. As it is dependent on the notes attribute, I make it a computed property, so that I can use the notes attribute and it automatically updates when the notes list changes. The file now looks like this (with some code related to posting new notes and editing existing ones removed for clarity)
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<div class="col-md-1 d-flex flex-column mx-md-3" :class="noteOrders[index]">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['initialnotes'],
data () {
return {
notes: this.initialnotes,
}
},
computed: {
noteOrders () {
return this.getNoteOrders(this.notes)
}
},
methods: {
getNoteOrders(notes) {
let noteOrders = []
notes.forEach((note,index) =>{
if (index === 0) {
noteOrders[index] = 'order-first'
} else if (note.noteuser !== notes[index-1].noteuser) {
if (noteOrders[index-1] === 'order-first') {
noteOrders[index] = 'order-last'
} else {
noteOrders[index] = 'order-first'
}
} else {
noteOrders[index] = noteOrders[index-1]
}
})
return noteOrders
}
}
}
</script>
There may very well be better solutions and I encourage anyone who happens upon this to post theirs if they have one, but as I found a solution that works for me I decided to post it for anyone else running into the same issue.

Ng-repeat is not displaying json data

thanks advance for any support. So I have a factory that uses a post to get some data from a C# method. That all seems to be working as I can see the data in the console log when it gets returned. However, when I get the data, I can't seem to get it to display properly using ng-repeat.
I've tried a couple different ways of nesting ng-repeats and still no luck. So now I'm thinking I may have not passed the data from the call properly or my scope is off. I've also tried passing data.d to hangar.ships instead of just data. Still pretty new to angular so in any help to point me int he right direction is greatly appreciated.
app code:
var app = angular.module('shipSelection', ['ngRoute', 'ngResource']);
app.controller('ShipController', function ($scope, ShipService) {
var hangar = this;
hangar.ships = [];
var handleSuccess = function (data, status) {
hangar.ships = data;
console.log(hangar.ships);
};
ShipService.getShips().success(handleSuccess);
});
app.factory('ShipService', function ($http) {
return {
getShips: function () {
return $http({
url: '/ceresdynamics/loadout.aspx/getships',
method: "post",
data: {},
headers: { 'content-type': 'application/json' }
});
}
};
});
Markup:
<div class ="col-lg-12" ng-controller="ShipController as hangar" >
<div class =" row">
<div class="col-lg-4" ><input ng-model="query" type="text"placeholder="Filter by" autofocus> </div>
</div><br />
<div class="row">
<div ng-repeat="ship in hangar.ships | filter:query | orderBy:'name'">
<div class="col-lg-4">
<div class="panel panel-default">
<div>
<ul class="list-group">
<li class="list-group-item" >
<p><strong>ID:</strong> {{ ship.ShipID }} <strong>NAME:</strong> {{ ship.Name }}</p>
<img ng-src="{{ship.ImageFileName}}" width="100%" />
</li>
</ul>
</div>
</div><!--panel-->
</div> <!--ng-repeat-->
</div>
</div>
</div> <!--ng-controller-->
JSON returned from the post(From the console.log(hangar.ships):
Object
d: "[{"ShipID":"RDJ4312","Name":"Relentless","ImageFileName":"Ship2.png"},{"ShipID":"ZLH7754","Name":"Hercules","ImageFileName":"Ship3.png"},{"ShipID":"FER9423","Name":"Illiad","ImageFileName":"Ship4.png"}]"
__proto__: Object
As per AngularJS version 1.2, arrays are not unwrapped anymore (by default) from a Promise (see migration notes). I've seen it working still with Objects, but according to the documentation you should not rely on that either.
Please see this answer Angular.js not displaying array of objects retrieved from $http.get
What happens if you add JSON.parse(data);
If this works you should add some checks in and perhaps migrate that logic to the service. Or use $resource per the other answer.
https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d

How to show the object in an AngularJS ng-repeat loop, only if its nested image exists?

My AngulaJS driven frontend gets the data from a local JSON file and will later switch to an API. The data is a list of Project objects with nested lists of Image objects. I'm displaying this data in a loop:
<div id="projects" ng-app="portfolio">
<div id="projectsList" ng-controller="ProjectsListController as projectsList">
<div class="projectItem" ng-repeat="projectItem in projectsList.projectsListData._embedded.projects">
<div class="project-image">
<img
ng-src="{{projectItem._embedded.images[0].src}}"
title="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
alt="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
/>
</div>
</div>
</div>
</div>
But sometimes the image src is invalid (error 404). It would be better to skip such projects, where the first image (images[0]) cannot be found. How to make the script skip the irrelevant objects?
EDIT
I've already got three answers, but the solutions don't work and I want to explain the problem exactly:
The src property of the images is alwys set. It's not the problem. That means, that checking if it's set or not (like ng-show="projectItem._embedded.images[0].src != ''" or ng-if="{{projectItem._embedded.images[0].src}}") will not work -- cannot work.
It doesn't work -- the src property is set. It's wrong (will cuase the 404 error), but it's set and projectItem._embedded.images[0].src != '' will return true for the "irrelevant" objects as well.
This is a hacky way of making this work:
To avoid loading images when they throw 404 error or when the images are invalid,
This must be inside your controller. This function checks whether the image URL is valid/invalid.
$scope.imageExists = function(image, object, index) {
var img = new Image();
img.onload = function() {
object[index] = true;
$scope.$apply();
};
img.onerror = function() {
return false;
};
img.src = image;
};
Now in View:
I'm initiating an object called img={}; in the ng-repeat.
Then initializing the value for that index in ng-repeat to $scope.imageExists function. Inside that function, on success of that image load, am setting img[index]= true.
<div ng-repeat="image in images" ng-init="img = {}">
<img ng-src="{{image}}" ng-init="img[$index] = imageExists(image, img, $index)" ng-show="img[$index]">
</div>
DEMO
So applying it to your code:
<div id="projects" ng-app="portfolio">
<div id="projectsList" ng-controller="ProjectsListController as projectsList">
<div class="projectItem" ng-repeat="projectItem in projectsList.projectsListData._embedded.projects" ng-init="img = {}">
<div class="project-image" ng-init="img[$index] = imageExists(projectItem._embedded.images[0].src, img, $index)" ng-show="img[$index]">
<img
ng-src="{{projectItem._embedded.images[0].src}}"
title="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
alt="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
/>
</div>
</div>
</div>
</div>
Place the $scope.imageExists code from above to your controller.
You can use ng-if
<div class="projectItem" ng-repeat="projectItem in projectsList.projectsListData._embedded.projects" ng-if="{{projectItem._embedded.images[0].src}}">
<div class="project-image">
<img
ng-src="{{projectItem._embedded.images[0].src}}"
title="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
alt="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
/>
</div>
</div>
</div>
Or you can also use ng-show/ng-hide which just doesn't show the element but would be present in DOM.
But be careful when you use ng-if as it creates its own scope.
EDIT: If the url is already set then one way is to test if the url exists using this method (or anything like this) JavaScript/jQuery check broken links , and
ng-if="UrlExists(projectItem._embedded.images[0].src)"
<div class="project-image" ng-if="projectItem._embedded.images[0].src != ''">
<img
ng-src="{{projectItem._embedded.images[0].src}}"
title="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
alt="{{projectItem.title}} - {{projectItem._embedded.images[0].title}}"
/>
</div>
You could create your own image directive:
<my-image src="http://someimageurl"></my-image>
If the image exists, the directive will insert the image into the DOM. If it does not, then you can ignore it, or insert a message.
var app = angular.module('app', []);
app.directive('myImage', function() {
return {
restrict: 'E',
replace:true,
link: function(scope, element, attr) {
var image = new Image();
image.onload = function() {
element.append(image);
}
image.onerror = function() {
element.html('Something went wrong or the image does not exist...');
}
image.src = attr.src;
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<my-image src="https://www.google.ca/images/srpr/logo11w.png" />
<my-image src="https://server/images/doesnotexist.png" />
</div>

Categories

Resources