Guide to create Joy Ride for an Angular Application using `angular-shepherd` library
Create app tour for angular application
Ever visited a new website and been greeted by an informative guide? This guide comprises a series of steps that highlight the key features of the application and take you on a tour. So, how can you create your own application tour in Angular? There are angular libraries that allow you to add joy ride in your application. One such library is angular-shepherd.
Shepherd.js is a JavaScript library that assists in creating steps for your tour in the form of dialogues. angular-shepherd is an Angular wrapper for Shepherd.js. This article aims to provide a comprehensive guide on incorporating angular-shepherd into your application to create an impressive site tour.
Installation
npm install angular-shepherd --save
Ensure that you install the version compatible with your Angular version. The Shepherd package now includes its own stylesheet file, shepherd.css. To use it with Angular, add it to the styles
array in the angular.json
file.
"styles": [
"node_modules/shepherd.js/dist/css/shepherd.css"
],
Now, we can create separate service for the joy-ride with all the steps and functions to create the flow of the tour.
Create Angular application
I created a basic Angular project with multiple pages to illustrate how an app tour works in applications with more than one page. Feel free to use your own application for creating a tour. To help you understand the implementation better, my Angular project includes three pages: Home, Contact, and About, each featuring a Menu Component.
<!-- menu.component.html -->
<mat-sidenav-container class="example-container">
<mat-sidenav mode="side" opened>
<mat-nav-list>
<a mat-list-item routerLink="/">Home</a>
<a mat-list-item routerLink="/contact" class="contact">Contact</a>
<a mat-list-item routerLink="/about" class="about">About</a>
</mat-nav-list></mat-sidenav
>
<mat-sidenav-content> <router-outlet></router-outlet></mat-sidenav-content>
</mat-sidenav-container>
Note, that I have added class='contact'
and class='about'
for menu items. This is important to add if you want that joy-ride steps should point to and highlight specific element during any of the steps.
Create service for joy-ride
In src/app
create a new folder services.
Inside the folder src/app/services,
create file joy-ride.service.ts
.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ShepherdService } from 'angular-shepherd';
import Step from 'shepherd.js/src/types/step';
const BUTTONS = {
back: {
classes: 'custom-back-button',
text: 'Back',
type: 'back',
},
};
@Injectable({
providedIn: 'root',
})
export class JoyRideService {
constructor(
private shepherdService: ShepherdService,
private router: Router
) {}
anytourActive = false;
isTourCompleted = false;
exitButton = {
text: 'Finish Tour',
classes: 'custom-cancel-button',
secondary: true,
action: () => {
this.destroyTour();
},
};
startTour(steps: Step.StepOptions[]) {
const defaultOptions = {
classes: 'custom-default-class',
scrollTo: true,
cancelIcon: {
enabled: false,
},
};
this.shepherdService.defaultStepOptions = defaultOptions;
this.shepherdService.modal = true;
this.shepherdService.confirmCancel = false;
this.shepherdService.addSteps(steps);
this.shepherdService.start();
}
addToCurrentTour(steps: Step.StepOptions[]) {
this.shepherdService.addSteps(steps);
this.shepherdService.next();
}
showById(id: string | number | undefined) {
this.shepherdService.tourObject.show(id);
}
hideSteps() {
this.shepherdService.hide();
}
completeTour() {
this.isTourCompleted = true;
this.anytourActive = false;
this.shepherdService.complete();
}
destroyTour() {
this.isTourCompleted = true;
this.anytourActive = false;
this.shepherdService.complete();
}
getSteps() {
return [
{
buttons: [
this.exitButton,
{
text: 'Next',
classes: 'custom-next-button',
action: () => {
this.shepherdService.next();
},
},
],
classes: 'custom-class-name-1 custom-class-name-2',
id: 'welcome',
title: 'Welcome to the Site.',
text: ' Welcome to the Site! This is welcome step',
},
{
attachTo: {
element: '.contact',
on: 'right',
},
buttons: [
this.exitButton,
{
text: 'Back',
classes: 'custom-back-button',
action: () => {
this.hideSteps();
this.router.navigate(['/']);
},
},
{
text: 'Next',
classes: 'custom-next-button',
action: () => {
this.router.navigate(['/contact']);
},
},
],
id: 'choose contact',
title: 'Choose Contact',
text: 'Click on Contact Page to go to contact',
},
{
buttons: [
this.exitButton,
BUTTONS.back,
{
text: 'Next',
classes: 'custom-next-button',
action: () => {
this.shepherdService.next();
},
},
],
id: 'contact',
title: 'Contact Page',
text: 'This is Contact Page',
},
{
attachTo: {
element: '.about',
on: 'right',
},
buttons: [
this.exitButton,
{
text: 'Back',
classes: 'custom-back-button',
action: () => {
this.hideSteps();
this.router.navigate(['/contact']);
},
},
{
text: 'Next',
classes: 'custom-next-button',
action: () => {
this.router.navigate(['/about']);
},
},
],
id: 'choose about',
title: 'Choose About',
text: 'Click on About Page to go to about',
},
{
buttons: [this.exitButton, BUTTONS.back],
id: 'about',
title: 'About Page',
text: 'This is About Page',
},
];
}
}
Let's understand the important parts of the code:
Imports:
Injectable
: Decorator from Angular's core library, indicating that the class can be injected with dependencies.Router
: Angular's router service, used for navigation.ShepherdService
: Service from the 'angular-shepherd' library, providing integration with Shepherd.js for creating guided tours.Step
: Type definition from Shepherd.js for specifying the properties of a tour step.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ShepherdService } from 'angular-shepherd';
import Step from 'shepherd.js/src/types/step';
Constants:
BUTTONS
: An object defining a constant for a back button with custom styling.
const BUTTONS = {
back: {
classes: 'custom-back-button',
text: 'Back',
type: 'back',
},
};
Injectable Service:
@Injectable({ providedIn: 'root' })
: Decorator specifying that the service should be provided at the root level.
@Injectable({
providedIn: 'root',
})
Constructor:
- Takes in
ShepherdService
andRouter
as dependencies.
- Takes in
constructor(
private shepherdService: ShepherdService,
private router: Router
) {}
Properties:
anytourActive
: A boolean indicating whether any joyride is currently active.isTourCompleted
: A boolean indicating whether the joyride is completed.exitButton
: Configuration for an exit button, specifying its text, classes, and action.
anytourActive = false;
isTourCompleted = false;
exitButton = {
text: 'Finish Tour',
classes: 'custom-cancel-button',
secondary: true,
action: () => {
this.destroyTour();
},
};
Methods:
startTour(steps: Step.StepOptions[])
: Initiates a new joyride with specified steps.addToCurrentTour(steps: Step.StepOptions[])
: Adds steps to the existing joyride.showById(id: string | number | undefined)
: Shows a specific step in the joyride by its ID.hideSteps()
: Hides the current joyride.completeTour()
: Marks the joyride as completed.destroyTour()
: Destroys the current joyride.
startTour(steps: Step.StepOptions[]) {
// ... (configures ShepherdService options)
this.shepherdService.addSteps(steps);
this.shepherdService.start();
}
addToCurrentTour(steps: Step.StepOptions[]) {
this.shepherdService.addSteps(steps);
this.shepherdService.next();
}
// ... (other methods)
getSteps():
The
getSteps()
method in theJoyRideService
class returns an array of step configurations that define the individual steps for a guided tour (joyride) within a web application. Each step configuration is an object with specific properties that customize the behavior and appearance of that step in the joyride.Here's a breakdown of the
getSteps()
method:getSteps() { return [ { buttons: [ this.exitButton, { text: 'Next', classes: 'custom-next-button', action: () => { this.shepherdService.next(); }, }, ], classes: 'custom-class-name-1 custom-class-name-2', id: 'welcome', title: 'Welcome to the Site.', text: ' Welcome to the Site! This is welcome step', }, // ... (other step configurations) ]; }
Let's go through the properties of a single step configuration:
buttons: An array defining the buttons associated with the current step.
classes: A string representing additional CSS classes to be applied to the step. It allows custom styling for each step.
id: A unique identifier for the step. This is used to reference and show a specific step using the
showById
method in the service.title: The title of the step, displayed prominently in the joyride.
text: The descriptive text or content associated with the step.
attach-to: This is important for the steps where you want the step to highlight specific element. You can specify the element by adding it's
class
and also theposition
of the step around the element.
An important observation in the getSteps()
function: please note that I have included buttons with actions in certain steps. These are specifically designed for steps requiring a change of page. For instance, if you aim to display the next step on another page, as is necessary in my application.
Start Tour
We can initiate the tour based on our requirements. Whether it's after the user completes authorization or when they visit a specific page, the choice is yours. I'm starting the tour on my home page.
In my home.component.ts
import { AfterViewInit, Component } from '@angular/core';
import { JoyRideService } from 'src/app/services/joy-ride.service';
import Step from 'shepherd.js/src/types/step';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent implements AfterViewInit {
constructor(public joyRide: JoyRideService) {}
ngAfterViewInit(): void {
this.homeTourSteps();
}
homeTourSteps() {
if (!this.joyRide.isTourCompleted) {
const steps = this.joyRide.getSteps();
this.joyRide.startTour(steps as Array<Step.StepOptions>);
}
}
}
We need to ensure that steps are only visible if the tour is incomplete. After confirming that the tour has not been completed, the steps are retrieved using getSteps
and then passed to the startTour
function to initiate the tour.
It's important to note that we also want to manage the steps when the page changes. Therefore, if a user lands on another page after clicking "Next" on any step, we want that new page to display the corresponding step. This can be achieved as follows:
import { Component } from '@angular/core';
import { JoyRideService } from 'src/app/services/joy-ride.service';
import Step from 'shepherd.js/src/types/step';
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css'],
})
export class ContactComponent {
constructor(public joyRide: JoyRideService) {}
ngAfterViewInit(): void {
this.contactSteps();
}
contactSteps() {
if (!this.joyRide.isTourCompleted) {
this.joyRide.hideSteps();
this.joyRide.showById('contact');
}
}
}
To ensure a seamless user experience, it's essential to hide the previous step when the page changes. If this step is not taken, the previous step will remain on the screen. Therefore, the approach is to hide the previous step and display the step intended for the current page.
This completes the joy-ride implementation in the angular application!
Let me know if there is any doubt or I missed something!
Code :
Github: angular-shepherd example