Transition Hooks

This guide describes UI-Router Transition Hooks, which allow a developer to tap into the lifecycle events of a Transition.

Be sure you’ve already read the Transitions Guide.

Overview

A transition has numerous lifecycle events. You can tap into each of the lifecycle events by registering a “Transition Hook”. A Transition Hook is a callback function that is run during the specific lifecycle event of a Transition. The hook function receives the current Transition object as the first argument.

function myHook(transition) {
  const toState = transition.to();
  // ... code
}

Global transition hooks are registered using the hook registration methods on the TransitionService (referred to as $transitions in the examples). There is a hook registration method for each lifecycle event: onBefore, onStart, onExit, onRetain, onEnter, onFinish, onSuccess, and onError.

Here’s an example transition hook which logs all successful transitions to the console.

$transitions.onSuccess({}, function(transition) {
  console.log(
      "Successful Transition from " + transition.from().name +
      " to " + transition.to().name
  );
});

In this example, the first argument is an empty criteria object ({}) which matches ALL transitions. You will learn more about criteria objects later in the guide.

Return values

The return value of a transition hook can be used to alter the transition. Note: The onSuccess and onError hooks execute after a transition is finished, so their return values are ignored.

Promises

If a hook returns a promise, the transition will pause and wait for the promise to settle. If the promise resolves to a value, the value is then processed (as if it were the return value for the hook).

This hook will delay all transitions to the sloth state by one second.

$transitions.onStart({}, function(transition) {
  if (transition.to().name === 'sloth') {
    return new Promise(resolve => setTimeout(resolve, 1000));
  }
}

If the returned promise is rejected, the transition is aborted. The promise’s rejection value can be read using Transition.error() in an onError hook.

Aborting a transition

If a transition hook returns false (or returns a promise that is later rejected), the transition will be aborted.

$transitions.onStart({}, function(transition) {
  if (transition.to().name === 'unreachable') {
    return false;
  }
}

Redirecting a transition

A transition hook can redirect a transition to a different state and/or parameter values by returning a new TargetState. A TargetState can be created using the StateService.

This hook redirects an unauthenticated user to a login state.

$transitions.onBefore({}, function(transition) {
  // check if the state should be protected
  if (transition.to().protected && !user.isAuthenticated()) {
    // redirect to the 'login' state
    return transition.router.stateService.target('login');
  }
}

By combining promises and redirects, you can easily secure states even if permissions checks require a call to the server.

$transitions.onBefore({}, function(transition) {
  const stateService = transition.router.stateService;
  const requiredRole = transition.to().data.role;

  return fetch('/currentuser/roles')
  .then(resp => resp.json())
  .then(roles => {
    if (roles.indexOf(requiredRole) === -1) {
      // User doesn't have the required role
      return stateService.target('noauth');
    }
  });
}

A hook can also redirect the transition, changing only parameter values. This example hook programmatically supplies a default parameter value for language.

$transitions.onBefore({}, function(transition) {
  // Don't mutate the current parameters
  const paramsCopy = Object.assign({}, transition.params());
  const stateService = transition.router.stateService;
  
  if (typeof paramsCopy.language === 'undefined') {
    // supply 'en' as a default language
    paramsCopy.language = 'en';
    return stateService.target(transition.to(), paramsCopy);
  }
}

Criteria object

When registering a Transition Hook, the first parameter is the hook’s criteria object. The criteria object is used to decide which transitions the hooks should be executed during. The hook will only be invoked for transitions that match the criteria defined in the object.

To/From

The criteria object can select transitions declaratively, based on the state it’s going to or from. Add a to or from property on the criteria object, and set the value to the name of a state.

$transitions.onSuccess({ to: 'login' }, function(transition) {
  console.log("Now at 'login' state");
});

$transitions.onSuccess({ from: 'login' }, function(transition) {
  console.log("Left 'login' state");
});

$transitions.onError({ from: 'home' }, function(transition) {
  console.log("Error while leaving 'home' state: " + transition.error());
});

Entering/Exiting/Retained

In addition to the to or from criteria, you can also match transitions which will enter, exit, or retain specific states. The criteria object’s entering, exiting, and retained properties are used to declare which transitions to match.

The following example hook is run when a transition entered the admin state, for example from login to admin.users.

$transitions.onSuccess({ entering: 'admin' }, function(transition) {
  console.log("Now inside the admin section!");
});

However, the hook is not run again when transitioning between states inside the admin state tree. This is because the admin state is not re-entered on those transitions, e.g. from admin.users to admin.users.create.

Globs

The criteria object may also use globs to match state names using wildcards.

This following hook will be run for each successful transition to any state in the admin.** tree. This would includes state names such as admin, admin.users, and admin.users.create.

$transitions.onSuccess({ to: 'admin.**' }, function(transition) {
  console.log("Switched to an admin state: " + transition.to().name);
});

Unlike the previous example, this hook will be run when transitioning between states inside the admin state tree, such as from admin.users to admin.users.create.

Functional criteria

If name-based declarative criteria is not flexible enough for your app, you can provide a function in the criteria object. The function will receive a reference to the StateDeclaration being tested. It should inspect the state declaration and return truthy or falsey (to match or not match the transition).

The following functional criteria example has the same effect as the declarative criteria: { to: 'admin' }.

const criteriaObj = {
  to: (state) => state.name === 'admin'
};

$transitions.onSuccess(criteriaObj, function(transition) {
  console.log("Switched to the admin state.");
});

Functional criteria can be used to check for metadata on state declarations. For example, you might add a title property to some of your states’ data object…

const states = [
  { name: 'home', data: { title: 'Home' } },
  { name: 'about', data: { title: 'About the app' } },
  { name: 'other' },
]

… and then use a functional criteria object which checks for a truthy title property on the to state’s data object. When the criteria matches (a title is found on the data object), the hook is invoked and it updates the document’s title.

const criteriaObj = {
  to: (state) => !!state.data.title
}

$transitions.onSuccess(criteriaObj, function(transition) {
  document.title = transition.to().data.title;
});

State-level hooks

There are three state-level hooks: onEnter, onExit, and onRetain.

Remember that multiple states can be entered or exited in a single transition. A state-level hook can be invoked once per state that is entered (or exited or retained) during a single transition. The second argument to a state-level transition hook is the state itself.

This example registers an onEnter which will be invoked for every state that is entered.

$transitions.onEnter({}, function(transition, state) {
  console.log('Transition #' + transition.$id + ' Entered ' + state.name);
}

A transition from hello to people.person will then log the following to the console:

Transition #1 Entered people
Transition #1 Entered people.person

Like before, the criteria object can be used to limit which transitions the hook should be run for.

$transitions.onEnter({ entering: 'people' }, function(transition, state) {
  console.log('Transition #' + transition.$id + ' Entered ' + state.name);
}

After adding the criteria, the same transition from hello to people.person would log the following:

Transition #1 Entered people

Updated: