OvSleep OvSleep - 1 month ago 64
Javascript Question

Slick Carousel with Angular 2

Hi I'm starting with Angular 2 and trying to make this carousel plugin to work: slick

After a while I managed to make it work like a Component like this:

import { Component, Input, ElementRef, AfterViewInit, AfterContentInit} from '@angular/core';
declare var jQuery: any;
import 'slick-slider';

@Component({
selector: 'slick-slider',
template: `
<ng-content></ng-content>
`
})
export class SlickSliderComponent implements AfterContentInit{
@Input() options: any;

$element: any;

defaultOptions: any = {};

constructor(private el: ElementRef) {
}

ngAfterContentInit() {
for (var key in this.options) {
this.defaultOptions[key] = this.options[key];
}

this.$element = jQuery(this.el.nativeElement).slick(this.defaultOptions);
}
}


And I would use it like this on a template:

<slick-slider>
<div><img class="btn btn-lg" src="/assets/img/img1.png" /></div>
<div><img class="btn btn-lg" src="/assets/img/img2.png" /></div>
<div><img class="btn btn-lg" src="/assets/img/img3.png" /></div>
<div><img class="btn btn-lg" src="/assets/img/img4.png" /></div>
<div><img class="btn btn-lg" src="/assets/img/img5.png" /></div>
</slick-slider>


This works fine. But when I try to use it inside *ngFor like this:

<slick-slider>
<div *ngFor="let source of sources">
<img class="btn btn-lg" (click)="watchSource(source)" src="/assets/img/{{source.name}}.png" />
</div>
</slick-slider>


It stops working. I assume is because the init of the slider is done before the
<div>
elements are being rendered. Because I end up with this rendered html:

<slick-slider class="slick-initialized slick-slider">
<div>
<img ... >
</div>
<div>
<img ... >
</div>
<div>
<img ... >
</div>
<div>
<img ... >
</div>

<div aria-live="polite" class="slick-list draggable">
<div class="slick-track" role="listbox" style="opacity: 1; width: 0px; transform: translate3d(0px, 0px, 0px);">
<!-- Divs should be rendered here with slick styles included -->
</div>
</div>
</slick-slider>


Any idea how to make this work? I tried using "ngAfterContentInit" and "ngAfterViewInit" neither seem to work.

This is the component using the SlickSliderComponent:

import { Component, OnInit } from '@angular/core'
import { DeviceSource } from './device-source'
import { RemoteService } from './remote.service';

@Component({
selector: 'device-selector',
moduleId: module.id,
templateUrl: 'device-selector.component.html',
providers: []
})



export class DeviceSelectorComponent implements OnInit {

sources: [DeviceSource];

ngOnInit(): void {
this.getDeviceSources();
}

constructor(private remoteService: RemoteService){}

getDeviceSources(): void {
this.remoteService.getDeviceSources().then(sources => this.sources = sources);
}
}


And here is the service used:

import { Injectable } from '@angular/core'
import { DeviceSource } from './device-source'

export const DEVICE_SOURCES:[DeviceSource]=
[
{id: 1, dispayName: 'TV', name:'tv'},
{id: 2, dispayName: 'Chrome', name:'chrome'},
{id: 3, dispayName: 'Cable', name:'cable'},
{id: 4, dispayName: 'XBox', name:'xbox'},
{id: 4, dispayName: 'Pi', name:'pi'},

];


@Injectable()
export class RemoteService {
getDeviceSources(): Promise<[DeviceSource]> {
return Promise.resolve(DEVICE_SOURCES);
}
}

Answer

You should add *ngIf directive to your slick-slider so it is inserted into DOM after data source is populated, in this case it is variable sources:

<slick-slider *ngIf="sources">
    <div *ngFor="let source of sources">
        <img class="btn btn-lg" (click)="watchSource(source)" src="/assets/img/{{source.name}}.png" />
    </div>
</slick-slider>