Matt Hintzke
Matt Hintzke

Reputation: 8004

How to efficiently architect the AngularUI ui-router state machine?

I am very new to the new AngularUI ui-router library that allows us to create nested views using a state machine configuration service and am trying to figure out the best way to architect my states. I am creating an application that uses nested views but my implementation is a little bit different than many of the examples I have been seeing.

The ui-router demo shows how nested views are achieved using the state machine syntax {parent}.{child}.{grandchild} where parent can have the equivalent route /home, /order, /about, etc.

Lets architect out a sample website now. Lets say at the top most level we have 3 views we want to render. The 3 views are /home, /order, and /about. All of these views are basically siblings as far as ui-router is concerned. However, we need an abstract state for the actual root of our application. This state we will call root.

$stateProvider.
    .state('root', {
        abstract: true
        url: '/',
        templateUrl: 'templates/index.html`
    })

templates/index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body>
    <div ui-view></div>
</body>
</html>

Now since root is abstract, we cannot just go directly to / to see our pages, we must invoke one of the child states home, about, or order. Lets create these states

$stateProvider
    .state('home', {
        url:'',
        templateUrl: 'templates/home.html'
    })
    .state('about', {
        url:'/about',
        templateUrl: 'templates/about.html'
    })
    .state('order', {
        url:'/order',
        templateUrl: 'templates/order.html'
    })

Now, if we navigate to /, /about, or /order, we get routed to home, about, and order states respectively. This seems pretty trivial so far, so lets get a little more complicated.

The home and about pages are fairly straight forward and require no extra work, but what if we want to make our order page have internal functionality such as an overview page, an accounts page, and a billings page. Now, navigating to order will actually bring us to overview which will have a nav menu to then travel between overview,accounts, andbillings`.

To get this functionality, we once again have to make order abstract. Doing so allows us to just use a simple ui-view directive that will be used to render either overview, accounts, and billings.

templates/order.html

<ul id="navMenu">
        <li ui-sref-active="active"><a ui-sref="">Back</a></li>
        <li ui-sref-active="active"><a ui-sref=".overview">Overview</a></li>
        <li ui-sref-active="active"><a ui-sref=".accounts" >Accounts</a></li>
        <li ui-sref-active="active"><a ui-sref=".billing">Billing</a></li>
</ul>

<div ui-view></div>

Now our $stateProvider will get the following added on

$stateProvider
    .state('order', {
        abstract: true,
        url: '/order/:id',
        templateUrl: 'templates/order.html'
    })
    .state('order.overview', {
        url: '/overview',
        templateUrl: 'templates/order.overview.html'
    })
    .state('order.accounts', {
        url: '/accounts',
        templateUrl: 'templates/order.accounts.html'
    })
    .state('order.billing', {
        url: '/billing',
        templateUrl: 'templates/order.billing.html'
    })

Notice the order state was modified from before so now it is abstract and can only be accessed by navigating to one of it's children states. Here is where the actual question comes into place. Right now, our "state" tree looks something like

                          root (abstract)
                           / |  \
                          /  |   \
                         /   |    \
                        /    |     \
                     home  order  about
                           / |  \
                          /  |   \
                         /   |    \
                        /    |     \
                       /     |      \
                overview  accounts billing

And everything looks great, but lets say now we want there to be another level under accounts to look at account details. We want the accounts page to have a table of accounts that can be clicked on. When clicked, we want the ENTIRE view to change to an account details page. This page's URL transition from accounts to account.details looks something like

/order/:orderId/accounts OR /order/3/accounts

to

/order/:orderId/accounts/:accountId OR /order/3/accounts/71

Which translates to go to Order 3 with the account Id 71 and view the details page. However in ui-router the accounts.details view is actually a sibling of accounts because we want the entire view to switch over to details instead of just another nested view INSIDE of accounts. The problem with this is we no longer are following the state hierarchy that ui-router is designed for. In a conceptual stand-point, the account details is a child state of accounts, but we want the view to render as an AngularUI state view as a sibling of accounts.

I am trying to figure out what the correct way to architect an entire website like this using the state machine design that ui-router provides. This means knowing when to use an abstract state and when not to.

Could someone show an example of how a complex site is architected using these techniques?

Upvotes: 4

Views: 1200

Answers (1)

Andrew Eisenberg
Andrew Eisenberg

Reputation: 28757

A couple of things here:

This paragraph implies a code smell:

Which translates to go to Order 3 with the account Id 71 and view the details page. However in ui-router the accounts.details view is actually a sibling of accounts because we want the entire view to switch over to details instead of just another nested view INSIDE of accounts. The problem with this is we no longer are following the state hierarchy that ui-router is designed for. In a conceptual stand-point, the account details is a child state of accounts, but we want the view to render as an AngularUI state view as a sibling of accounts.

Your states are not aligning with your views. In general, you should be ensuring this is the case, and I'd try to re-archtect things so that there is alignment. It's best to do this early on before you go too deep into your implementation since things will just get more complex over time. You'll find yourself fighting against the state machine instead of working with it.

Now, there may be a way to get what you want without making any changes to your transitions. The problem you have is not with the states, but the fact that when you transition to a sibling state, the parent state's view is not being refreshed. There are some options that you can use. See the documentation for ui-router $state.

It's likely that you will need to call $state.reload after the transition occurs. Be careful that you don't go into an infinite loop.

Upvotes: 1

Related Questions