Search code examples
angulartypescriptangular-directive

Angular directive: Dynamic change img background on hover


Looking at the screenshot, I have a background image for the page and two thumbnails below the text. I want to be able to change the background image of the page to the image of the thumbnail when the mouse hovers the thumbnail.

Below is my directive. I can get the src of the thumbnail but I am stuck as to how I could inject that to the element so the page background changes. Also I am already getting the error Expected 2 arguments, but got 1. from the ViewChild line.

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

@Directive({
  selector: '[dynamicBackgroundImg]'
})
export class DynamicBackgroundImgDirective {
  thumbSRC : string;
  @ViewChild('tourBackgroundImg') tourBackgroundImg:ElementRef;


  constructor(private el: ElementRef) {}

  @HostListener('mouseover') onMouseOver() {
    this.ChangeBackgroundImg();
  }

  @HostListener('mouseleave') onMouseLeave() {

  }

  ChangeBackgroundImg() {
    this.thumbSRC = this.el.nativeElement.getAttribute('src');
    alert(this.thumbSRC);
    this.tourBackgroundImg.nativeElement.setAttribute(this.thumbImgSrc);
  }

}

Here is my minified version of my HTML:

<section class="tours-section" [ngClass]="{ 'load-complete' : viewPage }">
  <img class="component-loader" src="../../../assets/icons/loading.svg">
  <div class="tours-wrapper">
    <div class="tours-right page-title">
      <p>{{pageTitle}}</p>
      <picture>
        <source srcset="{{ toursImageUrl }}?fm=webp" type="image/webp">
        <source srcset="{{ toursImageUrl }}?fm=png" type="image/png">
         <!-- Here is the tag that sets the background img of the page, when one hover over the thumnail, this needs to change, the src of the thumb needs to be injected here. -->
        <img src="{{ toursImageUrl }}?fm=png" alt="{{ toursImageAlt }}" (load)="imageLoaded()" class="section-background" tourBackgroundImg>
      </picture>
    </div>
    <div class="tours-left">
        <div class="tours-thumbs">
          <div class="tours-container">
            <!-- Ideally when one hovers over the thumnail, it would get the src of the tour-image -->
            <figure class="tour-image-small">
              <picture>
                <img src="assets/images/L1000433-min.JPG" alt="" dynamicBackgroundImg>
                <figcaption></figcaption>
              </picture>
            </figure>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>

Below is a screenshot that I hope will help you understand what I am trying to achieve:

enter image description here

Chances are I am approaching the whole thing wrongly, any input is appreciated.


Solution

  • There is no need to use @ViewChild in your directive.

    The following code should work:

    import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
    
    @Directive({
      selector: '[dynamicBackgroundImg]'
    })
    export class DynamicBackgroundImgDirective {
      // use the @Input to pass the target image
      @Input() target: HTMLImageElement;
    
      constructor(private el: ElementRef<HTMLImageElement>, private renderer: Renderer2) {}
    
      @HostListener('mouseover') onMouseOver() {
        this.ChangeBackgroundImg();
      }
    
      @HostListener('mouseleave') onMouseLeave() {
    
      }
    
      ChangeBackgroundImg() {
         // use the Renderer2 service to set the attribute
         this.renderer.setAttribute(this.target, 'src', this.el.nativeElement.src);
         // Add fade-in css class
        this.renderer.addClass(this.target, 'fade-in');
      }
    
    }
    

    In the component template:

    <- add a template variable for the image that you want to display it ->
    <img src="{{ toursImageUrl }}?fm=png" alt="{{ toursImageAlt }}" (load)="imageLoaded()" class="section-background" #tourBackgroundImg>
    
    ....
    
    <- When you hover this image the tourBackgroundImage will change the source ->
    <img src="assets/images/L1000433-min.JPG" dynamicBackgroundImg [target]="tourBackgroundImg">
    
    

    The error Expected 2 arguments, but got 1 from the ViewChild line makes me believe that your working with Angular 8. If so, you need to pass a configuration object in the @ViewChild like so:

    @ViewChild('element', { static: false })
    

    If it's used in OnInit life cycle hook, the static property must be true, else false. In previous versions as well in Angular 9, the second argument is optional unless it's not used in ngOnInit.

    Update - animation

    For animating the image you can use Angular animation or simple css like below:

    .fade-in {
      animation: fadeIn ease 1s;
    }
    
    @keyframes fadeIn {
      0% {
        opacity: 0;
      }
      100% {
        opacity: 1;
      }
    }
    

    I've added in the directive the code for adding the fade-in class when the thumbnail is hovered.