Handle Decorators
Learn more about Jovo @Handle
decorators, which determine which types of user input a handler should respond to.
Introduction
@Handle
decorators are TypeScript method decorators that can be added to a handler function to determine which types of user input the handler should respond to.
For example, the below showMenu
handler (you can name these methods however you like) is triggered if the user input contains either ShowMenuIntent
or YesIntent
:
import { Handle } from '@jovotech/framework'; // ... @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... }
Each of the properties of @Handle
also includes its own convenience decorator. The above example would also work like this:
import { Intents } from '@jovotech/framework'; // ... @Intents['ShowMenuIntent', 'YesIntent']) showMenu() { // ... }
The @Handle
decorator includes two types of properties:
- Routing properties: The router first looks if the handler matches a specific route:
- Condition properties: After that, it is evaluated if there are additional conditions that have to be fulfilled:
The more properties of @Handle
a handler fulfills, the higher it gets prioritized in the current request. Learn more about handler prioritization in the routing documentation.
For example, there could be an additional handler just for Alexa using platforms
:
import { Handle } from '@jovotech/framework'; // ... @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... } @Handle({ intents: ['ShowMenuIntent', 'YesIntent'], platforms: ['alexa'] }) showMenuOnAlexa() { // ... }
Or you could have different handlers depending on user data using if
:
import { Handle, Jovo } from '@jovotech/framework'; // ... @Handle({ intents: ['PlayGameIntent'] }) playGame() { // ... } @Handle({ intents: ['PlayGameIntent'], if: (jovo: Jovo) => jovo.$user.data.hasAlreadyPlayedToday }) tellUserToComeBackTomorrow() { // ... }
Routing Properties
Routing properties define the core elements a router is looking for when determining if a handler matches a request. Learn more in the routing documentation.
The following properties are available:
intents
The intents
property specifies which incoming intents the handler should be able to fulfill. Learn more about intents in the models documentation.
For example, this handler responds to only the ShowMenuIntent
:
@Handle({ intents: ['ShowMenuIntent'] }) showMenu() { // ... }
For some interactions, it might be helpful to add multiple intents as input. The below handler responds to both the ShowMenuIntent
and YesIntent
:
@Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... }
Sometimes, a handler should be global
for only some of the intents
. For this, you can turn an intent string into an object:
@Handle({ intents: [{ name: 'ShowMenuIntent', global: true }, 'YesIntent'] }) showMenu() { // ... }
It's also possible to use the @Intents
convenience decorator:
import { Intents } from '@jovotech/framework'; // ... @Intents(['ShowMenuIntent', 'YesIntent']) showMenu() { // ... }
This decorator supports the same structure as the intents
property in @Handle
. Additionally, it supports rest parameters, so you don't need to add an array for it to recognize multiple intents:
@Intents('ShowMenuIntent', 'YesIntent') showMenu() { // ... }
types
The types
property specifies which input types the handler should be able to fulfill.
@Handle({ types: ['LAUNCH'], }) welcomeUser() { // ... }
This property is especially helpful for platform specific request types. For example, you can use it to react to any Alexa request type like AudioPlayer.PlaybackStopped
without needing to extend Jovo core functionality in any way:
@Handle({ global: true, types: ['AudioPlayer.PlaybackStopped'], platforms: ['alexa'], }) playbackStopped() { // ... }
It's also possible to use the @Types
convenience decorator:
import { Types } from '@jovotech/framework'; // ... @Types(['LAUNCH']) welcomeUser() { // ... }
This decorator supports the same structure as the types
property in @Handle
. Additionally, it supports rest parameters, so you don't need to add an array for it to recognize multiple types:
@Types('LAUNCH', 'SomeOtherInputType') welcomeUser() { // ... }
global
By default, handlers are only accessible from their component. By making them global, they can be reached from any part of the Jovo app. This is similar to how "stateless" handlers worked in Jovo v3
.
@Handle({ global: true, // ... }) yourHandler() { // ... }
It is also possible to use the @Global
convenience decorator for this.
import { Global, Handle } from '@jovotech/framework'; // ... @Global() @Handle({ /* ... */ }) yourHandler() { // ... }
Sometimes, it might be necessary to split @Handle
to make sure that not all options are set to global
. In the below example, the handler is accessible from anywhere for both the ShowMenuIntent
and YesIntent
(which is probably not a good idea):
@Global() @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... }
By adding a second @Handle
decorator, you can make it global for only some intents:
@Handle({ global: true, intents: ['ShowMenuIntent'] }) @Handle({ intents: ['YesIntent'] }) showMenu() { // ... }
Alternatively, you can make an intent an object and add global
to it:
@Handle({ intents: [{ name: 'ShowMenuIntent', global: true }, 'YesIntent'] }) showMenu() { // ... }
subState
As components have their own state management system, we usually recommend using the $delegate()
method if you have steps that need an additional state. However, sometimes it might be more convenient to have all handlers in one component.
For this, you can set a $subState
in your handlers:
this.$subState = 'YourSubState';
Jovo then adds it to the component's state in the $state
stack:
$state = [ { component: 'YourComponent', subState: 'YourSubState', }, ];
You can then add subState
to your @Handle
decorator to make sure that this handler only responds to requests of this specific state.
@Handle({ subState: 'YesOrNoState', intents: ['YesIntent'], }) Yes() { // ... }
It's also possible to use the @SubState
convenience decorator:
import { SubState, Intents } from '@jovotech/framework'; // ... @SubState('YesOrNoState') @Intents(['YesIntent']) showMenu() { // ... }
subState
does not work with global components. We recommend using the$delegate()
method.
prioritizedOverUnhandled
Sometimes, it's possible that a conversation gets stuck in an UNHANDLED
handler. If you want to prioritize a specific handler over a subcomponent's UNHANDLED
handler, then you can add the prioritizedOverUnhandled
property.
@Handle({ // ... prioritizedOverUnhandled: true, }) yourHandler() { // ... }
It's also possible to use the @PrioritizedOverUnhandled
convenience decorator:
import { PrioritizedOverUnhandled } from '@jovotech/framework'; // ... @PrioritizedOverUnhandled() yourHandler() { // ... }
Learn more about UNHANDLED
prioritization in the routing docs.
Condition Properties
Condition properties are additional elements that need to be fulfilled for a handler to respond to a request. The more conditions are true, the higher a handler is prioritized.
Currently, they include:
platforms
You can specify that a handler is only responsible for specific platforms. The platforms
property is an array of strings with the names of each platform in camelCase:
@Handle({ // ... platforms: ['alexa', 'googleAssistant'] }) yourHandler() { // ... }
It's also possible to use the @Platforms
convenience decorator:
import { Platforms } from '@jovotech/framework'; // ... @Platforms(['alexa']) yourHandler() { // ... }
This decorator supports the same structure as the platforms
property in @Handle
. Additionally, it supports rest parameters, so you don't need to add an array for it to recognize multiple platforms:
@Platforms('alexa', 'googleAssistant') yourHandler() { // ... }
if
The if
property can be a function with access to the jovo
context (the same as this
inside a handler). The condition is fulfilled if the function returns true
.
Here is an example of an if
condition that says a handler should only be triggered if the user has already played today (stored as a hasAlreadyPlayedToday
boolean as part of user data):
import { Handle, Jovo } from '@jovotech/framework'; // ... @Handle({ // ... if: (jovo: Jovo) => jovo.$user.data.hasAlreadyPlayedToday }) yourHandler() { // ... }
It's also possible to use the @If
convenience decorator:
import { If, Jovo } from '@jovotech/framework'; // ... @If((jovo: Jovo) => jovo.$user.data.hasAlreadyPlayedToday)) yourHandler() { // ... }
Here is an additional example that returns a different message if it is a new user:
// src/components/GlobalComponent.ts import { Jovo, Component, BaseComponent, Global, Handle } from '@jovotech/framework'; // ... @Global() @Component() export class GlobalComponent extends BaseComponent { LAUNCH() { return this.$send('Welcome back!'); } @Handle({ types: ['LAUNCH'], if: (jovo: Jovo) => jovo.$user.isNew }) welcomeNewUser() { return this.$send('Welcome, new user!'); } }
Multiple Decorators
Sometimes, it might be necessary to split @Handle
into multiple decorators. For example, it could be used to make sure that not all options are set to global
. In the below example, the handler is accessible from anywhere for both the ShowMenuIntent
and YesIntent
(which is probably not a good idea):
@Global() @Handle({ intents: ['ShowMenuIntent', 'YesIntent'] }) showMenu() { // ... }
By adding a second @Handle
decorator, you can make it global
for only some intents:
@Handle({ global: true, intents: ['ShowMenuIntent'] }) @Handle({ intents: ['YesIntent'] }) showMenu() { // ... }
Imported Decorators
For a clearer structure and better readability, you can also outsource objects to be used by the @Handle
decorator in a separate file and then import it in your component file.
The below example exports a class called Handles
that can include both methods as well as properties:
// src/Handles.ts import { HandleOptions } from '@jovotech/framework'; export class Handles { // ... static newUserOnLaunch(): HandleOptions { return { global: true, types: ['LAUNCH'], if: (jovo: Jovo) => jovo.$user.isNew }; } }
You can then use it with @Handle
like this:
import { Handle } from '@jovotech/framework'; // ... @Handle(Handles.newUserOnLaunch()) welcomeNewUser() { // ... }
Some Jovo platforms also come with convenience decorator methods and objects. For example, the Alexa platform integration offers a class called AlexaHandles
that exports objects and methods that can be imported in your components.
Here is an example from AlexaHandles
:
import { EnumLike, HandleOptions } from '@jovotech/framework'; // ... export enum AudioPlayerType { PlaybackStarted = 'AudioPlayer.PlaybackStarted', PlaybackNearlyFinished = 'AudioPlayer.PlaybackNearlyFinished', PlaybackFinished = 'AudioPlayer.PlaybackFinished', PlaybackStopped = 'AudioPlayer.PlaybackStopped', PlaybackFailed = 'AudioPlayer.PlaybackFailed', } export type AudioPlayerTypeLike = EnumLike<AudioPlayerType> | string; static onAudioPlayer(type: AudioPlayerTypeLike): HandleOptions { return { global: true, types: [type], platforms: ['alexa'], }; }
In your component, it can be used like this (as explained in the Alexa AudioPlayer docs):
import { Handle } from '@jovotech/framework'; import { AlexaHandles } from '@jovotech/platform-alexa'; // ... @Handle(AlexaHandles.onAudioPlayer('AudioPlayer.PlaybackStopped')) playbackStopped() { // ... }
The result is the same as using this:
import { Handle } from '@jovotech/framework'; // ... @Handle({ global: true, types: ['AudioPlayer.PlaybackStopped'], platforms: ['alexa'], }) playbackStopped() { // ... }