Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gently change img src in Angular2+

Tags:

html

angular

When I dynamically change img's src attribute, old image is displayed while loading a new one.

I have a component which displays some data: text and image. On click the underlying data is changed (i.e. new data from server). Once click, text is changed immediately, but component displays old image while new one is loaded. When new image is loaded, then it is visually displayed which can take noticeable amount of time.

In real application one can have product details and changing products on button click. All data is replaced immediately but not image.

Problem exists when the component is not destroyed (reused).

I've already tried clear image src after click, but it not worked.

I have simple binding in template

img [src]="img.url" style="width: 300px; height: 300px">
<p>{{ img.text }}</p>

and image change on click

this.img = this.images[1];

You can see sample app here https://stackblitz.com/edit/angular-cojqnf

Is this possible to take more control of this image change process? It would be great to clear image on click and wait for new one with empty background for example.

like image 911
Walter Luszczyk Avatar asked Jan 01 '23 09:01

Walter Luszczyk


1 Answers

I hacked around a little with your stackblitz demo, I basically wrapped your code in an ImageGhostDirective to make it reusable. The directive listens to any changes on the src attribute using a MutationObserver to change the style. Using a HostListener on the 'load' event, it reverts the styles back to normal. I start with an opacity of 0 for the first load, followed by an opacity of 0.2 between successive image changes, but this is completely arbitrary and could be replaced by a spinner or any kind of placeholder...

Here is the link to the stackblitz: https://stackblitz.com/edit/angular-image-ghost-directive

<img [src]="'https://loremflickr.com/300/300?random=' + index"
     style="width: 300px; height: 300px" imgGhost>
@Directive({
  selector: 'img[imgGhost]'
})
export class ImageGhostDirective implements OnDestroy {
  private changes: MutationObserver;

  constructor(private elementRef: ElementRef) {
    this.changes = new MutationObserver((mutations: MutationRecord[]) =>
      mutations.filter(m => m.attributeName === 'src').forEach(() => this.opacity = 0.2)
    );

    this.changes.observe(this.elementRef.nativeElement, {
      attributes: true,
      childList: false,
      characterData: false
    });
  }

  ngOnDestroy(): void {
    this.changes.disconnect();
  }

  @HostBinding('style.display') display = 'block';
  @HostBinding('style.opacity') opacity = 0;

  @HostListener('load')
  onLoad(): void {
    this.opacity = 1;
  }
}

It is also possible to tell Angular to automatically attach this directive to every img element by using the img:not([imgGhost]) selector in the directive decorator. That way, you don't have to manually place the directive on every image in your app.

Hope this is useful.

like image 156
jo_va Avatar answered Jan 04 '23 23:01

jo_va