This is the multi-page printable view of this section. Click here to print.
Routing
1 - State-based routing in AngularTS applications
AngularTS ships a full-featured router ported from UI-Router. Unlike simple URL-based routers, it models your application as a state machine: each screen or workflow step is a named state, and navigation means transitioning from one state to another. URLs are one way to enter a state, but they are not the primary concept—states are.
Key concepts
The router is built from five interlocking primitives:
- States — named nodes in the application state tree. Each state can have a URL, template, controller, and resolved data.
- Views — the rendered output of a state, inserted into a
ng-viewelement in the DOM. - Resolves — asynchronous data-fetching functions that run before a state is entered.
- Transitions — the lifecycle of moving from one state (or set of states) to another, with hooks you can intercept.
- URL matching — an optional layer that maps browser URLs onto states and keeps them in sync.
How routing integrates with the module system
The router exposes three injectable services that cover the full routing API:
| Token | Type | Purpose |
|---|---|---|
$state | StateProvider | Navigate (go, transitionTo), inspect current state (is, includes, current) |
$transitions | TransitionProvider | Register lifecycle hooks (onBefore, onStart, onSuccess, …) |
$stateRegistry | StateRegistryProvider | Register and deregister state declarations at runtime |
All three share a RouterProvider globals object that tracks the current StateObject, the active Transition, and the latest resolved StateParams. The RouterProvider is injected as $router and holds $router.current, $router.transition, and $router.params.
Setting up the router
Register your states during config
Call $stateProvider.state(declaration) inside an Angular config block, or use
the equivalent module-level module.state(declaration) convenience. States must
have a unique name.
angular.module('app', ['ng.router'])
.config(function ($stateProvider) {
$stateProvider
.state({
name: 'home',
url: '/home',
template: '<h1>Home</h1>'
})
.state({
name: 'contacts',
url: '/contacts',
templateUrl: 'contacts.html',
controller: 'ContactsCtrl'
})
.state({
name: 'contacts.detail',
url: '/:contactId',
resolve: {
contact: function ($transition$, ContactService) {
return ContactService.get($transition$.params().contactId);
}
},
templateUrl: 'contact-detail.html',
controller: 'ContactDetailCtrl'
});
});
The same states can be registered without an explicit config block:
angular.module('app', ['ng'])
.state('home', {
url: '/home',
template: '<h1>Home</h1>'
})
.state('contacts', {
url: '/contacts',
templateUrl: 'contacts.html',
controller: 'ContactsCtrl'
});
Add ng-view to your layout
Place ng-view where you want the active state’s template to render. An unnamed ng-view receives the default view.
<!DOCTYPE html>
<html ng-app="app">
<body>
<nav>
<a ng-sref="home" ng-sref-active="active">Home</a>
<a ng-sref="contacts" ng-sref-active="active">Contacts</a>
</nav>
<!-- Active state template renders here -->
<div ng-view></div>
</body>
</html>
Navigate between states
Use $state.go() in controllers or services to perform programmatic navigation.
angular.module('app')
.controller('ContactsCtrl', function ($state) {
this.viewContact = function (contactId) {
$state.go('contacts.detail', { contactId: contactId });
};
});
The ng-view directive
ng-view is a viewport directive that renders the template and controller for the currently active state. When a transition completes, the view is swapped in. Multiple named views can coexist:
<div ng-view></div>
<!-- Named views -->
<div ng-view="header"></div>
<div ng-view="content"></div>
<div ng-view="sidebar"></div>
A state targets named views through its views property:
name: 'dashboard',
views: {
'header': { template: '<app-header></app-header>' },
'content': { templateUrl: 'dashboard.html', controller: 'DashboardCtrl' },
'sidebar': { component: 'DashboardSidebar' }
}
});
The ng-view directive emits $viewContentLoading before the DOM is rendered and $viewContentLoaded after. It also supports autoscroll and onload attributes.
Router directives
ng-sref
ng-sref generates an href for a state and triggers $state.go() on click. The value is a state name optionally followed by a params object:
<a ng-sref="home">Home</a>
<!-- With parameters -->
<a ng-sref="contacts.detail({ contactId: contact.id })">{{ contact.name }}</a>
<!-- Relative navigation -->
<a ng-sref="^">Up to parent</a>
<a ng-sref=".child">Down to child</a>
The ng-sref-opts attribute passes TransitionOptions:
Dashboard
</a>
ng-sref-active
ng-sref-active adds a CSS class when the linked state (or any of its descendants) is active:
<li ng-sref-active="active">
<a ng-sref="home">Home</a>
</li>
<!-- Multiple class/state mappings -->
<li ng-sref-active="{ 'active': 'contacts', 'exact': 'contacts' }">
<a ng-sref="contacts">Contacts</a>
</li>
ng-sref-active-eq works like ng-sref-active but only adds the class when the state is an exact match (uses $state.is() instead of $state.includes()).
ng-state
ng-state is a dynamic alternative to ng-sref. The target state name is read from a scope expression rather than being hard-coded in the attribute:
ng-state-params="vm.stateParams"
ng-state-opts="{ inherit: false }">
Dynamic link
</a>
This is useful when you build navigation menus driven by data.
Explore further
States
Define state hierarchies, configure resolves, and navigate with $state.go().
Transitions
Intercept the transition lifecycle with hooks for auth guards, analytics, and more.
URL matching
Configure parameterized URLs, typed parameters, hash mode, and base href.
Resolve
Fetch data asynchronously before a state is entered, with eager and lazy policies.
2 - States
A state represents a place in an AngularTS application: a page, a nested layout,
a modal, or a step in a workflow. States are declared as plain objects and
registered through $stateProvider during configuration or $stateRegistry at
runtime.
Exact state and router contracts live in TypeDoc:
StateDeclarationStateProviderStateRegistryProviderTransitionOptionsTransitionPromiseStateResolveObjectStateResolveArrayParamDeclarationHrefOptions
Register States
Register most states in a config block, or use module.state() for the same
provider registration through the fluent module API. Calls to both
$stateProvider.state() and module.state() are chainable.
angular.module("demo", []).config(($stateProvider) => {
$stateProvider
.state({
name: "home",
url: "/home",
component: "homePage",
})
.state({
name: "contacts",
url: "/contacts",
templateUrl: "contacts/list.html",
controller: "ContactsListCtrl",
controllerAs: "vm",
});
});
Equivalent module-level registration:
angular.module("demo", [])
.state("home", {
url: "/home",
component: "homePage",
})
.state("contacts", {
url: "/contacts",
templateUrl: "contacts/list.html",
controller: "ContactsListCtrl",
controllerAs: "vm",
});
Register states at runtime when a feature is loaded after bootstrap.
angular.module("demo").run(($stateRegistry) => {
$stateRegistry.register({
name: "settings",
url: "/settings",
component: "settingsPage",
});
});
Runtime registration requires the parent state to exist first. If the parent is missing, the state is queued until the parent is registered.
Nest States
Use dot notation or an explicit parent property to create a hierarchy.
$stateProvider
.state({
name: "contacts",
url: "/contacts",
template: "<div ng-view></div>",
})
.state({
name: "contacts.list",
url: "/list",
templateUrl: "contacts/list.html",
})
.state({
name: "contacts.detail",
url: "/:id",
templateUrl: "contacts/detail.html",
});
Child states inherit the parent URL prefix. A transition to contacts.detail
with { id: 42 } produces /contacts/42.
A parent state must provide a ng-view outlet where child views can render.
Use Abstract States
Abstract states cannot be activated directly. Use them to share a URL prefix, resolves, metadata, or layout with child states.
$stateProvider
.state({
name: "admin",
url: "/admin",
abstract: true,
template: "<admin-layout ng-view></admin-layout>",
resolve: {
currentUser: (AuthService) => AuthService.currentUser(),
},
data: { requiresAuth: true },
})
.state({
name: "admin.dashboard",
url: "/dashboard",
component: "adminDashboard",
});
Navigating to admin.dashboard enters both admin and admin.dashboard.
Declare Parameters
URL parameters are parsed from path and query segments.
$stateProvider.state({
name: "product",
url: "/products/:category?page&sort",
component: "productList",
});
Non-URL parameters belong in the params block.
$stateProvider.state({
name: "search",
url: "/search?q",
params: {
q: { value: "", squash: true },
filters: { value: null, type: "any" },
},
component: "searchPage",
});
Use parameter declarations for defaults, typed values, dynamic params, array params, squashing, inheritance, and raw URL values.
Resolve Data
Resolves fetch or compute data before a state renders. The router waits for required resolves before entering the state.
$stateProvider.state({
name: "contacts.detail",
url: "/:contactId",
resolve: {
contact: ($transition$, ContactService) =>
ContactService.get($transition$.params().contactId),
contactHistory: [
"contact",
"HistoryService",
(contact, HistoryService) => HistoryService.forContact(contact.id),
],
},
templateUrl: "contact-detail.html",
controller($scope, contact, contactHistory) {
$scope.contact = contact;
$scope.history = contactHistory;
},
});
Use array-style resolves when you need explicit tokens, dependency metadata, or resolve policies.
resolve: [
{
token: "contact",
deps: ["$transition$", "ContactService"],
resolveFn: ($transition$, ContactService) =>
ContactService.get($transition$.params().contactId),
policy: { when: "EAGER", async: "WAIT" },
},
];
Navigate
Use $state.go() for normal application navigation. It supports absolute
states, parent-relative states, sibling-relative states, params, and transition
options.
$state.go("contacts.detail", { contactId: 42 });
$state.go("^");
$state.go("^.list");
$state.go(".detail", { contactId: 42 });
$state.go("home", {}, { location: "replace", reload: true });
$state.go() returns a TransitionPromise, which is a promise with the active
Transition attached as .transition.
Use transitionTo() only when you need lower-level control.
$state.transitionTo("contacts.detail", { contactId: 42 }, {
location: true,
inherit: false,
reload: false,
supercede: true,
});
Inspect And Link
Generate links without navigating:
const url = $state.href("contacts.detail", { contactId: 42 });
const absUrl = $state.href(
"contacts.detail",
{ contactId: 42 },
{ absolute: true },
);
Check active states:
$state.is("contacts.detail");
$state.is("contacts.detail", { contactId: 42 });
$state.includes("contacts");
$state.includes("contacts.**");
$state.includes("*.detail.*.*");
Reload the current state or an ancestor subtree:
$state.reload();
$state.reload("contacts");
Related
3 - Routing transitions and lifecycle hooks
Every navigation in AngularTS is a Transition—a structured object that describes moving from one state (or set of states) to another. Transitions carry the from-state, the to-state, all parameter values, the tree of entering/exiting/retained states, and the resolve context. You hook into their lifecycle to implement auth guards, loading indicators, analytics, scroll resets, and more.
What is a transition
When you call $state.go('contacts.detail', { contactId: 42 }), the router creates a Transition instance. Internally it computes a TreeChanges object with five paths:
| Path | Description |
|---|---|
from | All currently active nodes, from root to the current leaf state |
to | All nodes that will be active after the transition |
exiting | Nodes that are active now but will not be active after (in reverse order, deepest first) |
retained | Nodes that are active both before and after (unchanged) |
entering | Nodes that are not active now but will be active after (parent first) |
The Transition exposes these paths through trans.exiting(), trans.retained(), and trans.entering(), each returning an array of StateDeclaration objects. Use trans.treeChanges(pathname) for the raw PathNode[] arrays.
Each transition has a numeric $id and a promise that resolves to the StateDeclaration for the to-state on success, or rejects with a Rejection on failure.
Transition lifecycle
The router runs hooks in a fixed sequence of phases:
onCreate (synchronous)
Fires during transition construction before the transition is returned. Used internally for view config setup and global state updates. Not available for application hooks.
onBefore (synchronous)
Fires before any state is exited or entered. This is the right place for synchronous guards that should prevent the transition from even starting.
onStart (async)
Fires as the transition starts running—after onBefore is settled but before any states are exited. Eager resolves are fetched here.
onExit (async, deepest first)
Fires for each state being exited, starting with the deepest and moving toward the root.
onRetain (async)
Fires for each state that is being retained (neither entered nor exited).
onEnter (async, shallowest first)
Fires for each state being entered, starting with the parent and moving toward the leaf. Lazy resolves for entering states are fetched before each state’s onEnter.
onFinish (async)
Fires after all onEnter hooks are done. This is the last chance to cancel or redirect before the transition is committed.
onSuccess / onError (synchronous, after commit)
Fires after the transition is fully committed. onSuccess hooks run when the promise resolves; onError hooks run when it rejects. Return values are ignored at this stage.
Registering hooks
All hook registration methods are on the $transitions service (injected as $transitions or ng.TransitionService). Each method returns a deregistration function.
$transitions.onBefore(
matchCriteria: HookMatchCriteria,
callback: (transition: Transition) => HookResult,
options?: HookRegOptions
): () => void // deregister function
Hook match criteria
HookMatchCriteria is an object with optional keys to, from, exiting, retained, and entering. Each value is:
- A state name string or glob (
'contacts.**') - A function
(state, transition) => boolean trueto match any state (the default when the key is omitted)
$transitions.onStart({}, callback);
// Matches transitions going to any child of 'admin'
$transitions.onBefore({ to: 'admin.**' }, callback);
// Matches transitions where a specific state is being exited
$transitions.onExit({ exiting: 'contacts.detail' }, callback);
// Matches using a function predicate
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.requiresAuth === true;
}
}, callback);
Hook registration options
priority: 10, // higher priority runs first (default: 0)
bind: myObject, // `this` inside callback
invokeLimit: 1 // auto-deregister after N invocations
});
Hook return values (HookResult)
The return value of a hook controls the transition:
| Return value | Effect |
|---|---|
undefined / anything else | Transition continues normally |
false | Transition is cancelled (aborted) |
TargetState | Transition is redirected to the new target |
Promise<false> | Transition waits for the promise; cancels if it resolves to false |
Promise<TargetState> | Transition waits for the promise; redirects when it resolves |
Rejected Promise | Transition fails with the rejection reason |
Note:
onSuccessandonErrorhooks run after the transition is committed. Their return values are ignored—you cannot cancel or redirect from these hooks.
Redirecting from a hook
Return a TargetState created with $state.target():
// Always redirect 'home' to 'home.dashboard' as a default substate
return $state.target('home.dashboard');
});
The router internally creates a new transition to the redirect target and chains the promises. If the original transition was triggered by a URL change, the redirect uses location: 'replace' so the original URL is removed from history.
Cancelling a transition
Return false from any hook that runs before onSuccess:
if (appIsLocked) {
return false; // abort
}
});
A cancelled transition is rejected with a Rejection of type ABORTED. The browser URL is reset to the previous location by the built-in updateUrl hook.
Common hook patterns
Authentication guard
This pattern intercepts any transition to a state with data.requiresAuth and redirects unauthenticated users to the login page.
.run(function ($transitions, $state, AuthService) {
$transitions.onBefore(
{
to: function (state) {
return state.data && state.data.requiresAuth;
}
},
function (transition) {
if (!AuthService.isAuthenticated()) {
// Redirect to login, passing the intended destination
return $state.target('login', {
redirectTo: transition.to().name
});
}
}
);
});
Mark states that require authentication:
name: 'admin.users',
url: '/users',
component: 'AdminUsers',
data: { requiresAuth: true }
});
Loading indicator
Show a spinner while any transition is in progress:
.run(function ($transitions, $rootScope) {
$transitions.onStart({}, function () {
$rootScope.isLoading = true;
});
$transitions.onSuccess({}, function () {
$rootScope.isLoading = false;
});
$transitions.onError({}, function () {
$rootScope.isLoading = false;
});
});
Analytics tracking
Fire a page-view event after every successful navigation:
.run(function ($transitions, AnalyticsService) {
$transitions.onSuccess({}, function (transition) {
var toState = transition.to();
AnalyticsService.pageView({
page: toState.name,
url: window.location.pathname,
params: transition.params()
});
});
});
Scroll reset
Scroll to the top of the page on each navigation:
.run(function ($transitions) {
$transitions.onSuccess({}, function () {
window.scrollTo(0, 0);
});
});
Async guard with redirect on failure
Allow users to authenticate mid-transition:
var AuthService = transition.injector().get('AuthService');
if (!AuthService.isAuthenticated()) {
// Return a promise; transition waits for it to settle
return AuthService.authenticate().catch(function () {
return $state.target('guest');
});
}
});
Transition results
A transition can finish in one of four states:
| Result | Description |
|---|---|
| Success | All hooks passed, the to-state is now active. transition.success === true. |
| Error | A hook threw, returned a rejected promise, or a resolve failed. transition.success === false, transition.error() contains the reason. |
| Ignored | The transition targets the same state with the same parameters that are currently active. Treated as success from the transitionTo() perspective. |
| Superseded | A newer transition started before this one finished. The older transition is abandoned; the newer one continues. |
Redirected transitions are not an error from the caller’s perspective: $state.go() transparently chains to the redirect target’s promise.
Accessing the transition object
The active transition is stored on $router.transition. Inside any hook, the Transition instance is passed as the first argument.
console.log('Transitioned to:', transition.to().name);
console.log('From:', transition.from().name);
console.log('Params:', transition.params());
console.log('Entering:', transition.entering().map(s => s.name));
console.log('Exiting:', transition.exiting().map(s => s.name));
console.log('ID:', transition.$id);
});
transition.redirectedFrom() returns the previous transition in a redirect chain. transition.originalTransition() walks the entire chain back to the first transition.
Per-transition hooks
Hooks can also be registered on a specific Transition instance rather than globally on $transitions. These hooks only affect that single transition:
trans.onSuccess({}, function () {
console.log('This specific transition succeeded.');
});
Warning: Per-transition hooks must be registered before the transition has finished running. Register them synchronously in the same call stack as the navigation, or in an
onStartglobal hook.
Dynamic resolvables in hooks
An onBefore hook can add additional resolve data to the current transition using transition.addResolvable(). The added resolvable is available to hooks and views that run after it:
transition.addResolvable({
token: 'requestId',
resolveFn: function () { return generateRequestId(); }
});
});
Transition hook ordering: priority and phase
Within a phase (e.g., all onStart hooks), hooks are invoked in descending priority order. The default priority is 0. Built-in hooks like URL update and view activation use priorities in the range 9000–10000, so application hooks with the default priority run before them.
For onExit, hooks are invoked deepest-first (children before parents). For onEnter, hooks are invoked shallowest-first (parents before children). This ordering is fixed regardless of priority.
4 - URL matching and configuration in AngularTS router
The AngularTS router matches the browser’s URL against registered state declarations and activates the best matching state. URL matching is an optional layer on top of the state machine—states can be navigated to programmatically without any URL involvement—but most applications use URLs to make deep-linking and browser history work correctly.
How URL matching works
When the browser URL changes (or on initial load), the UrlService calls sync(). It iterates all registered URL rules in priority order and finds the best match using a weighted scoring system. The winning rule’s handler is called, which calls $state.go() with the matched state and extracted parameter values.
URL rules are created automatically for every state that has a url property. You can also register custom URL rules directly on $urlService._rules.
UrlService.match(url) accepts a UrlParts object ({ path, search, hash }) and returns a MatchResult with the matching rule, the match data, and the match weight. Rules with the same sort order are ranked by weight so the most specific match wins.
URL parameters
Parameters are declared in the state’s url string. The router uses a UrlMatcher compiled from this pattern to test URLs and extract values.
Path parameters
Use a colon prefix for named path segments:
name: 'user',
url: '/users/:userId',
component: 'UserProfile'
});
// Matches: /users/42 → { userId: '42' }
Use curly braces for parameters with inline type annotations or custom regexps:
name: 'product',
url: '/products/{productId:int}',
component: 'ProductDetail'
});
// Matches: /products/123 → { productId: 123 } (integer, not string)
// Does NOT match: /products/abc
Custom regexp:
name: 'article',
url: '/articles/{slug:[a-z0-9-]+}',
component: 'Article'
});
Query parameters
Append a ? followed by parameter names. Multiple query params are separated by &:
name: 'search',
url: '/search?q&page',
component: 'SearchResults'
});
// Matches: /search?q=angularts&page=2 → { q: 'angularts', page: '2' }
Typed query params:
name: 'messages',
url: '/messages?{before:date}&{after:date}',
component: 'MessageList'
});
Mixed path and query params:
name: 'mailbox',
url: '/messages/:mailboxId?{before:date}&{after:date}',
component: 'Mailbox'
});
Optional parameters
Give a parameter a default value in the params block and set squash: true to make it optional. When the URL is visited without the parameter, the default value is used. When navigating with the default value, the parameter is omitted from the URL:
name: 'userList',
url: '/users/:page',
params: {
page: {
value: '1', // default value
squash: true // remove from URL when value equals default
}
},
component: 'UserList'
});
// /users/ → { page: '1' } (squashed)
// /users/3 → { page: '3' }
Built-in parameter types
The UrlConfigProvider ships the following built-in parameter types. Specify the type inline in the URL pattern or in the params block:
string
Default for path parameters. Encodes and decodes as a plain string. Slashes within the value are encoded as ~2F in AngularTS’s patched path type to avoid ambiguity in Angular 1’s $location.
url: '/items/:name'
// { name: 'foo bar' } → /items/foo%20bar
int
Parses URL segments as integers using parseInt. The pattern is /\d+/.
url: '/users/{id:int}'
// /users/42 → { id: 42 } (number, not string)
bool
Represents boolean values. Encodes true as "1" and false as "0". The pattern matches 1 or 0.
url: '/settings/{darkMode:bool}'
// /settings/1 → { darkMode: true }
json
Encodes arbitrary objects as a JSON string in the URL (URL-encoded). Useful for complex filter objects.
url: '/search?{filters:json}'
// { filters: { status: 'active', role: 'admin' } }
// → /search?filters=%7B%22status%22%3A%22active%22%7D
date
Encodes Date objects as YYYY-MM-DD strings. Parses the string back to a Date on the way in.
url: '/events?{startDate:date}&{endDate:date}'
// { startDate: new Date('2024-01-15') }
// → /events?startDate=2024-01-15
hash
The internal parameter type used for the # (hash/anchor) portion of the URL. Has inherit: false so the hash is not carried forward to child state transitions.
URL configuration with UrlConfigProvider
UrlConfigProvider (injected as $urlConfigProvider in config blocks, or accessed via $url._config at runtime) controls global URL matching behavior.
Case sensitivity
URL matching is case-sensitive by default. Allow case-insensitive matching:
.config(function ($urlConfigProvider) {
$urlConfigProvider.caseInsensitive(true);
});
Strict mode (trailing slashes)
By default, /users/ and /users are distinct. Disable strict mode to treat trailing slashes as equivalent:
.config(function ($urlConfigProvider) {
$urlConfigProvider.strictMode(false); // /users/ matches /users
});
Default squash policy
Control the global default for how parameters with default values appear in URLs:
.config(function ($urlConfigProvider) {
// 'false' (default): include the default value in the URL
// 'true': omit default value from URL
// '~': replace default value with '~' in the URL
$urlConfigProvider.defaultSquashPolicy(true);
});
Custom parameter types
Register a custom ParamType before using it in state URL patterns. The type must implement encode, decode, is, equals, and optionally pattern:
.config(function ($urlConfigProvider) {
// Encodes an array of integers as a dash-separated string
$urlConfigProvider.type('intarray', {
encode: function (array) {
return array.join('-');
},
decode: function (str) {
return str.split('-').map(function (x) { return parseInt(x, 10); });
},
is: function (val) {
return Array.isArray(val) && val.every(function (x) {
return typeof x === 'number' && !isNaN(x);
});
},
equals: function (a, b) {
return a.length === b.length &&
a.every(function (x, i) { return x === b[i]; });
},
pattern: /[0-9]+(?:-[0-9]+)*/
});
});
Use the custom type in a state URL:
name: 'report',
url: '/reports/{ids:intarray}',
component: 'Report'
});
// $state.go('report', { ids: [10, 20, 30] }) → /reports/10-20-30
// /reports/10-20-30 → { ids: [10, 20, 30] }
Note: Register custom types before any state that uses them.
UrlConfigProvider.type()returns the provider itself for chaining.
Hash mode vs HTML5 mode
AngularTS inherits Angular 1’s $location modes. Configure the mode on $locationProvider:
Hash mode (default)
URLs use a hash fragment: http://example.com/app/#/contacts/42. No server configuration is needed; the hash segment is never sent to the server.
angular.module('app')
.config(function ($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!'); // optional: use #!/ instead of #/
});
The UrlService.href() method prepends # (plus the hash prefix) when generating hrefs in hash mode.
HTML5 mode (pushState)
URLs use real paths: http://example.com/contacts/42. The server must return the app’s index.html for all routes.
angular.module('app')
.config(function ($locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: true // <base href="..."> must be present in <head>
});
});
In HTML5 mode, UrlService.href() prepends the base path (stripped of its last segment) rather than a hash.
Base href
In HTML5 mode, the base href is read from the <base> tag in the document <head>:
<base href="/myapp/">
</head>
UrlService.baseHref() returns the current base href. If no <base> tag is present it falls back to window.location.pathname. The base href is used when constructing absolute URLs via $state.href(..., { absolute: true }) and when pushing new history entries.
var base = $url.baseHref(); // "/myapp/"
Reading the current URL
UrlService provides three methods to read URL components:
$url.getPath(); // "/contacts/42"
$url.getSearch(); // { tab: 'notes' }
$url.getHash(); // "section1"
// All three at once
var parts = $url.parts();
// { path: '/contacts/42', search: { tab: 'notes' }, hash: 'section1' }
// The full normalized URL (strips base, adds hash prefix in hash mode)
$url.url(); // "/contacts/42?tab=notes#section1"
Updating the URL
UrlService.url(newUrl) replaces the current URL. The router then calls sync() to find and activate the matching state:
$url.url('/contacts/99?tab=history');
UrlService.push() is the internal method used by the built-in onSuccess hook to update the browser address bar after a successful state transition:
$url.push(state.navigable.url, $state.params, { replace: false });
URL rule priority and matching weight
When multiple rules could match the same URL, the router scores each match and picks the winner:
- Rules are sorted by a primary sort order (rules created from state declarations all share the same group).
- Within a group, the match with the highest weight wins. Weight is computed by counting matched segments, typed parameters, and specificity of regexps.
- A state URL like
/users/{id:int}outweighs/users/:idfor the path/users/42because the typed parameter provides a stricter match.
Exact path matches score higher than prefix matches. Query parameters do not affect path scoring but must all be present if declared without defaults.
Listening for URL changes
UrlService.onChange(callback) registers a low-level listener that fires on every $locationChangeSuccess event. The listener receives the Angular scope event:
console.log('URL changed to:', $url.url());
});
// Stop listening
deregister();
UrlService.listen(false) stops the router from responding to URL changes entirely. Call listen(true) to resume. This is useful when loading states asynchronously and you want to defer URL-driven navigation until the states are registered:
.run(function ($url, $stateRegistry) {
$url.listen(false);
fetch('/api/states')
.then(r => r.json())
.then(function (states) {
states.forEach(s => $stateRegistry.register(s));
$url.listen(true);
$url.sync(); // activate the state matching the current URL
});
});