Transition lifecycle management with hooks for controlling state transitions, implementing cross-cutting concerns, and handling asynchronous operations during navigation. Hooks provide powerful interception points throughout the transition lifecycle.
Service for registering global transition hooks that apply to all matching transitions.
/**
* Transition service for registering global lifecycle hooks
*/
interface TransitionService {
/** Hook executed before transition starts */
onBefore(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
/** Hook executed when transition starts */
onStart(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
/** Hook executed when entering states */
onEnter(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
/** Hook executed when retaining states */
onRetain(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
/** Hook executed when exiting states */
onExit(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
/** Hook executed when transition finishes */
onFinish(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
/** Hook executed on successful transition */
onSuccess(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
/** Hook executed on transition error */
onError(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
}Usage Examples:
angular.module('myApp').run(['$transitions', function($transitions) {
// Authentication check before any transition
$transitions.onBefore({}, function(trans) {
const requiresAuth = trans.to().data && trans.to().data.requiresAuth;
if (requiresAuth && !AuthService.isAuthenticated()) {
return trans.router.stateService.target('login');
}
});
// Loading indicator during transitions
$transitions.onStart({}, function(trans) {
LoadingService.show();
});
$transitions.onFinish({}, function(trans) {
LoadingService.hide();
});
// Log state changes
$transitions.onSuccess({}, function(trans) {
console.log('Navigated from', trans.from().name, 'to', trans.to().name);
});
}]);Register hooks with matching criteria and optional configuration.
/**
* Criteria for matching transitions to hook execution
*/
interface HookMatchCriteria {
/** Match transitions TO these states */
to?: string | string[] | StateDeclaration | StateDeclaration[] | Function;
/** Match transitions FROM these states */
from?: string | string[] | StateDeclaration | StateDeclaration[] | Function;
/** Match transitions between states (from->to pairs) */
between?: string | string[] | StateDeclaration | StateDeclaration[] | Function;
/** Custom matching function */
when?: (transition: Transition) => boolean;
}
/**
* Hook registration options
*/
interface HookRegOptions {
/** Hook priority (higher numbers run first) */
priority?: number;
/** Bind hook function to specific context */
bind?: any;
/** Invoke hook for each matching state (state hooks only) */
invokeEachState?: boolean;
/** Invoke hook once per transition (state hooks only) */
invokeOnce?: boolean;
}Usage Examples:
// Hook for specific target state
$transitions.onBefore({ to: 'admin.**' }, function(trans) {
if (!hasAdminRole()) {
return trans.router.stateService.target('unauthorized');
}
});
// Hook for specific source state
$transitions.onExit({ from: 'editor' }, function(trans, state) {
if (hasUnsavedChanges()) {
return confirm('Discard unsaved changes?');
}
});
// Hook for state transitions
$transitions.onBefore({
from: 'public.**',
to: 'private.**'
}, function(trans) {
return authenticateUser();
});
// Custom matching criteria
$transitions.onStart({
when: function(transition) {
return transition.to().data && transition.to().data.requiresAnalytics;
}
}, function(trans) {
AnalyticsService.trackPageView(trans.to().name);
});
// Hook with priority and options
$transitions.onBefore({}, function(trans) {
// This runs first due to higher priority
return validateTransition(trans);
}, { priority: 100 });Different hook types execute at specific points in the transition lifecycle.
Execute before the transition starts, can prevent or redirect the transition.
/**
* Before hook - executes before transition starts
* @param criteria - Matching criteria for transitions
* @param callback - Hook function
* @param options - Registration options
* @returns Deregistration function
*/
onBefore(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
type TransitionHookFn = (transition: Transition) => HookResult;
type HookResult = boolean | TargetState | void | Promise<boolean | TargetState | void>;Usage Examples:
// Prevent transition based on condition
$transitions.onBefore({ to: 'restricted' }, function(trans) {
if (!hasPermission()) {
return false; // Cancel transition
}
});
// Redirect to different state
$transitions.onBefore({ to: 'oldRoute' }, function(trans) {
return trans.router.stateService.target('newRoute', trans.params());
});
// Async validation
$transitions.onBefore({ to: 'secure.**' }, function(trans) {
return AuthService.validateToken().then(function(valid) {
if (!valid) {
return trans.router.stateService.target('login');
}
});
});Execute when the transition starts, cannot prevent the transition.
/**
* Start hook - executes when transition starts
*/
onStart(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;Usage Examples:
// Show loading indicator
$transitions.onStart({}, function(trans) {
LoadingIndicator.show();
});
// Log transition start
$transitions.onStart({}, function(trans) {
console.log('Starting transition to:', trans.to().name);
});
// Track navigation timing
$transitions.onStart({}, function(trans) {
trans._startTime = Date.now();
});Execute for individual states during transition (onExit, onRetain, onEnter).
/**
* State lifecycle hooks - execute for individual states
*/
onExit(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
onRetain(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
onEnter(criteria: HookMatchCriteria, callback: TransitionStateHookFn, options?: HookRegOptions): Function;
type TransitionStateHookFn = (transition: Transition, state: StateDeclaration) => HookResult;Usage Examples:
// Clean up resources when exiting states
$transitions.onExit({ from: 'editor' }, function(trans, state) {
return EditorService.cleanup();
});
// Initialize state-specific services
$transitions.onEnter({ to: 'dashboard' }, function(trans, state) {
DashboardService.initialize();
});
// Handle state retention
$transitions.onRetain({ to: 'users.**' }, function(trans, state) {
console.log('Staying in users section:', state.name);
});
// Hook with state-specific logic
$transitions.onEnter({}, function(trans, state) {
if (state.data && state.data.trackEntry) {
AnalyticsService.trackStateEntry(state.name);
}
}, { invokeEachState: true });Execute when the transition completes (success or failure).
/**
* Finish hook - executes when transition completes
*/
onFinish(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
onSuccess(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
onError(criteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;Usage Examples:
// Hide loading indicator
$transitions.onFinish({}, function(trans) {
LoadingIndicator.hide();
});
// Log successful transitions
$transitions.onSuccess({}, function(trans) {
const elapsed = Date.now() - trans._startTime;
console.log(`Transition to ${trans.to().name} completed in ${elapsed}ms`);
});
// Handle transition errors
$transitions.onError({}, function(trans) {
console.error('Transition failed:', trans.error());
NotificationService.showError('Navigation failed');
});The Transition object provides information about the current transition.
/**
* Transition object containing transition information and methods
*/
interface Transition {
/** Get source state */
from(): StateDeclaration;
/** Get target state */
to(): StateDeclaration;
/** Get transition parameters */
params(): RawParams;
/** Get injector for resolve data */
injector(): UIInjector;
/** Get transition options */
options(): TransitionOptions;
/** Transition promise */
promise: Promise<any>;
/** Transition success status */
success: boolean;
/** Get transition error if failed */
error(): any;
/** Get transition router instance */
router: UIRouter;
}Usage Examples:
$transitions.onBefore({}, function(trans) {
console.log('From:', trans.from().name);
console.log('To:', trans.to().name);
console.log('Params:', trans.params());
// Access resolve data
const injector = trans.injector();
if (injector) {
const userData = injector.get('user', null);
console.log('User data:', userData);
}
// Check transition options
if (trans.options().reload) {
console.log('This is a reload transition');
}
});Common patterns for using transition hooks effectively.
// Global authentication guard
$transitions.onBefore({}, function(trans) {
const requiresAuth = trans.to().data && trans.to().data.requiresAuth;
const isAuthenticated = AuthService.isAuthenticated();
if (requiresAuth && !isAuthenticated) {
// Store intended destination
SessionService.setReturnUrl(trans.to().name, trans.params());
return trans.router.stateService.target('login');
}
});
// Redirect after login
$transitions.onSuccess({ to: 'login' }, function(trans) {
const returnUrl = SessionService.getReturnUrl();
if (returnUrl) {
SessionService.clearReturnUrl();
return trans.router.stateService.go(returnUrl.state, returnUrl.params);
}
});$transitions.onBefore({}, function(trans) {
const requiredPermissions = trans.to().data && trans.to().data.permissions;
if (requiredPermissions) {
const userPermissions = AuthService.getUserPermissions();
const hasAccess = requiredPermissions.every(permission =>
userPermissions.includes(permission)
);
if (!hasAccess) {
return trans.router.stateService.target('unauthorized');
}
}
});// Preload critical data
$transitions.onStart({ to: 'dashboard' }, function(trans) {
return Promise.all([
UserService.getCurrentUser(),
NotificationService.getUnreadCount(),
ConfigService.getAppConfig()
]).then(function([user, notifications, config]) {
// Data available for dashboard
CacheService.setDashboardData({ user, notifications, config });
});
});// Global error handling
$transitions.onError({}, function(trans) {
const error = trans.error();
// Log error details
console.error('Transition error:', {
from: trans.from().name,
to: trans.to().name,
error: error
});
// Handle specific error types
if (error.type === 'RESOLVE_ERROR') {
NotificationService.showError('Failed to load data');
return trans.router.stateService.go('error.resolve');
} else if (error.type === 'INVALID_PARAMS') {
NotificationService.showError('Invalid parameters');
return trans.router.stateService.go('error.params');
}
// Fallback error handling
return trans.router.stateService.go('error.general');
});All hook registration methods return a deregistration function.
// Register hook and get deregistration function
const deregister = $transitions.onBefore({}, function(trans) {
// Hook logic
});
// Later, remove the hook
deregister();
// Conditional hook registration
let authHook;
if (requiresAuthentication) {
authHook = $transitions.onBefore({}, authenticationGuard);
}
// Clean up when no longer needed
function cleanup() {
if (authHook) {
authHook(); // Deregister
authHook = null;
}
}