Build an Image Carousel with Svelte - Part 2 (Adding Features)
Tags: javascript, svelte, beginners, frontend
Published: 2020-10-16 04:52:28

# Some notes from Part 1

This article picks up from a previous article, if you haven't read it already please go back and [check it out](https://dev.to/bmw2621/build-an-image-carousel-with-svelte-4kf8). The repo is also available [here](https://github.com/bmw2621/svelte-carousel)

I posted the article and youtube video in a few places online and received some feedback on the usefulness of Carousels.  This series is not intended to advocate the use of Carousels or comment on their impact on user experience.  This is more of a way to introduce some of the functionality of Svelte.  In part 1 we:

- Downloaded the svelte template
- Created a component
- Pass props from parent to child component
- Explored conditional rendering with the `{#for <Iterable> as <Reference>}{/for}` loop
- Implemented event handling and Svelte's directive syntax
- Implemented slots to place child components

The goal was to get introduced to Svelte as tool with something other than the standard "Hello World" or "Todo" application.

In this continuation we will add a features to enable autoplay, allow the user to disable the control buttons, in the process we will:

- Set and Clear intervals
- Further explore conditional rendering logic with `{#if}{/if}` syntax
- Introduce `onDestroy` lifecyle method
- Demonstrate custom events and dispatching data from child to parent components

As will the last article, this one coincides with a the following Twitch stream now available on my YouTube channel:

{% youtube XiRGDv8rlag %}

# Lets add some features
## Adding autoplay capability

First we need to expose a prop variable by exporting it in the `Carousel.svelte` file.  We will set it to `false` as a default, and then update our Component declaration to set it as `true` so we can see it while we build. We also want to pass in a speed at which the autoplay will occur, so we expose `autoplaySpeed` as prop with a default value of 5 seconds, though we will override it by passing 3000 milliseconds to the prop for demonstration purposes.

``` javascript
// src/components/Carousel.sveltejs
<script>
  import { flip } from 'svelte/animate';

  export let images;
  export let imageWidth = 300;
  export let imageSpacing = 20;
  export let speed = 500;
  export let controlColor= '#444';
  export let controlScale = '0.5';
  export let autoplay = false;         // <---
  export let autoplaySpeed = 5000;     // <---
...
```
``` javascript
// src/App.svelte

<script>
...

<Carousel
	{images}
	imageWidth={250}
	imageSpacing={15}
  autoplay={true}              // <---
  autoplaySpeed={3000}         // <---
/>


<style>

</style>
...
```

Back in our component file we will create the `startAutoPlay` and `stopAutoPlay` functions which will will... you know, control our autoplay.  Then we will run a check to see if `autoplay` is set to true, and if so, call the `startAutoPlay`.

The `startAutoPlay` will set an interval to call the `rotateLeft` function we wrote in part 1. The `stopAutoPlay` will clear the intervals.  Be sure to also check if `autoplay` is enabled in the `startAutoPlay` function to ensure it isn't mistakenly started after a mouseover.

> __IMPORTANT__ In the video stream I forgot to discuss the `onDestroy` lifecyle method.  The `onDestroy` lifecyle method is called when the component is unmounted.  Remember to add this to insure the interval is cleared.

``` javascript
// src/components/Carousel.sveltejs
...
import { onDestroy } from 'svelte';
...
  const startAutoPlay = () => {
    if(autoplay){
      interval = setInterval(rotateLeft, autoplaySpeed)
    }
  }

  const stopAutoPlay = () => {
    clearInterval(interval)
  }

  if(autoplay){
    startAutoPlay()
  }

  onDestroy(() => {stopAutoPlay()})
...
```

We now have a functioning autoplay feature!

## Custom Events and Dispatching from Child to Parent component

> Because this is a demonstration, it is only visible in the [demonstration/eventDispathChildToParent](https://github.com/bmw2621/svelte-carousel/tree/demonstration/eventDispathChildToParent)  branch of the repo.

Just like with a normal `on:<EventName>` event listener directive, we can create our own events.  So we will add the `on:imageClicked` to the Carousel declaration in the `App.svelte` file.  Now the Carousel will listen for the `imageClicked` event that is emitted from within.  For now we will handle the event by calling a `handleImageClicked` function which will log the event to the console.

Inside the `Carousel.svelte` file we will have to create an event dispatcher.  First we must import `createEventDispatcher` from the svelte package. Now we will store it in a variable called `dispatch`, and add it to each `<img>` tag that is rendered in the `{#each}` loop.

In Svelte, an event dispatcher takes two parameters, the first is the signal you emit, and the second is called the events `detail` and will be accessible as and attribute of the event.  Here we will call `dispatch('imageClicked', image.path)` to each `<img>` tag. This will emit the images path as the events detail.

Lastly we will update our `App.svelte` so that it doesn't log "image clicked", but instead will log the clicked image's path.

``` javascript
// src/components/Carousel.svelte
<script>
  import { flip } from 'svelte/animate';
  import { createEventDispatcher, onDestroy } from 'svelte';   // <---

  export let images;
  export let imageWidth = 300;
  export let imageSpacing = 20;
  export let speed = 500;
  export let controlColor= '#444';
  export let controlScale = '0.5';
  export let autoplay = false;
  export let autoplaySpeed = 5000;
  export let displayControls = true;
  let interval;

  const dispatch = createEventDispatcher()                  // <---
  ...
  {#each images as image (image.id)}
    <img
      src={image.path}
      alt={image.id}
      id={image.id}
      style={`width:${imageWidth}px; margin: 0 ${imageSpacing}px;`}
      on:mouseover={stopAutoPlay}
      on:mouseout={startAutoPlay}
      on:click={() => dispatch('imageClicked',image.path)}   // <---
      animate:flip={{duration: speed}}/>
  {/each}
  ...

```

``` javascript
// src.App.svelte

const handleImageClicked = e => {       // <---
  console.log(e.detail)                 // <---
}
</script>


<Carousel
  {images}
  imageWidth={250}
  imageSpacing={15}
  controlColor={'white'}
  controlScale={0.8}
  autoplay={true}
  autoplaySpeed={3000}
  on:imageClicked={handleImageClicked}   // <---
/>
...

```

Now when you click on an image, you will see its path logging to console with the source being `App.svelte`

## Make the controls disappear

> For the rest of the article I will be working off the master branch, so dispatch examples will not be visible

Lastly, we will expose another boolean variable in the `Carousel.svelte` file called `displayControls` and give it a default value of `true`.  However, we will pass false to the prop from the component declaration so we can see it disappear.

Now we can wrap both button elements in the `Carousel.svelte` with the `{#if <Expression>}{/if}` conditional logic, and watch the controls disappear.

``` javascript
// src.App.svelte

...
<Carousel
  {images}
  imageWidth={250}
  imageSpacing={15}
  controlColor={'white'}
  controlScale={0.8}
  displayControls={false}               // <---
  autoplay={true}
  autoplaySpeed={3000}
/>
...

```

``` html
// src/components/Carousel.svelte

...
{#if displayControls}                           // <---
<button id="left" on:click={rotateLeft}>
  <slot name="left-control">
    ...
  </slot>
</button>
<button id="right" on:click={rotateRight}>
  <slot name="right-control">
    ...
  </slot>
</button>
{/if}                                           // <---
...

```

# Conclusion

Thanks for following the series.  In future articles I will continue with `stores`, Sveltes context API for sharing data with adjacent components.

If you are interested in other topics surrounding Svelte leave a comment.