From 7a54966361ddd508658772d1085c6f2f012450aa Mon Sep 17 00:00:00 2001 From: Heo Sangmin Date: Wed, 15 Aug 2018 13:48:01 +0900 Subject: [PATCH] refactor(slick): improve destroy/init performance via ngAfterViewChecked lazy process & remove initSlidesLength input --- README.MD | 1 - package.json | 2 +- src/package.json | 2 +- src/slick.component.ts | 338 ++++++++++++++++++++++------------------- 4 files changed, 181 insertions(+), 162 deletions(-) diff --git a/README.MD b/README.MD index 75a0230..6b4d0e8 100644 --- a/README.MD +++ b/README.MD @@ -79,7 +79,6 @@ Once your library is imported, you can use its components, directives and pipes SlickCarouselComponent), - multi: true - }], - template: '', + selector: 'ngx-slick-carousel', + exportAs: 'slick-carousel', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SlickCarouselComponent), + multi: true + }], + template: '', }) -export class SlickCarouselComponent implements AfterViewInit, OnDestroy { - - @Input() config: any; - @Input() initSlidesLength: number; - @Output() afterChange: EventEmitter = new EventEmitter(); - @Output() beforeChange: EventEmitter = new EventEmitter(); - @Output() breakpoint: EventEmitter = new EventEmitter(); - @Output() destroy: EventEmitter = new EventEmitter(); - @Output() init: EventEmitter = new EventEmitter(); - - - public slides: any = []; - public $instance: any; - public currentIndex: number; - private initialized: Boolean = false; - - /** - * Constructor - */ - constructor(private el: ElementRef, - private zone: NgZone) { - - } - - /** - * On component destroy - */ - ngOnDestroy() { - this.unslick(); - } - - /** - * On component view init - */ - ngAfterViewInit() { - } - - /** - * init slick - */ - initSlick() { - this.zone.runOutsideAngular(() => { - this.$instance = jQuery(this.el.nativeElement); - - this.$instance.on('init', (event, slick) => { - this.zone.run(() => { - this.init.emit({event, slick}); +export class SlickCarouselComponent implements OnDestroy, AfterViewChecked { + + @Input() config: any; + @Output() afterChange: EventEmitter = new EventEmitter(); + @Output() beforeChange: EventEmitter = new EventEmitter(); + @Output() breakpoint: EventEmitter = new EventEmitter(); + @Output() destroy: EventEmitter = new EventEmitter(); + @Output() init: EventEmitter = new EventEmitter(); + + + public slides: any[] = []; + private _removedSlides: SlickItemDirective[] = []; + private _addedSlides: SlickItemDirective[] = []; + public $instance: any; + public currentIndex: number; + private initialized: Boolean = false; + + /** + * Constructor + */ + constructor(private el: ElementRef, + private zone: NgZone) { + + } + + /** + * On component destroy + */ + ngOnDestroy() { + this.unslick(); + } + + /** + * On component view checked + */ + ngAfterViewChecked() { + if (this._addedSlides.length > 0 || this._removedSlides.length > 0) { + const nextSlidesLength = this.slides.length - this._removedSlides.length + this._addedSlides.length; + if (!this.initialized) { + if (nextSlidesLength > 0) { + this.initSlick(); + } + // if nextSlidesLength is zere, do nothing + } else if (nextSlidesLength === 0) { // unslick case + this.unslick(); + } else { + this._addedSlides.forEach(slickItem => { + this.slides.push(slickItem); + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickAdd', slickItem.el.nativeElement); + }); + }); + this._addedSlides = []; + + this._removedSlides.forEach(slickItem => { + const idx = this.slides.indexOf(slickItem); + this.slides = this.slides.filter(s => s !== slickItem); + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickRemove', idx); + }); + }); + this._removedSlides = []; + } + } + } + + /** + * init slick + */ + initSlick() { + this.slides = this._addedSlides; + this._addedSlides = []; + this._removedSlides = []; + this.zone.runOutsideAngular(() => { + this.$instance = jQuery(this.el.nativeElement); + + this.$instance.on('init', (event, slick) => { + this.zone.run(() => { + this.init.emit({event, slick}); + }); + }); + + this.$instance.slick(this.config); + + this.zone.run(() => { + this.initialized = true; + + this.currentIndex = (this.config && this.config.initialSlide) ? this.config.initialSlide : 0; + }); + + this.$instance.on('afterChange', (event, slick, currentSlide) => { + this.zone.run(() => { + this.afterChange.emit({event, slick, currentSlide}); + this.currentIndex = currentSlide; + }); + }); + + this.$instance.on('beforeChange', (event, slick, currentSlide, nextSlide) => { + this.zone.run(() => { + this.beforeChange.emit({event, slick, currentSlide, nextSlide}); + }); + }); + + this.$instance.on('breakpoint', (event, slick, breakpoint) => { + this.zone.run(() => { + this.breakpoint.emit({event, slick, breakpoint}); + }); + }); + + this.$instance.on('destroy', (event, slick) => { + this.zone.run(() => { + this.destroy.emit({event, slick}); + }); + }); }); - }); + } - this.$instance.slick(this.config); + addSlide(slickItem: SlickItemDirective) { + this._addedSlides.push(slickItem); + } - this.zone.run(() => { - this.initialized = true; + removeSlide(slickItem: SlickItemDirective) { + this._removedSlides.push(slickItem); + } - this.currentIndex = (this.config && this.config.initialSlide) ? this.config.initialSlide : 0; - }); + /** + * Slick Method + */ + public slickGoTo(index: number) { + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickGoTo', index); + }); + } - this.$instance.on('afterChange', (event, slick, currentSlide) => { - this.zone.run(() => { - this.afterChange.emit({event, slick, currentSlide}); - this.currentIndex = currentSlide; + public slickNext() { + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickNext'); }); - }); + } - this.$instance.on('beforeChange', (event, slick, currentSlide, nextSlide) => { - this.zone.run(() => { - this.beforeChange.emit({event, slick, currentSlide, nextSlide}); + public slickPrev() { + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickPrev'); }); - }); + } - this.$instance.on('breakpoint', (event, slick, breakpoint) => { - this.zone.run(() => { - this.breakpoint.emit({event, slick, breakpoint}); + public slickPause() { + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickPause'); }); - }); + } - this.$instance.on('destroy', (event, slick) => { - this.zone.run(() => { - this.destroy.emit({event, slick}); + public slickPlay() { + this.zone.runOutsideAngular(() => { + this.$instance.slick('slickPlay'); }); - }); - }); - } - - addSlide(slickItem: SlickItemDirective) { - this.slides.push(slickItem); - - if (!this.initialized) { - // Wait for adding slides as much as initSlidesLength if it defined - if (!this.initSlidesLength || this.initSlidesLength === this.slides.length) { - this.initSlick(); - } - return; } - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickAdd', slickItem.el.nativeElement); - }); - } - - removeSlide(slickItem: SlickItemDirective) { - const idx = this.slides.indexOf(slickItem); - this.slides = this.slides.filter(s => s !== slickItem); - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickRemove', idx); - }); - } - - /** - * Slick Method - */ - public slickGoTo(index: number) { - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickGoTo', index); - }); - } - - public slickNext() { - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickNext'); - }); - } - - public slickPrev() { - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickPrev'); - }); - } - - public slickPause() { - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickPause'); - }); - } - - public slickPlay() { - this.zone.runOutsideAngular(() => { - this.$instance.slick('slickPlay'); - }); - } - - public unslick() { - if (this.$instance) { - this.zone.runOutsideAngular(() => { - this.$instance.slick('unslick'); - }); + public unslick() { + if (this.$instance) { + this.zone.runOutsideAngular(() => { + this.$instance.slick('unslick'); + }); + } + this.initialized = false; + this.slides = []; + this._addedSlides = []; + this._removedSlides = []; } - this.initialized = false; - } } @Directive({ - selector: '[ngxSlickItem]', + selector: '[ngxSlickItem]', }) -export class SlickItemDirective implements AfterViewInit, OnDestroy { - constructor(public el: ElementRef, - @Inject(PLATFORM_ID) private platformId: string, - @Host() private carousel: SlickCarouselComponent) { - } - - ngAfterViewInit() { - if (isPlatformBrowser(this.platformId)) { - this.carousel.addSlide(this); +export class SlickItemDirective implements OnInit, OnDestroy { + constructor(public el: ElementRef, + @Inject(PLATFORM_ID) private platformId: string, + @Host() private carousel: SlickCarouselComponent) { + } + + ngOnInit() { + if (isPlatformBrowser(this.platformId)) { + this.carousel.addSlide(this); + } } - } - ngOnDestroy() { - if (isPlatformBrowser(this.platformId)) { - this.carousel.removeSlide(this); + ngOnDestroy() { + if (isPlatformBrowser(this.platformId)) { + this.carousel.removeSlide(this); + } } - } }