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:

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() {
  // ...
}

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 camel case:

@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() {
  // ...
}