UI-Router
Apps with Depth
Nested Views and Routing for Angularjs
Overview for CincyNg Meetup
Sponsored by
Union of Rad
Who I am by day?
Graphic Designer turned
eLearning Course Designer turned
Flash Animator turned
Flash/Flex Developer turned
Web UI/UX Designer turned
Front-end Web Developer turned ?
Who am I by night?
Lazy Loving husbandNeed Input!
Agenda
-
UI-Router's backstory
- Current option, ngRoute module
- Compare ui.router to ngRoute
- Nested states
- State activation
- How to activate a state
- State urls
- Multiple named views
- Abstract states
- Popularity
- Challenges
- Resources
How I Found Angular
Researching JavaScript frameworks for project.
Angular: Easy, Fun, Familiar
Except for one thing
Were others feeling the same?
YUP!
-
Nothing existed in angular at this point
- Found a budding github discussion—AngularUI-backed
-
Gave my .99 cents
-
Some folks (including myself) shaped the initial API
-
Released v0.0.1 on 05.24.2013
Credit
ngRoute module
$route / $routeProvider
(In 1.2 its now a separate module)
$routeProvider (current, single level)
ui-router module
$state / $stateProvider
& $urlRouterProvider
UI-Router's solution
States vs. Routes
States vs. Routes
A lot is the same
$stateProvider.state('contact.detail', {
url: '/contacts/:id',
template: '<h1>Hello</h1>',
templateUrl: 'contacts.html',
controller: function($scope){ ... },
resolve: { ... }
})
$routeProvider.when('/contacts/:id', {
template: '<h1>Hello</h1>',
templateUrl: 'contacts.html',
controller: function($scope){ ... },
resolve: { ... }
})
specifies its url within the config.
Similarities
Difference of When()
ngRoute
ui.router
Main method of adding routes
1.2 changes to route()
Just for creating url redirects
To use when or otherwise, $urlRouterProvider
$urlRouterProvider
.when('/user/:id', '/contacts/:id') .otherwise('/');
Of course, add it
Nesting is where "it" is at!
Let State Views Describe It
a field within
a panel within
a listview within
a page within
a site.
A Typical Use Case
How is this achieved?
index.html
<body>
<ui-view/>
</body>
app.js
.state('top', { template:"<ui-view/>"
}) .state('top.middle', { template:" <ui-view/>"
<ui-view/>" }) .state('top.middle.bottom', { template:"
})
The Dot®
How baby states are made.
$stateProvider
.state("home", { ... });
.state("contacts", { ... });
.state("contacts.detail", { ... });
.state("contacts.detail.edit", { ... });
The dot in the state names auto-denotes parental hierarchy.
It knows that 'contacts.detail' is a child of 'contacts'.
Dot Optional
-
The Dot®
-
'parent' property in config
1. $stateProvider
.state('contacts', {}) .state('contacts.list', {});
2. $stateProvider
.state('contacts', {}) .state('list', { parent: 'contacts' });
Object-based States
var contacts = {
name: 'contacts', //mandatory
templateUrl: 'contacts.html'
}
var contactsList = {
name: 'list', //mandatory
parent: contacts, //mandatory
templateUrl: 'contacts.list.html'
}
$stateProvider
.state(contacts)
.state(contactsList)
Inheritance
- Resolved Dependencies
- Custom Data
Inherited Resolved Dependencies
.state('parent', {
resolve:{
resA: function(){
return {'value': 'A'};
}
},
controller: function($scope, resA){
$scope.resA = resA.value;
}
})
.state('parent.child', {
resolve:{
resB: function(resA){
return {'value': resA.value + 'B'};
}
},
controller: function($scope, resA, resB){
$scope.resA2 = resA.value;
$scope.resB = resB.value;
}
Inherited Custom Data
$stateProvider
.state('parent', { data:{ customData1: "Hello", customData2: "World!" } }) .state('parent.child', { data:{ // customData1 inherited from 'parent' // but we'll overwrite customData2 customData2: "UI-Router!" } }); $rootScope.$on('$stateChangeStart', function(event, toState){ var greeting = toState.data.customData1 + " " + toState.data.customData2; console.log(greeting); })
When 'parent' is activated it prints 'Hello World!'
When 'parent.child' is activated it prints 'Hello UI-Router!'
Activating a State
- Merge options with defaults.
- Check if state is defined, if not, broadcast $stateNotFound.
- Merge params with ancestor params (unless options.inherit = false).
-
Figure out which states are changing and which aren't.
No changes, no transition, unless options.reload = true. - Broadcast $stateChangeStart.
If evt.defaultPrevented, returns rejected promise "transition prevented". - Begin resolving locals for newly activated states.
Activating a State (cont)
Once locals are resolved the real transition can begin:-
Check if superseded by newer transition
-
Run OnExit for all unkept states
-
Run OnEnter for all newly active states
-
Update $state service
- Update $stateParams service
-
Update $location
-
Broadcast $stateChangeSuccess
Activating a Child State
-
Activate implicit root state
- Activate 'contacts' state
- Activate 'contacts.detail' state
- Activate 'contacts.detail.edit' state
- Exit 'contacts.detail.edit' state
- Exit 'contacts.detail' state (contact 1)
- Activate 'contact.detail' state (contact 2)
No redundant work is done.
Callbacks
$stateProvider.state("contacts", {
template: '<h1>{{title}}</h1>',
resolve: { title: 'My Contacts' },
controller: function($scope, title){
$scope.title = 'My Contacts';
},
onEnter: function(title){
if(title){ ... do something ... }
},
onExit: function(title){
if(title){ ... do something ... }
}
})
Events
State Change Events
can e.preventDefault()
good for lazy state definitions
View Load Events
How to Activate a State
(if provided)
$state.go()
myApp.controller('contactCtrl', ['$scope', '$state',
function($scope, $state){
$scope.goToDetails = function(){
$state.go('contact.details', {id: selectedId});
}
}
Relative Navigation
by using special characters.
Examples
Go to parent - $state.go('^')
Go to child - $state.go('.3')
Go to sibling - $state.go('^.1')
Absolute Path - $state.go('3.3')
Hey Cous! - $state.go('^.^.2.1')
ui-sref
<a ui-sref="home">Home</a>
(if url exists)
<a ui-sref="home" href="#/home">Home</a>
ui-sref
<li ng-repeat="contact in contacts">
<a ui-sref="contacts.detail({ id: contact.id })"</a>
</li>
<a ui-sref='^'>Home</a>
In other words the state that loaded the template containing the link.
URLs
In theory...
one could build an app using ui-router that associated not a single url with any state. They could navigate via go() and ui-sref....In theory.
Nested Urls
$stateProvider
.state('contacts', {
url: '/contacts',
})
.state('contacts.list', {
url: '/list',
});
/contacts/list
Absolute Urls
$stateProvider
.state('contacts', {
url: '/contacts',
})
.state('contacts.list', {
url: '^/list',
});
/list
URL Parameters
url: '/contacts/:contactId'
url: '/contacts/{contactId}'
url: '/contacts/{contactId:[0-9a-fA-F]{1,8}}' //Hexadecimals
url: '/contacts?contactId&contactRegion' //Separate with '&'
$stateParams
//State URL:
url: '/users/:id/details/{type}/{repeat:[0-9]+}?from&to'
//Navigate to:
'/users/123/details//0' //$stateParams will be
{ id:'123', type:'', repeat:'0' }
//Navigated to:
'/users/123/details/default/0?from=there&to=here'
//$stateParams will be
{ id:'123', type:'default', repeat:'0',
from:'there', to:'here' }
Two $stateParams Caveats
- Populated when state is activated and all dep's resolved.
In resolve functions, use $state.current.params instead.
- In state controllers, it's scoped to only that state's params.
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
resolve: { depA: function(){
return $state.current.params.contactId + "!" };
},
controller: function($stateParams){
$stateParams.contactId // Exists!
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams){
$stateParams.contactId // Doesn't exist
$stateParams.itemId // Exists!
}
})
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
resolve: { depA: function(){
return $state.current.params.contactId + "!" };
},
controller: function($stateParams){
$stateParams.contactId // Exists!
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams){
$stateParams.contactId // Doesn't exist
$stateParams.itemId // Exists!
}
})
Multiple Named Views
- Provides flexibity
- You get to have multiple views in any template
- Great for singleton top-level components (side panel, modal)
- Many devs put them to great use!
- Often unnecessary, try to nest first!
- Leading cause of confusion
- Can lead to antipatterns
(but I'll give you some good tips on how to avoid them)
Quick Concept
{ name: '', url: '^', 'abstract': true
}
Good Use Case Example
Naming Views
Configure Multiple Views
$stateProvider
.state('deep.down.state.mainbits', {
url: "url/still/goes/up/here"
views: {
'main@': { ... },
'sidenav@': { ... }
}
})
View Name
Pop Quiz!
View Config Object
- template, templateUrl, templateProvider
- controller, controllerProvider
$stateProvider .state('report', { url: "url/still/goes/up/here" views: { 'main': { ... }, 'sidenav': {
templateUrl: "sidenav.html",
controller: "SideNavCtrl"
} } })
Another Use Case
Potential Anti-pattern
Consider
Nested Views for those.
Who's dependent on who?
Really need separated?
Abstract States
-
A state that cannot be explicitly activated.
(attempts to activate it directly will throw)
- It can have child states.
-
It is activated implicity when one of its descendants are activated
Abstract State Usages
-
To prepend a url to all child state urls.
-
To set a template its child states will populate.
- Optionally assign a controller to the template.
- Provide $scope properties/methods for children to inherit
-
To resolves dependencies for child states to use.
-
To set custom data for child states or events to use.
-
To run an onEnter or onExit function.
-
Any combination of the above.
if child states intend to populate with templates.
UI-Router Popularity
Making Waves in Core
Doesn't really matter, because you have access to these features either way!
More Love for UI-Router
Challenges
(v0.3.0 coming very soon)
Is it ready to use?
Fix the bug and submit a pull request!
Roadmap
- $view service - decoupling view functions into own service
- Typed parameters; custom param encoding/decoding
- Two-way binding between url params and $stateParams.
- Orthogonal views with own state trees.
- Components, aka reusable states.
- More hooks for lazy-loading states
The Factos
oh snap!
Thanks!!
Resources
Copy of UI-Router
By Denis Stoyanov
Copy of UI-Router
UI-Router is an angularjs replacement module for ngRoute. It builds upon ngRoute's features by adding nested views and states capabiliites as well as the ability to have multiple named views at any level in the state tree.
- 1,055