Worth Noting
It is worth noting that AngularJs 2.0 is reported to have far better routing than AngularJs 1.x has. I was listening to the Adventures in Angular podcast this week and they were talking about the new routing features coming up. If you are reading this after 2.0 has been released, then it's probably worth your while looking further into native routing rather than using ui-router. I'll make a note to blog about the new routing features as soon as I can lay my hands on them. In the meantime, ui-router is by far the best option in my opinion...Why Use Routing
I think the best use case for client side routing is when you don't want to change the whole page content, but do want the user to be able to link to that section. For instance, you may have a header, footer, hero image, menu, etc., that you want to be displayed regardless of the main content in the page. In my opinion this is the best use case for ui-router. The functionality that it provides is quite powerful, you can even pass parameters between your states. I have created a search that on submit passed the search query to a new state and populated the main grid with search results, while in another state the main grid was populated by a category of products that the user had chosen from a menu.Installing
ui-router is available on both Bower and Node, both of which know it as angular-ui-router. Use the relevant install command, or grab it from GitHub or a CDN, or whatever else works for you. From the root of your application use one of the following commands:npm install angular-ui-router
bower install angular-ui-router
Setting Up Your App
JavaScript
Once you've referenced AngularJs and ui-router in your HTML file you need to tell your app about it using dependancy injection at the module level.var myApp = angular.module('myApp', ['ui-router']);
Next you need to create the configuration. This is a typical configuration that I use. There are many other options and combinations, but this is my preferred basic version.
myApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/');
$stateProvider
.state('stateOne', {
url: '/stateOne',
templateUrl: 'stateOne/index.html',
controller: 'stateOneCtrl',
data: {
pageTitle: 'State one active'
}
})
.state('stateTwo', {
url: '/stateTwo',
templateUrl: 'stateTwo/index.html',
controller: 'stateTwoCtrl'
})
.state('stateThree.details', {
url: '/stateThree/:id',
templateUrl: 'stateThree/index.html',
controller: 'stateThreeCtrl'
});
}]);
Here we inject
$stateProvider
and $urlRouterProvider
. These are both from ui-router and are the dependancies that replace AngularJs' own $routeProvider
. Make sure that you use the array syntax that I have here if you are planning to minify your JavaScript files (you should).The first thing the configuration should have is a default route. Here I've used "/", which means go to the top level URL relative to the page your app is registered on. If you do this in an HTML file in the root of your website then if would route to http://www.example.com/, but if you had your app's HTML file in another directory it would look like http://www.example.com/someDirectory/. This method also works if your HTML is embedded in a back end MVC solution. For instance, I've used ui-router as part of a .NET MVC 4 solution, where my app's root HTML was in Views/someDirectory/. If you have no default content you could use one of the states you later declare as a default route, or if it makes more sense for your user to be delivered to a particular page over another at the start of their journey. This is also the route that gets used if the user has entered a URL that doesn't exist, including due to typos, so make sure that it's not confusing to find oneself here.
Next, use
$stateProvider
to declare all of your states. In my example above I've included two, but there is no limit. This doesn't mean that you should go crazy, use as many states as your application needs and no more. Each .state
corresponds to a single state in your application. The first parameter is the name of your state. Make this something clear, as you're going to be using this throughout your application to tell ui-router which state to transition to. The second parameter contains the configuration settings and options for that state.For me, the starting options are
url
and templateUrl
. url
declares the URL that the browser is requesting, the URL that the user types into the address bar. templateUrl
declares where on the server the HTML file that needs to be served. This HTML file should only be a partial HTML file, and not a fully formed document. The root element should be a block level container element such as div
, article
, or section
. The alternative to templateUrl
is template
, which would then include you HTML inline like so:template: '<div>Some HTML code goes in here</div>'
I don't think this is very tidy and it could mean delivering HTML code that is never used, so I don't use it nor recommend this method.
If you're going to use a controller you can declare it here with
controller
. The value here is the name of the controller that you've declared elsewhere in your AngularJs application, or a function that becomes the controller such as:controller: function($scope){//controller code here}
I prefer to not use it as an inline function like this for the sake of clarity.
You can use URL parameters as a prettier version of query string parameters. This is convenient for the user as it allows them to bookmark a state with any variable information that the system needs to function. As a developer it makes accessing passed values much easier to reference in code. What you type after the colon (:) will be the variable name for the value that is passed in as that parameter.
There are many more options available, but these are the ones that I find most useful. I also use
data
to change things outside of the scope of the state's controller, but that's worth a post to itself. The next two options that I really want to work with are the onEnter
and onExit
callback functions. These clearly have great potential, but I've yet to use them in my current projects.HTML
There's very little that needs to be done in the HTML in the way of setup. One thing you must do is add anui-view
attribute to declare where your content from the template for the current state should be inserted. I tend to put this on a main
element, as whenever I've used ui-router I've only wanted to switch in and out the primary content on the page.<main ui-view></main>
When the user navigates to a new state the content in the
template
option or in the HTML file at the location declared in the templateUrl
option will be injected into this element. Any content that you put inside this element will be deleted when ui-router loads a state for the first time. I have been known to put default content in here, but beware that if it's not in a template then the user is never going to see it again once they've moved into a new state on the page. Every time a new state is loaded, ui-router removes the content of this element and injects the content that is associated with the new state.Changing States
HTML
From the user's perspective a state change is the equivalent of moving between pages, even if the state is the same but reloading with new URL parameters. Due to this I think that all state changes must come due to some direct interaction the user has had with the page. The best way to do this is using click events which trigger some$scope
function or using the a
tag.<a ui-sref="stateOne">Some indication as to where the user is being directed</a>
or if you're using URL parameters:
<a ui-sref="stateThree.details({id:2})">Some indication as to where the user is being directed</a>
When this is sent to the browser ui-router automatically populates the
href
attribute:<a ui-sref="stateOne" href="#/stateOne”>Some indication as to where the user is being directed</a>
or when using parameters:
<a ui-sref="stateThree.details({id:2})" href="#/stateThree/2”>Some indication as to where the user is being directed</a>
When the user clicks on these the URL in their browser's address bar will look like this:
www.example.com/#/stateOne
or this:
www.example.com/#/stateThree/2
JavaScript
To change the state within your JavaScript you need to inject$state
into your controller. $state
is a service built into ui-router.$state.go("stateOne")
or if you're using URL parameters add an object as a second parameter containing the values:
$state.go("stateThree", {id:2})
I'm always concerned when I'm using this method that I'm not utilising ui-router to the best affect. Perhaps this is where I should be using
onEnter
and onExit
. There are two times where I have used this method.The first, and the use case that I think is most justifiable for changing state in the JavaScript, is when I've used URL parameters that have needed checking before they could be used. If I've received bad data then I've redirected the user to either an error page or the home page.
The second occasion is when I've needed to pass user data as a URL parameter. I created a search function that used the same template as the category pages did, but a different controller. When the user submitted the form it triggered a function hanging off the
$scope
where I replaced any spaces with hyphens so that it was safe in the URL. Once the search controller grabs the value then I swap the hyphens back out for spaces before sending a request to the server. I know there are other options for this, but using hyphens allows users to easily create their own search terms directly in the address bar.Retrieving URL parameters
Once your state change has been triggered you can retrieve the values passed as URL parameters in your controller. You need to inject$stateParams
, which is built into ui-router, into your controller. At that point you can use dot notation to retrieve the values as passed, using the variable name that you declared in your configuration:$stateParams.id
You'll get a better efficiency if you need to look at this value more than once by assigning it to a local variable and using that.
No comments:
Post a Comment