60

Goal: Load an image with a dynamic source. If no image is found, then load a placeholder image instead.

This should demonstrate what I'm trying to do, but I don't know how to conditionally set validImage based on whether the first img src is valid.

<img *ngif="validImage" class="thumbnail-image" src="./app/assets/images/{{image.ID}}.jpg" alt="...">
<img *ngif="!validImage" class="thumbnail-image" src="./app/assets/images/placeholder.jpg" alt="...">

validImage should be true if src="./app/assets/images/{{image.ID}}.jpg" returns an image. Otherwise it would return false and only the second img tag should show.

There are obvious work arounds like storing a list of all valid image sources, but I'm thinking there is a better way to accomplish this.

Any suggestions on the best way to implement this in Angular2 would be greatly appreciated.

JSmith
  • 4,519
  • 4
  • 29
  • 45
user2263572
  • 5,435
  • 5
  • 35
  • 57

9 Answers9

168

The best way to handle broken image links is the use the onError event for the <img> tag:

<img  class="thumbnail-image" src="./app/assets/images/{{image.ID}}.jpg"
      onerror="this.src='./app/assets/images/placeholder.jpg';"  alt="..." />
Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
JanR
  • 6,052
  • 3
  • 23
  • 30
45
<img [src]="pic" (error)="setDefaultPic()">

And somewhere in your component class:

setDefaultPic() {
  this.pic = "assets/images/my-image.png";
}
Tiago Bértolo
  • 3,874
  • 3
  • 35
  • 53
  • 2
    Better than the accepted answer. It allows you to do much more things on error (remove the img tag and display a text maybe...), not only changing the img src. – Random Nov 03 '20 at 13:29
32

I ran into a similar need. I wanted to default to a 1X1 transparent pixel if an img url was null or returned an error (404 etc).

import { Directive, Input } from '@angular/core';

@Directive({
    selector: 'img[src]',
    host: {
        '[src]': 'checkPath(src)',
        '(error)': 'onError()'
    }
})
export class DefaultImage { 
    @Input() src: string;
    public defaultImg: string = '{YOUR_DEFAULT_IMG}';
    public onError() {
        this.src = this.defaultImg;
    }
    public checkPath(src) {
        return src ? src : this.defaultImg;
    }
}

Markup

<img [src]="{DESIRED_IMAGE_SOURCE}" />
Chris Wheaton
  • 429
  • 4
  • 3
18

The following approach also works if you want to handle the error in you class:

In your template:

<img [src]='varWithPath' (error) ="onImgError($event)">

In your class:

onImgError(event) { 
    event.target.src = 'assets/path_to_your_placeholder_image.jpg';
}
Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
Omar
  • 1,005
  • 11
  • 11
4

I've created a custom component that uses a placeholder image if the image is still not loaded or if an error occurs when loading it:

img.component.ts:

import { Component, Input, OnChanges } from '@angular/core';

@Component({
    selector: 'my-img',
    templateUrl: 'img.component.html',
})
export class ImgComponent implements OnChanges {
    @Input() 
    public src: string;
    @Input() 
    public default: string;
    @Input() 
    public alt: string;
    public cached = false;
    public loaded = false;
    public error = false;

    private lastSrc: string;

    constructor() { }

    public ngOnChanges() {
        if (this.src !== this.lastSrc) {
            this.lastSrc = this.src;
            this.loaded = false;
            this.error = false;
            this.cached = this.isCached(this.src);
        }

        if (!this.src) {
            this.error = true;
        }
    }

    public onLoad() {
        this.loaded = true;
    }

    public onError() {
        this.error = true;
    }

    private isCached(url: string): boolean {
        if (!url) {
            return false;
        }

        let image = new Image();
        image.src = url;
        let complete = image.complete;

        // console.log('isCached', complete, url);

        return complete;
    }
}

img.component.html:

<ng-container *ngIf="!cached">
    <img 
        *ngIf="!error" 
        [hidden]="!loaded"
        [src]="src" 
        [alt]="alt" 
        (load)="onLoad()" 
        (error)="onError()"
    >
    <img 
        *ngIf="default && (error || !loaded)" 
        [src]="default" 
        [alt]="alt"
    >
</ng-container>

<ng-container *ngIf="cached">
    <img 
        *ngIf="!error" 
        [src]="src" 
        [alt]="alt" 
        (error)="onError()"
    >
    <img 
        *ngIf="default && error" 
        [src]="default" 
        [alt]="alt"
    >
</ng-container>

Then you can use it like:

<my-img [src]="src" [alt]="alt" [default]="DEFAULT_IMAGE"></my-img>

PS: I verify beforehand if the image is cached to avoid the image blinking (normally when a component that has the image inside is re-rendered) between the placeholder and the image (if it is cached, I show the image even before the loaded flag be set to true). You can uncomment the log in the isCached function to see if your images are cached or not.

Lucas Basquerotto
  • 7,260
  • 2
  • 47
  • 61
  • Am I right that in your solution you cache every image component separately and thus spend extra memory on every same image occurance on the page (example: table where several cells show the same image, other cells a different one)? I'm almost sure browser default caching of images is much more effective in this case. – Arsenii Fomin Jun 14 '18 at 11:07
  • @ArseniiFomin The cache is not separate. It's only one cache per image (unless you inlude some parameter to bypass that, but that also happens when you use `img` `src`). If the cache was different for the same image, it would always return `false` when `isCached()` is called. You can make the test of loading an image in an `img` tag and after call `new Image()` passing the same `src` and see that `isCached()` returns `true` synchronously (because it uses the same cache). You can also inspect in your browser inspector to see the network calls. – Lucas Basquerotto Jun 14 '18 at 12:00
  • @ArseniiFomin Just to complement what I said earlier, the javascript native Image uses the browser default caching: [https://stackoverflow.com/a/10240297/4850646](https://stackoverflow.com/a/10240297/4850646) – Lucas Basquerotto Jun 14 '18 at 12:14
  • so all caching is done internally in Image class implementation? – Arsenii Fomin Jun 15 '18 at 16:02
  • The javascript `Image` uses the browser cache. From the [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image): The `Image()` constructor creates a new `HTMLImageElement` instance. It is functionally equivalent to `document.createElement('img')`. – Lucas Basquerotto Jun 15 '18 at 17:42
  • what if I want to pass down css class names to the img element? – devmiles.com Oct 02 '18 at 12:06
  • @devmiles.com If it will be in a code that you have control and can change (like a css file that you can modify), I advise to include the class in the `my-img` element and make the css rule like `my-img.my-class img { ... }`. If you really need to include the class in the `img` element, then you could change the component to have a `@Input()` that receives the class and pass it to the 4 `img` elements in the html template (like ``). – Lucas Basquerotto Oct 02 '18 at 17:54
1
<img class="thumbnail-image" src="getImage()" alt="...">

getImage():string{  //I don't know how would you handle your situation here. But you can think of it.

  if (this.validImage) // I don't know how would you manage validImage here.
  {
     return this.validImagePath;
  }

   return this.placeholderImagePath;
}
micronyks
  • 54,797
  • 15
  • 112
  • 146
0
src="validImage ? validImageUrl : placeHolderImgUrl"
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
0

I Just did this :

In my HTML FILE wrote this

<div 
   (click)="getInfo(results[movie].id)"
   *ngFor="let movie of (results | key:'10')" 
    class="card" style="margin-top:7%;">
 <img [src]="getImage(results[movie])" alt="" class="card-img-top pointer"></div>

I called a function that is in my component.ts and pass the object wheres my url as a parameter

getImage(result){
if(result.poster_path){
  return this.imageURL+(result.poster_path);
}else return "./assets/noFound.jpg"

Heres my function , first I verify if the object image url is different from null if is true then I return the image url else i return my default "noFound" image that is in my app assets.
Hope it helps!

0

You can use property binding and OR operator to do this without generating a 404 error in the console. Just make sure to provide the exact location for the placeholder image.

<img class="thumbnail-image" [src]="validImage || '/assets/images/placeholder.jpg'" alt="...">

In here, the validImage property should provide the dynamic image. If it's not available, it uses the placeholder image.

FelixDracul
  • 111
  • 6