Skip to content

Chapter 7 - Move the slideshow data to the service

In this chapter, you will move the slideshow data to the AppService file to allow more flexibility in the making of the slideshow. If you ever need to update the slideshow, you can do it in one place without having to modify the Handlebars template.

Steps

Update the service

Update the service to return an array of slides.

The slide is consisted of:

  • src: The source of the media;
  • type: The kind of media (image or video);
  • alt: An alternative text that is helpful for accessibility;
  • interval: The interval the slide is shown.
src/app.service.ts
import { Injectable } from '@nestjs/common';

const slideshow = [
	{
		src: "https://source.unsplash.com/random?1",
		type: "image",
		alt: "Random photo on Unsplash",
		interval: 5000,
	},
	{
		src: "https://source.unsplash.com/random?2",
		type: "image",
		alt: "Random photo on Unsplash",
		interval: 5000,
	},
	{
		src: "https://source.unsplash.com/random?3",
		type: "image",
		alt: "Random photo on Unsplash",
		interval: 5000,
	},
	{
		src: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4",
		type: "video",
		alt: "Sintel: first open movie",
		interval: 10000,
	},
]

@Injectable()
export class AppService {
	getSlideshow() {
		return slideshow;
	}
}

Update the controller

Update the controller to return the slideshow data to the Handlebar template.

src/app.controller.ts
import { Get, Controller, Render } from '@nestjs/common';
import { AppService } from './app.service';

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

	@Get()
	@Render('slideshow')
	getSlideshow() {
		const slideshow = this.appService.getSlideshow();

		return { slideshow: slideshow };
	}
}

Update the main page view

Update the main page view to iterate over the array returned by the service.

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="A beautiful slideshow">
			<div class="splide__track">
				<ul class="splide__list">
					{{#each slideshow}} <!-- (1)! -->
					{{#if (eq this.type "image")}} <!-- (2)! -->
							<li
								class="splide__slide"
								{{#if this.interval}}
								data-splide-interval="{{this.interval}}"
								{{/if}}
							> <!-- (3)! -->
								<img src="{{this.src}}" alt="{{this.alt}}">
							</li>
						{{else if (eq this.type "video")}} <!-- (4)! -->
							<li
								class="splide__slide"
								data-splide-html-video="{{this.src}}"
								{{#if this.interval}}
								data-splide-interval="{{this.interval}}"
								{{/if}}
							>
							</li> <!-- (5)! -->
						{{/if}} <!-- (6)! -->
					{{/each}} <!-- (7)! -->
				</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. This will iterate over each element of the array slideshow.
  2. This will check if the element's type is an image with the eq helper you will create next.
  3. This is the HTML component that is rendered if the element's type is an image. The element's properties can be accessed with the keyword this.
  4. This will check if the element's type is a video with the eq helper you will create next.
  5. This is the HTML component that is rendered if the element's type is a video. The element's properties can be accessed with the keyword this.
  6. This indicates the end of the if block.
  7. This indicates the end of the each block.

Update the main file

Handlebars offers some built-in helpers that you can use in the template.

Some of the built-in helpers are if and each as seen earlier for example.

However, the if helper only evaluates boolean (true/false). As such, you have to create a new custom helper eq that can compare two objects. If they are equal, return true, otherwise, false.

Update the main file to add this custom helper to Handlebars.

src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';
import { handlebars } from 'hbs';

async function bootstrap() {
	const app = await NestFactory.create<NestExpressApplication>(AppModule);

	app.setBaseViewsDir(join(__dirname, '..', 'views'));
	app.useStaticAssets(join(__dirname, '..', 'public'));
	app.setViewEngine('hbs');

	handlebars.registerHelper(/* (1)! */'eq', /* (2)! */(v1, v2) => v1 === v2);

	await app.listen(3000);
}

bootstrap();
  1. The eq helper can then be used in the template as seen earlier.
  2. This peculiar version of the function (v1, v2) => v1 === v2 is the same as function (v1, v2) { return v1 === v2 }. It's a shorter syntax that is helpful in this use case.

Check the results

Start the application and access http://localhost:3000. You should see the exact same slideshow as earlier.

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

Summary

Congrats! You have moved the slideshow data to the service. You will proceed to make full usage of this change in the next chapters.