Introduction to $location service in angularjs

angularjs provides an abstraction layer over URLs (and their behavior) in the form of the $location service. This service masks the difference between the hashbang and the HTML5 URL modes allowing us, application developers, to work with a consistent API regardless of the browser and the mode being used. But the $location service does more heavy lifting, by providing the following functions:

  • Provides convenient, clean API to easily access different parts of the current URL (protocol, host, port, path, query parameters, and so on)
  • Lets us to programmatically manipulate different parts of the current URL and have those changes reflected in the browser’s address bar
  • Allows us to easily observe changes in different components of the current URL and take actions in response to those changes
  • Intercepts the users’ interactions with links on a page (such as clicking on the <a> tags) to reflect those interactions in the browser’s history

Understanding the $location service API and URLs

Before we see practical examples of using the $location service, we need to get familiar with its API. Since the $location service works with URLs, the easiest way to get started is to see how the different components of a URL map to the API methods.

Let’s consider a URL that points to a list of users. To make the example more complex, we will use a URL that has all the possible components: a path, a query string, and a fragment. The essential part of the URL could look as follows:

/admin/users/list?active=true#bottom

We could decipher this URL as: in the administration section, list all users that are active and scroll to the bottom of the retrieved list. This is the only part of a URL that is interesting from the point of view of our application, but in reality a URL is the “whole package”, with a protocol, host, and so on. This URL in its full form, will be represented differently, depending on the mode (hashbang or HTML5) used. In the following URL, we can see examples of the same URL represented in both modes. In HTML5 it would look as follows:

http://myhost.com/myapp/admin/users/list?active=true#bottom

In hashbang mode it would take its longer, uglier form, which is as follows:

http://myhost.com/myapp#/admin/users/list?active=true#bottom

Regardless of the mode used, the $location service API will mask the differences and offer a consistent API. The following table shows a subset of the methods available on the API:

Method Given the above example, would return
$location.url() /admin/users/list?active=true#bottom
$location.path() /admin/users/list
$location.search() {active: true}
$location.hash() Bottom

All of these methods are jQuery-style getters. In other words, they can be used to both get and set the value of a given URL’s component. For example, to read the URL fragment value you would use: $location.hash(), while setting the value would require an argument to be supplied to the same function: $location.hash('top').

The $location service offers other methods (not listed in the previous table) to access any part of a URL: the protocol (protocol()), the host (host()), the port (port()), and the absolute URL (absUrl()). The methods are getters only. Theycan’t be used to mutate URLs.

Hashes, navigation within a page, and $anchorScroll

The side effect of using hashbang URLs is that the part of the URL after the # sign, normally used to navigate within a loaded document, is “taken” to represent the URL part interesting from a single-page web application point of view. Still, there are cases where we need to scroll to a specified place in a document loaded into the browser. The trouble is that, in hashbang mode, URLs would contain two # signs, for example:

http://myhost.com/myapp#/admin/users/list?active=true#bottom

Browsers have no way of knowing that the second hash (#bottom) should be used to navigate within a document, and we need a little bit of help from angularjs here. This is where the $anchorScroll service comes into play.

By default the $anchorScroll service will monitor URL fragments. Upon detecting a hash that should be used to navigate within a document, it will scroll to the specified position. This process will work correctly in both the HTML5 mode (where only one hash is present in a URL) as well as in hashbang mode (where two hashes can make it into a URL). In short, the $anchorScroll service does the job which is normally performed by the browser, but taking the hashbang mode into account.

If we want to have more fine-grained control over the scrolling behavior of the $anchorScroll service you can opt out from its automatic URL fragment monitoring. To do so, you need to call the disableAutoScrolling() method on the $anchorScrollProvider service in a configuration block of a module as follows:

angular.module('myModule', [])
  .config(function ($anchorScrollProvider) {
    $anchorScrollProvider.disableAutoScrolling();
  });

By doing these configuration changes, we gain manual control over when scrolling takes place. We can trigger scrolling by invoking the service’s function ($anchorScroll()) at any chosen point of time.

Configuring the HTML5 mode for URLs

By default angularjs is configured to use hashbang mode for URLs. To enjoy nice-looking URLs in HTML5 mode, we need to change the default angularjs configuration, as well as set up our server to support bookmarkable URLs.

Client side

Changing to HTML5 URL mode in angularjs is trivial. It boils down to calling the html5Mode() method on $locationProvider with an appropriate argument as follows:

angular.module('location', [])
  .config(function ($locationProvider) {
    $locationProvider.html5Mode(true);
  })

Server side

For HTML5 mode to work correctly in all cases, we need a little help from the server that is responsible for serving the angularjs application files to the browser. In a nutshell, we need to set up redirections on the web server, so that requests to any deep-linking application URL will be responded with the single-page web application’s starting page (the one that contains the ng-app directive).

To understand why such redirections are needed, let’s examine a situation where a user uses a bookmarked URL (in HTML5 mode) pointing to a product backlog of a specific project. Such a URL could look as follows:

http://host.com/projects/50547faee4b023b611d2dbe9/productbacklog

To the browser, such a URL looks like any other normal URL and the browser will issue a request to the server. Obviously such a URL only makes sense in the context of the single-page web application, at the client side. The /projects/50547faee4b023b611d2dbe9/productbacklog resource doesn’t exist physically on the server and can’t be generated by the server dynamically. The only thing that a server can do with such URLs is to redirect them to the starting point of the application. This will result in the angularjs application being loaded into the browser. When the application starts, the $location service will pick up the URL (still present in the browser’s address bar), and this is where the client-side processing can take over.

Providing further details on how to configure different servers is beyond the scope of this book, but there are some general rules that apply to all servers. Firstly, there are roughly three types of URLs that a typical web server needs to handle:

  • URLs pointing to static resources (images, CSS files, angularjs partials, and so on)
  • URLs for back-end data retrieval or modifications requests (for instance, a RESTful API)
  • URLs that represent features of the application in HTML5 mode, where the server must respond with the application’s landing page (typically a bookmarked URL used, or a URL typed in in a browser’s address bar)

Since it is cumbersome to enumerate all the URLs that can hit a web server due to the HTML5 mode links, it is probably the best to use a well-known prefix for both static resources and URLs used to manipulate data. This is a strategy employed by the sample SCRUM application where all the static resources are served from the URLs with the /static prefix, while the ones prefixed with the /databases prefix are reserved for data manipulation on a back-end. Most of the remaining URLs are redirected to the starting point of the SCRUM application (index.html).

Handcrafting navigation using the $location service

Now that we are familiar with the $location service API and its configuration, we can put the freshly acquired knowledge into practice by building a very simple navigation scheme using the ng-include directive and the $location service.

Even the simplest navigation scheme in a single-page web application should offer some fundamental facilities to make developers life easier. As a bare minimum we should be able to perform the following activities:

  • Define routes in a consistent, easy to maintain manner
  • Update the application’s state in response to URL changes
  • Change the URL in response to the users navigating around the application (clicking on a link, using back and forward buttons in a browser, and so on)

    Note

    In the rest of this book, the term routing is used to denote a facility that helps synchronize the application’s state (both screens and corresponding model) in response to URL changes. A single route is a collection of metadata used to transition an application to a state matching a given URL.

Structuring pages around routes

Before we dive into code samples, we should note that in a typical web application there are “fixed parts” of a page (header, footer, and so on) as well as “moving parts”, which should change in response to the user’s navigation actions. Taking this into account, we should organize markup and controllers on our page in such a way that fixed and moving parts are clearly separated. Building on the previously seen URL examples, we could structure the application’s HTML as follows:

<body ng-controller="NavigationCtrl">
<div class="navbar">
    <div class="navbar-inner">
      <ul class="nav">
        <li><a href="#/admin/users/list">List users</a></li>
        <li><a href="#/admin/users/new">New user</a></li>
      </ul>
    </div>
</div>
<div class="container-fluid" ng-include="selectedRoute.templateUrl">
    <!-- Route-dependent content goes here -->
</div>
</body>

The moving part in the preceding example is represented by a <div> tag with the ng-include directive referencing a dynamic URL: selectedRoute.templateUrl. But how does this expression gets changed based on URL changes?

Let’s examine the JavaScript part, by having a closer look at NavigationCtrl. Firstly, we can define our routes variable as follows:

.controller('NavigationCtrl', function ($scope, $location) {

    var routes = {
      '/admin/users/list': {templateUrl: 'tpls/users/list.html'},
      '/admin/users/new': {templateUrl: 'tpls/users/new.html'},
      '/admin/users/edit': {templateUrl: 'tpls/users/edit.html'}
    };
    var defaultRoute =  routes['/admin/users/list'];
    …
});

The routes object gives a basic structure to the application, it maps all possible partial templates to their corresponding URLs. By glancing over these routes definition, we can quickly see which screens make up our application and which partials are used in each route.

Mapping routes to URLs

Having a simple routing structure in place isn’t enough. We need to synchronize the active route with the current URL. We can do this easily, by watching the path() component of the current URL as follows:

$scope.$watch(function () {
  return $location.path();
}, function (newPath) {
  $scope.selectedRoute = routes[newPath] || defaultRoute;
});

Here, each change in $location.path() component will trigger a lookup for one of the defined routes. If a URL is recognized, a corresponding route becomes the selected one. Otherwise, we fall back to a default route.

Defining controllers in route partials

When a route is matched, a corresponding template (partial) is loaded, and included into the page by the ng-include directive. As you remember the ng-include directive creates a new scope, and most of the time we need to set up this scope with some data that make sense for the newly loaded partial: a list of users, a user to be edited, and so on.

In angularjs, setting up a scope with data and behavior is the job the controller. We need to find a way to define a controller for each and every partial. Obviously, the simplest approach is to use the ng-controller directive in the root element of each partial. For example, a partial responsible for editing users would look as follows:

<div ng-controller="EditUserCtrl">
    <h1>Edit user</h1>
    ...
</div>

The downside of this approach is that we can’t reuse the same partial with different controllers. There are cases where it would be beneficial to have exactly the same HTML markup, and only change the behavior and the data set up behind this partial. One example of such a situation is an edit form, where we would like to reuse the same form both for adding new items editing an existing item. In this situation the form’s markup could be exactly the same but data and behavior slightly different.

The missing bits in the handcrafted navigation

The ad-hoc solution presented here is not very robust, and should not be used as it is in real-life applications. Although incomplete, it illustrates several interesting features of the $location service.

To start with, the $location service API provides an excellent wrapper around various raw APIs exposed by browsers. Not only is this API consistent across browsers, but it also deals with different URL modes (hashbang and HTML5 history).

Secondly, we can really appreciate the facilities offered by angularjs and its services. Anyone who had tried to write a similar navigation system in vanilla JavaScript would quickly see how much is offered by the framework. Still, there are numerous bits that could be improved! But instead of continuing with this custom development, based on the $location service, we will discuss the built-in angularjs solution to these navigation-related issues: the $route service.

Deven Rathore

Deven is an Entrepreneur, and Full-stack developer, Constantly learning and experiencing new things. He currently runs CodeSource.io and Dunebook.com.

Published by
Deven Rathore

Recent Posts

Are There any Similarities Between Web Design and Art?

You may be surprised at how many of the same skills are involved!  Let’s get…

14 hours ago

Tips on Increasing your organic traffic on WordPress

Introduction  As more and more people are using online services nowadays, most of the business…

2 days ago

Five Reasons You Should Start a Social Media Campaign

Small businesses need all the advertisements they can get. Traditional avenues for advertising, such as…

2 days ago

Top 10 SEO Writing Tips for Beginners 2021

Search Engine Optimization. It’s complicated, to say the least. Search engines, like Google, are constantly…

1 week ago

Should you become a freelancer in 2021? Pros and Cons

Freelancing and working from home was long considered idyllic by many, but the global pandemic…

1 week ago

The Leading Renewable Energy Trends in 2021

The past year saw slowdowns in the ongoing shift toward renewable energy in many countries…

2 weeks ago