Skip to content

Chapter 9 - Define and access multiple slideshows

In this chapter, you will define and access multiple slideshows. This will allow you to create diverse slideshows that you can show in different contexts without the need to change them every time.

Steps

Add an ID to the slideshow type

Update the Slideshow type to give it an ID and the slides for this slideshow.

src/types/slideshow.type.ts
1
2
3
4
5
6
import { Slide } from "./slide.type"

export type Slideshow = {
	id: string;
	slides: Slide[];
}

Create two different slideshows

Update the AppService to define two different slideshows with the updated Slideshow type. In this example, the main slideshow is split into two slideshows. The slideshows are then saved in a Map with a key that identifies each slideshow. The method getSlideshows allows to get all the slideshows.

src/app.service.ts
import { Injectable } from '@nestjs/common';
import { MediaType } from './enums/media-type.enum';
import { Slideshow } from './types/slideshow.type';

const slideshow1: Slideshow = { // (1)!
	id: 'slideshow1',
	slides: [
		{
			src: "https://source.unsplash.com/random?1",
			type: MediaType.IMAGE,
			alt: "Random photo on Unsplash",
		},
		{
			src: "https://source.unsplash.com/random?2",
			type: MediaType.IMAGE,
			alt: "Random photo on Unsplash",
			interval: 2000,
		},
	]
}

const slideshow2: Slideshow = { // (2)!
	id: 'slideshow2',
	slides: [
		{
			src: "https://source.unsplash.com/random?3",
			type: MediaType.IMAGE,
			alt: "Random photo on Unsplash",
		},
		{
			src: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",
			type: MediaType.VIDEO,
			alt: "Sintel: first open movie",
			interval: 10000,
		},
	]
}

const slideshows: Map<string, Slideshow> = new Map([ // (3)!
	[slideshow1.id, slideshow1],
	[slideshow2.id, slideshow2],
]);

@Injectable()
export class AppService {
	getSlideshows() {
		return slideshows; // (4)!
	}

	getSlideshow(slideshowId: string) { // (5)!
		const slideshow = slideshows.get(slideshowId);

		return slideshow; // (6)!
	}
}
  1. Define the first slideshow.
  2. Define the second slideshow.
  3. The slideshows are saved in a Map. The first slideshow has the ID slideshow1. The second has the ID slideshow2.
  4. The slideshows are returned.
  5. The slideshow ID parameter (slideshowId) defines the slideshow to get from the Map.
  6. The slideshow is returned.

Update the controller

Update the controller to set up two routes.

The second route has a little catch. What if the ID of the slideshow doesn't exist or is not existent? A simple solution would be to check if the slideshow requested by the user exists. If it doesn't exist, redirect the user to the page listing the slideshows to allow them to select a correct one.

src/app.controller.ts
import { Get, Controller, Render, Param, Res } from '@nestjs/common'; // (1)!
import { Response } from 'express'; // (2)!
import { AppService } from './app.service';

@Controller()
export class AppController {
	constructor(private readonly appService: AppService) {}

	@Get()
	@Render('index')
	getSlideshows() {
		const slideshows = this.appService.getSlideshows();

		// TODO: This can be improved when the following pull request is merged:
		// https://github.com/handlebars-lang/handlebars.js/pull/1679
		const slideshowsAsArray = Array.from(slideshows).map(([key, value]) => ({
			id: key,
			slideshow: value,
		})) // (3)!

		return { slideshows: slideshowsAsArray };
	}

	@Get(':id') // (4)!
	getSlideshow(@Res() res: Response/* (5)! */, @Param('id') id: string/* (6)! */) {
		const slideshow = this.appService.getSlideshow(id);

		if (!slideshow) { // (7)!
			return res.redirect('/'); // (8)!
		}

		return res.render( // (9)!
			'slideshow',
			{ slideshow: slideshow },
		);
	}
}
  1. Import missing dependencies to access the response to send to the user.
  2. Get the type of the response so Visual Studio Code knows the typing of res.redirect.
  3. This is a workaround as Handlebars doesn't support Map for the moment.
  4. Remove @Render('slideshow') to dynamically render it later.
  5. The @Res() res: Response is a decorator to extract the response that can be sent to the user.
  6. The @Param('id') id: string is a decorator to extract the id from the user's request.
  7. Check if the slideshow exists.
  8. If it doesn't exist, redirect the user to the main page (/).
  9. Return the slideshow if it exists with the slideshow Handlebars template.

Update the main template to list all available slideshows

The main page (/) will now list all available slideshows with links to access the slideshows.

views/index.hbs
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Media Player</title>
	</head>
	<body>
		{{!--
		TODO: This can be improved when the following pull request is merged:
		https://github.com/handlebars-lang/handlebars.js/pull/1679
		--}}
		{{#each slideshows}}
			<p>
				<a href="/{{this.id}}">Access slideshow '{{this.id}}'.</a>
			</p>
		{{else}} <!-- (1)! -->
			<p>No slideshow.</p>
		{{/each}}
	</body>
</html>
  1. If no slideshows are available, the message No slideshow. will be shown to inform the user.

Update the slideshow template

Update the slideshow template for the new Slideshow type.

views/slideshow.hbs
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Media Player</title>
		<link rel="stylesheet" href="/css/themes/splide-default.min.css">
		<link rel="stylesheet" href="/css/splide-extension-video.min.css">
		<link rel="stylesheet" href="/css/splide.min.css">
		<link rel="stylesheet" href="/css/stylesheet.css">
		<script src="/js/splide-extension-video.min.js"></script>
		<script src="/js/splide.min.js"></script>
	</head>
	<body>
		<section id="slideshow" class="splide" aria-label="{{ slideshow.id }}"> <!-- (1)! -->
			<div class="splide__track">
				<ul class="splide__list">
					{{#each slideshow.slides}} <!-- (2)! -->
						{{#if (eq this.type "image")}}
							<li
								class="splide__slide"
								{{#if this.interval}}
								data-splide-interval="{{ this.interval }}"
								{{/if}}
							>
								<img src="{{ this.src }}" alt="{{ this.alt }}">
							</li>
						{{else if (eq this.type "video")}}
							<li
								class="splide__slide"
								data-splide-html-video="{{ this.src }}"
								{{#if this.interval}}
								data-splide-interval="{{ this.interval }}"
								{{/if}}
							>
							</li>
						{{/if}}
					{{/each}}
				</ul>
			</div>
		</section>
	</body>
	<script>
		const splide = new Splide(
			'#slideshow',
			{
				type: 'fade',
				interval: 0,
				rewind: true,
				speed: 10000,
				interval: 5000, // Default interval
				start: 0,
				lazyLoad: 'nearby',
				arrows: false,
				pagination: false,
				autoplay: true,
				pauseOnHover: false,
				drag: true,
				rewindByDrag: true,
				keyboard: 'global',
				mouse: false,
				video: {
					loop: true,
					mute: true,
					autoplay: true,
					disableOverlayUI: true,
					hideControls: true,
					htmlVideo: {
						controls: false,
					},
				},
			}
		)

		splide.mount(window.splide.Extensions);

		// Get the autoplay component
		const autoplay = splide.Components.Autoplay;

		// Start the autoplay
		autoplay.play();
	</script>
</html>
  1. Display the ID of the slideshow.
  2. Render the slideshow's slides as earlier.

Check the results

Start the application and access http://localhost:3000. You should see a list with the two slideshows. Access the first to see the two images slideshow. Access the second to see an image and a video slideshow.

Try to access http://localhost:3000/non-existing-slideshow, you will be redirected to http://localhost:3000 as the slideshow with ID non-existing-slideshow doesn't exist.

To stop your NestJS application, press Ctrl+C in your terminal.

Summary

Congrats! You can now define and access multiple slideshows. Your application list all available slideshows and you can choose which one you'd like to display. If a user tries to access a wrong slideshow ID, they will be redirected to the main page will all available slideshows.

Go further

Can you create a 404 page to display a custom message when you try to access an inexistent slideshow? You can check the NestJS - MVC #Dynamic template rendering documentation for resources.

Show me the answer!

Create a new views/404.hbs Handlebars template. This template will display an error message and a link to go back to the main page.

views/404.hbs
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Media Player</title>
	</head>
	<body>
		<p>This slideshow doesn't exist.</p>
		<p>
			<a href="/">Go back to homepage.</a>
	 </p>
	</body>
</html>

Update the AppController to render the 404 Handlebars when a slideshow wasn't found.

src/app.controller.ts
1
2
3
if (!slideshow) {
	return res.render('404');
}

Save your changes. If you didn't stop your NestJS application, it should automatically restart the application. Access http://localhost:3000/non-existing-slideshow, you should see the error message. You can then go back to the main page.