Angular hash-tags and resolves

Home / Angular hash-tags and resolves

Angular’s routing does a lot for us. Whether you’re using ui-router or ngRouter, you get a many powerful mechanisms to determine when your state has changed. There are times, though, that I want a “global state.”

What do I mean by global state?

In my mind, a global state is a static state that lasts throughput the life of the application.

This sounds a lot like a singleton service. But, to clarify, let’s consider this example. I like to use ui-router with HTML5 mode off. This makes my URL’s look something like this:

https://localhost/#state?<parm1>=<value1>&<parm2>=<value3>

One immediate problem with this paradigm is that the hash-tag data is not shared with the server. Another problem is that whatever is before the hash is, generally, inaccessible by the $state provider. It’s a bit annoying, honestly. Yet another problem is that if the user hits “page refresh,” we have no “starting point” since all JavaScript objects are destroyed.

Why does this matter? Thinking of a real world sample, it is those parameters before the hash that I want to not only be useful within my Angular app, I want that data to potentially have meaning for the server and be the baseline entry point. I could possibly use it as a mechanism to send a value (or more) from server to client without attempting to mash the hash-tag into my server redirects/urls. This same mechanism can be utilized in in the event that a user is being redirected to the server – or refreshes the page manually.

Additionally, changing the hash-tag manually doesn’t necessarily change state. However, if someone manually modifies my base url (global state), the page will be refreshed and the application/server can respond accordingly.

Let’s look at a real world example.

In one scenario, I have website A that will redirect users to website B when a button on the website A is clicked. Let’s say that one parameter that we want to pass in the URL is an identifier of some sort. I’ll call it widgetId. When the user accesses website B, everything they do in that session will be coupled to the widgetId. If the widgetId changes, it’s, effectively, a whole new session or “global state.”

The redirect URL looks like this:

http://www.serverb.com?widgetId=1234

Server B will simply process the URL and then return the user all of the assets to run the Angular applicaiton. ui-router will then append its state to the end of this URL via the URL hash. From that point forward, the URL will look like this:

http://www.serverb.com?widgetId=1234#/mystate

Sure, I could have just as easily modified the redirect URL into the Angular app like this:

http://www.serverb.com/#/mystate?widgetId=1234

An issue with this approach is that now, I have to grab that value before it’s lost and deal with maintaining it across states.

This looks like a workable solution to me. But Angular, or specifically ui-router, doesn’t let you have access to the query parameters that exist before the hash tag. However, we can remedy this with a dependency resolver and the $location service.

Defining the resolver in the ui-router’s state means we can then use injection to get the resolved values rather than putting logic in our controllers to deal with $location and such. I define the resolve object like so to get my “widgetId” value. Obviously, I wrote this with a switch/case in event that I want to parse N query parameters:

$stateProvider
    .state('main', {
        abstract: true,
        url: '/?widgetId',
        views: {
            'main': { template: '<div id="main-views" data-ui-view></div>' }
        },
        resolve: {
            myData: ['$location', function ($location) {
                var url = $location.absUrl();
                var startIndex = url.indexOf('?') + 1;
                var endIndex = url.indexOf('#');
                var values = url.slice(startIndex, endIndex).split('&');
                var salesId = '';
                if (values.length > 0) {
                    for (var i = 0; i < values.length; i++) {
                        var split = values[i].split('=');
                        if (split.length > 1) {
                            var name = split[0].toLowerCase();
                            switch (name) {
                                case 'widgetId':
                                    widgetId = split[1];
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
                return {
                    widgetId: widgetId,
                };
            }]
        },
    })

To access that resolved data, we $inject “myData” into one of our child state controllers, or wherever else we want to access it. Since we put the resolver on our base abstract view, it becomes available everywhere, afaik. If we can guarantee that this child state is the default/first state, we could also pass the resolved data into a shared/injected service to easily pass around our originally resolved data. Here’s how we’d inject into a controller:

(function () {
    var childController = function (myData) {
        var vm = this;
        vm.widgetId = myData.widgetId;
    }
    childController.$inject = ['myData'];
    angular
        .module('myApp.controllers')
        .controller('childController', childCtrl);
})()

This gets me what I want – a global state. If the user modifies the “widgetId,” they’ll get redirect back to the server and our work flow starts all over. And, if I’m doing something like passing that “widgetId” with my API calls, I have a single point of reference that isn’t easily modifiable by mucking with the URL hash.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.