import { Component , ElementRef , EventEmitter , Inject , Input , Output , PLATFORM_ID , Renderer2 , ViewChild , DoCheck , NgZone , OnInit , OnDestroy , ChangeDetectionStrategy , ChangeDetectorRef , OnChanges , SimpleChanges , AfterViewInit } from '@angular/core' ; import { isPlatformServer , DOCUMENT } from '@angular/common' ; import { ISlide } from './ISlide' ; import { IImage } from './IImage' ; import { DomSanitizer , TransferState , makeStateKey , SafeStyle } from '@angular/platform-browser' ; import { PointerService } from './pointer.service' ; import { Subscription } from 'rxjs' ; const FIRST_SLIDE_KEY = makeStateKey < any > ( 'firstSlide' ) ; @ Component ( { selector : 'slideshow' , templateUrl : './slideshow.component.html' , styleUrls : [ './slideshow.component.scss' ] , providers : [ PointerService ] , changeDetection : ChangeDetectionStrategy . OnPush } ) export class SlideshowComponent implements OnInit , AfterViewInit , DoCheck , OnChanges , OnDestroy { slideIndex : number = - 1 ; slides : ISlide [ ] = [ ] ; hideLeftArrow : boolean = false ; hideRightArrow : boolean = false ; private _urlCache : ( string | IImage ) [ ] ; private _autoplayIntervalId : any ; private _initial : boolean = true ; private _isHidden : boolean = false ; private _slideSub : Subscription ; private _clickSub : Subscription ; @ Input ( ) imageUrls : ( string | IImage ) [ ] = [ ] ; @ Input ( ) height : string = '100%' ; @ Input ( ) minHeight : string ; @ Input ( ) arrowSize : string ; @ Input ( ) showArrows : boolean = true ; @ Input ( ) disableSwiping : boolean = false ; @ Input ( ) autoPlay : boolean = false ; @ Input ( ) autoPlayInterval : number = 3333 ; @ Input ( ) stopAutoPlayOnSlide : boolean = true ; @ Input ( ) autoPlayWaitForLazyLoad : boolean = true ; @ Input ( ) debug : boolean ; @ Input ( ) backgroundSize : string = 'cover' ; @ Input ( ) backgroundPosition : string = 'center center' ; @ Input ( ) backgroundRepeat : string = 'no-repeat' ; @ Input ( ) showDots : boolean = false ; @ Input ( ) dotColor : string = '#FFF' ; @ Input ( ) showCaptions : boolean = true ; @ Input ( ) captionColor : string = '#FFF' ; @ Input ( ) captionBackground : string = 'rgba(0, 0, 0, .35)' ; @ Input ( ) lazyLoad : boolean = false ; @ Input ( ) hideOnNoSlides : boolean = false ; @ Input ( ) fullscreen : boolean = false ; @ Input ( ) enableZoom : boolean = false ; @ Input ( ) enablePan : boolean = false ; @ Input ( ) noLoop : boolean = false ; @ Output ( ) onSlideLeft = new EventEmitter < number > ( ) ; @ Output ( ) onSlideRight = new EventEmitter < number > ( ) ; @ Output ( ) onSwipeLeft = new EventEmitter < number > ( ) ; @ Output ( ) onSwipeRight = new EventEmitter < number > ( ) ; @ Output ( ) onFullscreenExit = new EventEmitter < boolean > ( ) ; @ Output ( ) onIndexChanged = new EventEmitter < number > ( ) ; @ Output ( ) onImageLazyLoad = new EventEmitter < ISlide > ( ) ; @ Output ( ) onClick = new EventEmitter < { slide : ISlide ; index : number ; } > ( ) ; @ ViewChild ( 'container' ) container : ElementRef ; @ ViewChild ( 'prevArrow' ) prevArrow : ElementRef ; @ ViewChild ( 'nextArrow' ) nextArrow : ElementRef ; get safeStyleDotColor ( ) : SafeStyle { return this . sanitizer . bypassSecurityTrustStyle ( `--dot-color: ${ this . dotColor }` ) ; } constructor ( private _pointerService , private _renderer , private _transferState , private _ngZone , private _cdRef , public sanitizer , @ Inject ( PLATFORM_ID ) private platform_id , @ Inject ( DOCUMENT ) private document ) { } ngOnInit ( ) { if ( this . debug !== undefined ) { console . warn ( '[Deprecation Warning]: The debug input will be removed from ng-simple-slideshow in 1.3.0' ) ; } this . _slideSub = this . _pointerService . slideEvent . subscribe ( ( indexDirection ) => { this . onSlide ( indexDirection , true ) ; } ) ; this . _clickSub = this . _pointerService . clickEvent . subscribe ( ( ) => { this . _onClick ( ) ; } ) ; if ( this . noLoop ) { this . hideLeftArrow = true ; } } ngAfterViewInit ( ) { this . _pointerService . bind ( this . container ) ; } ngOnDestroy ( ) { try { if ( this . _slideSub && ! this . _slideSub . closed ) { this . _slideSub . unsubscribe ( ) ; } } catch ( error ) { console . warn ( 'Slide Subscription error caught in ng-simple-slideshow OnDestroy:' , error ) ; } try { if ( this . _clickSub && ! this . _clickSub . closed ) { this . _clickSub . unsubscribe ( ) ; } } catch ( error ) { console . warn ( 'Click Subscription error caught in ng-simple-slideshow OnDestroy:' , error ) ; } try { this . _pointerService . unbind ( this . container ) ; } catch ( error ) { console . warn ( 'Pointer Service unbind error caught in ng-simple-slideshow OnDestroy:' , error ) ; } try { if ( this . _autoplayIntervalId ) { this . _ngZone . runOutsideAngular ( ( ) => clearInterval ( this . _autoplayIntervalId ) ) ; this . _autoplayIntervalId = null ; } } catch ( error ) { console . warn ( 'Autoplay cancel error caught in ng-simple-slideshow OnDestroy:' , error ) ; } } ngOnChanges ( changes ) { if ( changes [ 'noLoop' ] ) { if ( changes [ 'noLoop' ] . currentValue ) { this . hideLeftArrow = this . slideIndex <= 0 ; this . hideRightArrow = this . slideIndex === this . slides . length - 1 ; } else { this . hideLeftArrow = false ; this . hideRightArrow = false ; } this . _cdRef . detectChanges ( ) ; } } ngDoCheck ( ) { if ( this . imageUrls && this . imageUrls . length > 0 ) { if ( this . _initial === true ) { this . _urlCache = Array . from ( this . imageUrls ) ; } if ( this . _isHidden === true ) { this . _renderer . removeStyle ( this . container . nativeElement , 'display' ) ; this . _isHidden = false ; } this . setSlides ( ) ; } else if ( this . hideOnNoSlides === true ) { this . _renderer . setStyle ( this . container . nativeElement , 'display' , 'none' ) ; this . _isHidden = true ; } this . setStyles ( ) ; this . handleAutoPlay ( ) ; this . _pointerService . disableSwiping = this . disableSwiping ; this . _pointerService . enableZoom = this . enableZoom ; this . _pointerService . enablePan = this . enablePan ; } param { number } indexDirection param { boolean } isSwipe description onSlide ( indexDirection , isSwipe ? ) { this . handleAutoPlay ( this . stopAutoPlayOnSlide ) ; this . slide ( indexDirection , isSwipe ) ; } description private _onClick ( ) { const currentSlide = this . slides . length > 0 && this . slides [ this . slideIndex ] ; this . onClick . emit ( { slide : currentSlide , index : this . slideIndex } ) ; if ( currentSlide && currentSlide . image . clickAction ) { currentSlide . image . clickAction ( ) ; } else if ( currentSlide && currentSlide . image . href ) { this . document . location . href = currentSlide . image . href ; } } param { number } index description goToSlide ( index ) { const beforeClickIndex = this . slideIndex ; this . slideIndex = index - 1 ; this . setSlideIndex ( 1 ) ; if ( this . slides [ this . slideIndex ] && ! this . slides [ this . slideIndex ] . loaded ) { this . loadRemainingSlides ( ) ; } this . handleAutoPlay ( this . stopAutoPlayOnSlide ) ; this . slideRight ( beforeClickIndex ) ; this . slides [ beforeClickIndex ] . selected = false ; this . slides [ this . slideIndex ] . selected = true ; this . _cdRef . detectChanges ( ) ; } param { number } index description getSlideStyle ( index ) { const slide = this . slides [ index ] ; if ( slide && slide . loaded ) { return { "background-image" : 'url(' + slide . image . url + ')' , "background-size" : slide . image . backgroundSize || this . backgroundSize , "background-position" : slide . image . backgroundPosition || this . backgroundPosition , "background-repeat" : slide . image . backgroundRepeat || this . backgroundRepeat } ; } else { return { "background-image" : undefined , "background-size" : undefined , "background-position" : undefined , "background-repeat" : undefined } ; } } exitFullScreen ( e ) { e . preventDefault ( ) ; this . fullscreen = false ; this . onFullscreenExit . emit ( true ) ; } param { number } indexDirection param { boolean } isSwipe description private slide ( indexDirection , isSwipe ? ) { const oldIndex = this . slideIndex ; if ( this . setSlideIndex ( indexDirection ) ) { if ( this . slides [ this . slideIndex ] && ! this . slides [ this . slideIndex ] . loaded ) { this . loadRemainingSlides ( ) ; } if ( indexDirection === 1 ) { this . slideRight ( oldIndex , isSwipe ) ; } else { this . slideLeft ( oldIndex , isSwipe ) ; } this . slides [ oldIndex ] . selected = false ; this . slides [ this . slideIndex ] . selected = true ; } this . _cdRef . detectChanges ( ) ; } param { number } indexDirection description private setSlideIndex ( indexDirection ) { let willChange = true ; this . slideIndex += indexDirection ; if ( this . noLoop ) { this . hideRightArrow = this . slideIndex === this . slides . length - 1 ; this . hideLeftArrow = false ; } if ( this . slideIndex < 0 ) { if ( this . noLoop ) { this . slideIndex -= indexDirection ; willChange = false ; this . hideLeftArrow = true ; } else { this . slideIndex = this . slides . length - 1 ; } } else if ( this . slideIndex >= this . slides . length ) { if ( this . noLoop ) { this . slideIndex -= indexDirection ; willChange = false ; this . hideRightArrow = true ; } else { this . slideIndex = 0 ; } } if ( willChange ) { this . onIndexChanged . emit ( this . slideIndex ) ; } return willChange ; } param { number } oldIndex param { boolean } isSwipe description private slideLeft ( oldIndex , isSwipe ? ) { if ( isSwipe === true ) { this . onSwipeLeft . emit ( this . slideIndex ) ; } else { this . onSlideLeft . emit ( this . slideIndex ) ; } this . slides [ this . getLeftSideIndex ( oldIndex ) ] . leftSide = false ; this . slides [ oldIndex ] . leftSide = true ; this . slides [ oldIndex ] . action = 'slideOutLeft' ; this . slides [ this . slideIndex ] . rightSide = false ; this . slides [ this . getRightSideIndex ( ) ] . rightSide = true ; this . slides [ this . slideIndex ] . action = 'slideInRight' ; } param { number } oldIndex param { boolean } isSwipe description private slideRight ( oldIndex , isSwipe ? ) { if ( isSwipe === true ) { this . onSwipeRight . emit ( this . slideIndex ) ; } else { this . onSlideRight . emit ( this . slideIndex ) ; } this . slides [ this . getRightSideIndex ( oldIndex ) ] . rightSide = false ; this . slides [ oldIndex ] . rightSide = true ; this . slides [ oldIndex ] . action = 'slideOutRight' ; this . slides [ this . slideIndex ] . leftSide = false ; this . slides [ this . getLeftSideIndex ( ) ] . leftSide = true ; this . slides [ this . slideIndex ] . action = 'slideInLeft' ; } description private setSlides ( ) { if ( this . imageUrls ) { if ( this . checkCache ( ) || this . _initial === true ) { this . _initial = false ; this . _urlCache = Array . from ( this . imageUrls ) ; this . slides = [ ] ; if ( this . lazyLoad === true ) { this . buildLazyLoadSlideArray ( ) ; } else { this . buildSlideArray ( ) ; } this . _cdRef . detectChanges ( ) ; } } } description private buildLazyLoadSlideArray ( ) { for ( let image of this . imageUrls ) { this . slides . push ( { image : ( typeof image === 'string' ? { url : null } : { url : null , href : image . href || '' } ) , action : '' , leftSide : false , rightSide : false , selected : false , loaded : false } ) ; } if ( this . slideIndex === - 1 ) { this . slideIndex = 0 ; } this . slides [ this . slideIndex ] . selected = true ; this . loadFirstSlide ( ) ; this . onIndexChanged . emit ( this . slideIndex ) ; } description private buildSlideArray ( ) { for ( let image of this . imageUrls ) { this . slides . push ( { image : ( typeof image === 'string' ? { url : image } : image ) , action : '' , leftSide : false , rightSide : false , selected : false , loaded : true } ) ; } if ( this . slideIndex === - 1 ) { this . slideIndex = 0 ; } this . slides [ this . slideIndex ] . selected = true ; this . onIndexChanged . emit ( this . slideIndex ) ; } description private loadFirstSlide ( ) { const tmpIndex = this . slideIndex ; const tmpImage = this . imageUrls [ tmpIndex ] ; if ( isPlatformServer ( this . platform_id ) ) { this . slides [ tmpIndex ] . image = ( typeof tmpImage === 'string' ? { url : tmpImage } : tmpImage ) ; this . slides [ tmpIndex ] . loaded = true ; this . _transferState . set ( FIRST_SLIDE_KEY , this . slides [ tmpIndex ] ) ; } else { const firstSlideFromTransferState = this . _transferState . get ( FIRST_SLIDE_KEY , null as any ) ; if ( firstSlideFromTransferState === null ) { let loadImage = new Image ( ) ; loadImage . src = ( typeof tmpImage === 'string' ? tmpImage : tmpImage . url ) ; loadImage . addEventListener ( 'load' , ( ) => { this . slides [ tmpIndex ] . image = ( typeof tmpImage === 'string' ? { url : tmpImage } : tmpImage ) ; this . slides [ tmpIndex ] . loaded = true ; this . onImageLazyLoad . emit ( this . slides [ tmpIndex ] ) ; this . _cdRef . detectChanges ( ) ; } ) ; } else { this . slides [ tmpIndex ] = firstSlideFromTransferState ; this . _transferState . remove ( FIRST_SLIDE_KEY ) ; } } } description todo private loadRemainingSlides ( ) { for ( let i = 0 ; i < this . slides . length ; i ++ ) { if ( ! this . slides [ i ] . loaded ) { new Promise ( ( resolve ) => { const tmpImage = this . imageUrls [ i ] ; let loadImage = new Image ( ) ; loadImage . addEventListener ( 'load' , ( ) => { this . slides [ i ] . image = ( typeof tmpImage === 'string' ? { url : tmpImage } : tmpImage ) ; this . slides [ i ] . loaded = true ; this . _cdRef . detectChanges ( ) ; this . onImageLazyLoad . emit ( this . slides [ i ] ) ; resolve ( ) ; } ) ; loadImage . src = ( typeof tmpImage === 'string' ? tmpImage : tmpImage . url ) ; } ) ; } } } param { boolean } stopAutoPlay description private handleAutoPlay ( stopAutoPlay ? ) { if ( isPlatformServer ( this . platform_id ) ) { return ; } if ( stopAutoPlay === true || this . autoPlay === false ) { if ( this . _autoplayIntervalId ) { this . _ngZone . runOutsideAngular ( ( ) => clearInterval ( this . _autoplayIntervalId ) ) ; this . _autoplayIntervalId = null ; } } else if ( ! this . _autoplayIntervalId ) { this . _ngZone . runOutsideAngular ( ( ) => { this . _autoplayIntervalId = setInterval ( ( ) => { if ( ! this . autoPlayWaitForLazyLoad || ( this . autoPlayWaitForLazyLoad && this . slides [ this . slideIndex ] && this . slides [ this . slideIndex ] . loaded ) ) { this . _ngZone . run ( ( ) => this . slide ( 1 ) ) ; } } , this . autoPlayInterval ) ; } ) ; } } description private setStyles ( ) { if ( this . fullscreen ) { this . _renderer . setStyle ( this . container . nativeElement , 'height' , '100%' ) ; this . _renderer . setStyle ( this . container . nativeElement , 'background-color' , 'white' ) ; } else { this . _renderer . removeStyle ( this . container . nativeElement , 'background-color' ) ; if ( this . height ) { this . _renderer . setStyle ( this . container . nativeElement , 'height' , this . height ) ; } if ( this . minHeight ) { this . _renderer . setStyle ( this . container . nativeElement , 'min-height' , this . minHeight ) ; } } if ( this . arrowSize ) { this . _renderer . setStyle ( this . prevArrow . nativeElement , 'height' , this . arrowSize ) ; this . _renderer . setStyle ( this . prevArrow . nativeElement , 'width' , this . arrowSize ) ; this . _renderer . setStyle ( this . nextArrow . nativeElement , 'height' , this . arrowSize ) ; this . _renderer . setStyle ( this . nextArrow . nativeElement , 'width' , this . arrowSize ) ; } } description private checkCache ( ) { return ! ( this . _urlCache . length === this . imageUrls . length && this . _urlCache . every ( ( cacheElement , i ) => cacheElement === this . imageUrls [ i ] ) ) ; } param { number } i returns { number } description private getLeftSideIndex ( i ? ) { if ( i === undefined ) { i = this . slideIndex ; } if ( -- i < 0 ) { i = this . slides . length - 1 ; } return i ; } param { number } i returns { number } description private getRightSideIndex ( i ? ) { if ( i === undefined ) { i = this . slideIndex ; } if ( ++ i >= this . slides . length ) { i = 0 ; } return i ; } param { number } index param { ISlide } slide returns { any } description trackByFn ( index , slide ) { return slide . image ; } description handleClick ( event ) { event . preventDefault ( ) ; } }