mehmet-erim/ngx-slickjs

View on GitHub
src/lib/directives/slick-container.directive.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { Directive, ElementRef, EventEmitter, Input, NgZone, OnDestroy, Output, Inject } from '@angular/core';
import compare from 'just-compare';
import { take, map, filter, switchMap, takeUntil } from 'rxjs/operators';
import { Options } from '../models/options';
import { Slick } from '../models/slick';
import { timer, forkJoin, fromEvent, Subject } from 'rxjs';
import { LazyLoadService } from '../services/lazy-load.service';

declare const $: any;

@Directive({
  selector: '[slickContainer]',
  exportAs: 'slick',
})
export class SlickContainerDirective implements OnDestroy {
  @Input('slickConfig')
  config: Slick.Config;

  @Output('slickAfterChange')
  afterChange: EventEmitter<any> = new EventEmitter();

  @Output('slickBeforeChange')
  beforeChange: EventEmitter<any> = new EventEmitter();

  @Output('slickBreakpoint')
  breakpoint: EventEmitter<any> = new EventEmitter();

  @Output('slickDestroy')
  destroy: EventEmitter<any> = new EventEmitter();

  @Output('slickInit')
  init: EventEmitter<any> = new EventEmitter();

  slides: HTMLElement[] = [];

  jQueryElement: any;

  initialize: boolean = false;

  slickCount: number = 0;

  private destroy$ = new Subject();

  constructor(
    private elRef: ElementRef,
    private zone: NgZone,
    private lazyLoadService: LazyLoadService,
    @Inject('slick-links') private links: Options.Links,
  ) {}

  ngAfterViewInit() {
    this.lazyLoadService
      .load(this.links.jquery, 'script')
      .pipe(
        map(() => 'jQuery is loaded'),
        filter(jquery => !!jquery),
        switchMap(() =>
          forkJoin(
            this.lazyLoadService.load(this.links.slickJs, 'script'),
            this.lazyLoadService.load(this.links.slickCss, 'style'),
            this.lazyLoadService.load(this.links.slickThemeCss, 'style'),
          ),
        ),
        take(1),
      )
      .subscribe(() => {
        this.initSlick();
      });
  }

  ngOnDestroy() {
    this.unslick();
    this.slides = [];
    this.destroy$.next();
  }

  initSlick() {
    const that = this;
    this.zone.runOutsideAngular(() => {
      $(this.elRef.nativeElement)[0].innerHTML = '';
      this.jQueryElement = $(this.elRef.nativeElement);
      this.jQueryElement.on('init', (event, slick) => {
        this.zone.run(() => {
          timer(0).subscribe(() => this.init.emit({ event, slick }));
        });
      });

      this.jQueryElement.slick(this.config);
      this.initialize = true;
      this.listenMouseWheel();

      this.jQueryElement.on('afterChange', (event, slick, currentSlide) => {
        that.zone.run(() => {
          that.afterChange.emit({ event, slick, currentSlide });
        });
      });

      this.jQueryElement.on('beforeChange', (event, slick, currentSlide, nextSlide) => {
        that.zone.run(() => {
          this.slickCount = slick.slideCount;
          that.beforeChange.emit({ event, slick, currentSlide, nextSlide });
        });
      });

      this.jQueryElement.on('breakpoint', (event, slick, breakpoint) => {
        that.zone.run(() => {
          that.breakpoint.emit({ event, slick, breakpoint });
        });
      });

      this.jQueryElement.on('destroy', (event, slick) => {
        that.zone.run(() => {
          that.destroy.emit({ event, slick });
        });
      });
    });

    this.syncSlides();

    if (this.config && this.config.initialSlide && this.config.initialSlide <= this.slides.length) {
      this.goTo(this.config.initialSlide);
    }
  }

  syncSlides() {
    if (this.slickCount >= this.slides.length) return;

    this.slides.forEach(slide => {
      this.zone.run(() => {
        this.jQueryElement.slick('slickAdd', slide);
      });
    });
  }

  addSlide(slide: HTMLElement) {
    this.slides = [...this.slides, slide];

    if (!this.initialize) return;

    this.zone.run(() => {
      this.jQueryElement.slick('slickAdd', slide);
    });
  }

  removeSlide(slide: HTMLElement) {
    this.slides = this.slides.filter(slide => compare(slide, slide));

    if (!this.initialize) return;

    this.zone.run(() => {
      this.jQueryElement.slick('slickRemove', this.slides.indexOf(slide));
    });
  }

  goTo(index: number) {
    this.zone.run(() => {
      this.jQueryElement.slick('slickGoTo', index);
    });
  }

  next() {
    this.zone.run(() => {
      this.jQueryElement.slick('slickNext');
    });
  }

  prev() {
    this.zone.run(() => {
      this.jQueryElement.slick('slickPrev');
    });
  }

  pause() {
    this.zone.run(() => {
      this.jQueryElement.slick('slickPause');
    });
  }

  play() {
    this.zone.run(() => {
      this.jQueryElement.slick('slickPlay');
    });
  }

  unslick() {
    if (this.jQueryElement) {
      this.zone.run(() => {
        this.jQueryElement.slick('unslick');
      });
    }
    this.initialize = false;
  }

  listenMouseWheel() {
    if (this.config.mouseWheelMove === false) return;

    fromEvent(this.elRef.nativeElement, 'wheel')
      .pipe(takeUntil(this.destroy$))
      .subscribe((event: MouseWheelEvent) => {
        event.preventDefault();
        if (event.deltaX > 0 || event.deltaY < 0) {
          this.prev();
        } else if (event.deltaX < 0 || event.deltaY > 0) {
          this.next();
        }
      });
  }
}