All files / directives upp-visible.ts

0% Statements 0/24
0% Branches 0/4
0% Functions 0/8
0% Lines 0/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                                                                                                                                                                   
import { Directive, AfterViewInit, OnDestroy } from '@angular/core';
import { Input, Output, EventEmitter, ElementRef} from '@angular/core';
 
/**
 * @directive VisibleDirective
 * @description Detects the visibility of an element in the viewport using the IntersectionObserver API.
 * Emits an event when the visibility state changes.
 *
 * This directive is useful for optimizing performance by triggering events or computations only when the element is visible.
 * 
 * @example
 * ```html
 * <div uppVisible (visibilityChange)="onVisibilityChange($event)">
 *   Observed Content
 * </div>
 * ```
 * 
 * ```typescript
 * onVisibilityChange(isVisible: boolean) {
 *   console.log('Element is visible:', isVisible);
 * }
 * ```
 */
@Directive({
    selector: '[uppVisible]'
})
export class VisibleDirective implements AfterViewInit, OnDestroy {
    private observer: IntersectionObserver | null = null;
 
    /**
     * @input threshold
     * @description Defines the intersection ratio required to trigger the visibility change.
     * Defaults to `0.1` (10% of the element must be visible to trigger the event).
     */    
    @Input() threshold = 0.1;
 
    /**
     * @output visibilityChange
     * @description Emits a boolean value indicating whether the element is visible in the viewport.
     */    
    @Output() visibilityChange: EventEmitter<boolean> = new EventEmitter();
 
    /**
     * @constructor
     * @param elementRef Reference to the DOM element associated with the directive.
     */    
    constructor(private elementRef: ElementRef) {
        // nothing to do
    }
 
    private _isvisible = false;
 
    /**
     * @property isVisible
     * @description Gets the current visibility state of the element.
     * @returns {boolean} `true` if the element is visible, otherwise `false`.
     */    
    get isVisible(): boolean {
        return this._isvisible;
    }
 
    /**
     * @private _UpdateVisibility
     * @description Updates the visibility state and emits an event if the state changes.
     * @param {boolean} isCurrentlyVisible - The new visibility state of the element.
     */    
    private _UpdateVisibility(isCurrentlyVisible: boolean) {
        Iif (this._isvisible !== isCurrentlyVisible) {
            this._isvisible = isCurrentlyVisible;
            this.visibilityChange.emit(this._isvisible);
        }
    }
 
    /**
     * @method ngAfterViewInit
     * @description Initializes the `IntersectionObserver` and checks the element's initial visibility.
     * If the element is visible upon initialization, it updates the state accordingly.
     */    
    ngAfterViewInit() {
        this.observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                this._UpdateVisibility(entry.isIntersecting);
            });
        }, { threshold: this.threshold });
 
        this.observer.observe(this.elementRef.nativeElement);
 
        setTimeout(() => {
            const _rect = this.elementRef.nativeElement.getBoundingClientRect();
            const _visible = _rect.top < window.innerHeight && _rect.bottom > 0;
            this._UpdateVisibility(_visible);
        }, 0);        
    }
 
    /**
     * @method ngOnDestroy
     * @description Cleans up and disconnects the `IntersectionObserver` when the directive is destroyed.
     * Prevents memory leaks by ensuring the observer is properly disposed of.
     */    
    ngOnDestroy() {
        Iif (this.observer) {
            this.observer.disconnect();
            this.observer = null;
        }
    }
}