How to overwrite data-bound-property of HTML-Element with Angular directive

50 views Asked by At

I am trying to implement a fallback image on an img-element in angular. Therefor i have created a directive that listens for errors on the img and replaces its source with the fallback image whenever an error occurs.

Now the original src attribute of my image is set via data binding which results in a permanent loop of changing the src back and forth between the original and the fallback. I assume that is because the data-binding keeps on changing the src back.

My template:

<img [src]="sanitizer.bypassSecurityTrustResourceUrl(building + url + with + variables)"
  fallbackImage="path/to/fallback.svg">

My Directive:

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

@Directive({
    selector: 'img[fallbackImage]'
})
export class ImgFallbackDirective {

    constructor(private element: ElementRef) {}

    @Input('fallbackImage') fallbackImage: string;

    @HostListener('error') displayFallbackImage() {
        console.log('Before: ', this.element.nativeElement.src);
        this.element.nativeElement.src = this.fallbackImage || '';
        console.log('After: ', this.element.nativeElement.src);
    }
}

The logs document the src being changed back and forth.

1

There are 1 answers

0
Naren Murali On BEST ANSWER

We can insert an adjacent element( img ) with the fallback src set, Then we can delete the element on which the directive exists, this works great!

@HostListener('error') displayFallbackImage() {
    if (this.isFirstLoad) {
      this.isFirstLoad = false;
      var img = document.createElement('img');
      img.src = this.fallbackImage;
      this.renderer.appendChild(this.element.nativeElement.parentNode, img);
      this.renderer.removeChild(
        this.element.nativeElement.parentNode,
        this.element.nativeElement,
        true
      );
    }
  }

full code

main.ts

import { Component } from '@angular/core';
import { bootstrapApplication, DomSanitizer } from '@angular/platform-browser';
import 'zone.js';
import { ImgFallbackDirective } from './test.directive';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ImgFallbackDirective],
  template: `
    <img [src]="sanitizer.bypassSecurityTrustResourceUrl(failingUrl)"
  fallbackImage="https://placehold.co/600x400">
  `,
})
export class App {
  failingUrl = 'https://placeeeeeeeeeehold.co/600x400';

  constructor(public sanitizer: DomSanitizer) {}

  setFailingUrl(value: any) {
    console.log(value);
    this.failingUrl = value;
  }
}

bootstrapApplication(App);

directive

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

@Directive({
  selector: 'img[fallbackImage]',
  standalone: true,
})
export class ImgFallbackDirective {
  isFirstLoad = true;
  constructor(private element: ElementRef, private renderer: Renderer2) {}

  @Input('fallbackImage') fallbackImage!: string;

  @HostListener('error') displayFallbackImage() {
    if (this.isFirstLoad) {
      this.isFirstLoad = false;
      var img = document.createElement('img');
      img.src = this.fallbackImage;
      this.renderer.appendChild(this.element.nativeElement.parentNode, img);
      this.renderer.removeChild(
        this.element.nativeElement.parentNode,
        this.element.nativeElement,
        true
      );
    }
  }
}

Stackblitz Demo