UI-Router in angular-client-side-auth

I just merged a big change to the angular-client-side-auth repo, switching out the default Angular routing system, ngRoute, for UI-Router. UI-Router is an excellent project which enables you to organize your application as a finite state machine rather than a collection of pages (with distinct URL's). This allows you to better componentize your application by nesting states.

The introduction of UI-router means that things work a little differently now, and the purpose of this post is to introduce those changes. The first of these is, unexpectedly, the routing setup. Here's an excerpt from the app.js file of the repo to illustrate how the routing setup works with UI-router:

// Anonymous routes
$stateProvider
    .state('anon', {
        abstract: true,
        template: "<ui-view/>",
        data: {
            access: access.anon
        }
    })
    .state('anon.login', {
        url: '/login/',
        templateUrl: 'login',
        controller: 'LoginCtrl'
    })
    .state('anon.register', {
        url: '/register/',
        templateUrl: 'register',
        controller: 'RegisterCtrl'
    });

// Regular user routes
$stateProvider
    .state('user', {
        abstract: true,
        template: "<ui-view/>",
        data: {
            access: access.user
        }
    })
    .state('user.home', {
        url: '/',
        templateUrl: 'home'
    })
    .state('user.private', {
        abstract: true,
        url: '/private/',
        templateUrl: 'private/layout'
    })
    .state('user.private.home', {
        url: '',
        templateUrl: 'private/home'
    })
    .state('user.private.nested', {
        url: 'nested/',
        templateUrl: 'private/nested'
    })
    .state('user.private.admin', {
        url: 'admin/',
        templateUrl: 'private/nestedAdmin',
        data: {
            access: access.admin
        }
    });

The first thing you might notice is that it's much easier to group your application states into "access areas" depending on their access levels. For example, all user states are nested views of the user state, while anonymous states are nested views of the anon state. The top-level state is simply an abstract, URL-less state which sets a common property, data.access, to be inherited by all nested views.

As exemplified in the user.private.* group of states it is also possible to have nested groups below the top-level state. Here the user.private state inherits the data.access property from the user state, then goes on to define another abstract state which the user.private.* states inherit properties from. In this case the user.private.* states inherit the URL property (so state user.private.nested will have a URL of /private/nested) and the template set in user.private. The template of user.private acts as a layout view by implementing the ui-view directive, which will be used by all nested views to inject their own content:

<div class="container">  
    <div class="row">
        <!-- Navigation menu shared among nested views -->
        <nav>
            <!-- ... -->
        </nav>

        <!-- Nested views can inject their own content here -->
        <div data-ui-view="data-ui-view"></div>
    </div>
</div>  

Of course you can choose to organize your routing setup differently, but the important takeaway here is that nested views inherit and reuse properties from their parent views. The other thing that had to be changed was ngRoute's $routeChangeStart event handler. The corresponding event handler in UI-router is called $stateChangeStart, but behaves nearly identically:

$rootScope.$on("$stateChangeStart", 
    function (event, toState, toParams, 
              fromState, fromParams) {
    if (!Auth.authorize(toState.data.access)) {
        $rootScope.error = "Access denied";
        event.preventDefault();

        if(fromState.url === '^') {
            if(Auth.isLoggedIn())
                $state.go('user.home');
            else {
                $rootScope.error = null;
                $state.go('anon.login');
            }
        }
    }
});

The state change will be prevented if the Auth.authorize() call fails, and a redirection occurs if this is the first state the user has tried to access. That's all for now, but feel free to ask questions if something is unclear!

Frederik Nakstad

Read more posts by this author.

Tokyo, Japan