Complete Guide to Angularjs routes from sctrach for beginners

You will learn following topics in this tutorial for your ease here we will use previously created storyboard app for learning routes in angularjs

  • Components of angularjs routes
  • Creating routes
  • Route parameters
  • Creating and resolving dependencies in routes
  • Route events

Every web application has a URL, and you can use this to define the state of the application. Based on the URL, you can intelligently route the user to the part of the application that they want to see. This technique is called URL routing, and angularjs allows you to implement routing in your web applications with the ngRoute sub-module. Routes help you intelligently decide what to show and how to show it, based on the URL of the application. We’ll spend the rest of this Tutorial discussing the various parts that make routes possible in angularjs, while showing how we can use it in Angello.

7.1. The components of angularjs routes

Routing in angularjs consists of four components that work together to allow you to use URL routes to control the state of your application. See figure 7.1 and table 7.1 for the big picture of how these components work together.

Figure 7.1.ngRoute big picture

Table 7.1. ngRoute components

Component

Responsibility

$routeProvider Configures routes
$route Listens to URL changes and coordinates with the ng-view instance
ng-view Responsible for coordinating the creation of the appropriate controller and view for the current route
$routeParams Interprets and communicates URL parameters to the controller

You’ll see all of these components in action in a moment, but a high-level example would be if, say, you wanted to see the user stories assigned to a specific user. You’d use $routeProvider to configure a route with the $route service to detect when the URL is pointing to a specific user such as /users/123, with 123 being the user’s ID. $route will detect this route and work with ng-view to create the appropriate controller and view to display the user’s stories. The $routeParams service is injected into the controller and exposes the user’s ID from the URL so that the controller can act upon it.

 

How to create routes in angularjs

Now that we’ve identified the major components of ngRoute, it’s time to set up a few routes within our Angello application so we can navigate from page to page. We’ll start out with a basic implementation and build from that foundation.

7.2.1. Create your first route with ngRoute and ngView

Because ngRoute isn’t part of the angularjs core, the first thing we need to do is to include the ngRoute source file. You can download the source file directly from the angularjs website or use the CDN or Bower to fetch the file:

// client/assets/js/boot.js
{ file:
    '//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular-route.min.js'
},

Now that we’ve included the source file, we need to reference the sub-module in our application module definition:

// client/src/angello/Angello.js
var myModule = angular.module('Angello',
    [
        'ngRoute',
        //...
    ]);

7.2.2. Add ngView

One final piece before we start defining our routes: we need to tell Angello where we want to display the route’s rendered template in our application:

 <!-- client/index.html -->
<body ng-controller="MainCtrl as main" ng-class="{loading:loadingView}">
   <!-- ... -->
   <div ng-view=""></div>
   <!-- ... -->
</body>

We accomplish this by adding <div ng-view=""></div> into our main layout file. ngView is responsible for fetching the route template and compiling it with the route’s controller and displaying the finished, compiled view to the user.

Complex layouts

The relationship between a route and a view is one-to-one, which can be a significant disadvantage if you have a complex layout that requires nested views. A great solution to this problem is to use AngularUI Router: https://github.com/angular-ui/ui-router.

 

 

Set up your route with $routeProvider

The first route that we need to set up is for the root of Angello, since this is the entry point for the entire application. The path for this route will follow standard web conventions and consist of a single forward slash. We’ll define the template we want to use for the view and the controller that we need to control that view.

Planning your routes

Routes are always configured in the config block of the module, because it’s behavior that needs to be available as soon as the application runs.

Routes are primarily configured using the when method provided by $routeProvider. The when method takes two arguments: the path parameter and the route configuration object. The path parameter defines the URL pattern that the route will match against, and the route configuration object defines how the matched route is supposed to be handled.

Now that we’ve set up the base route for Angello, we need to set up a way for the viewer to get to the dashboard and users view. We can apply the same templateUrl and controller pattern to accomplish this. The following code illustrates this in action:

What happens if a user tries to go to a route that doesn’t exist? $routeProvider comes with an additional method called otherwise that’s used when a route doesn’t match any other definition. In the preceding code , we call redirectTo from within otherwise to navigate back to the root of the application if no matching route is found.

7.2.4. Set up route navigation

Now that we have our routes defined, it’s time to modify our navigation so that we can navigate to the routes in our application. By default, angularjs uses a hash symbol such as #/users as a reference point for a route. To navigate to the root of the site, we therefore would use <a href="#/"></a> to accomplish the task.

<!-- client/index.html -->
<div class="navbar navbar-fixed-top navbar-default">
   <div class="navbar-header">
       <a class="logo navbar-brand" href="#/">
           <img src="assets/img/angello.png">
       </a>
   </div>
   <div class="btn-group pull-right" ng-show="main.currentUser">
       <a class="btn btn-danger" href="#/">
           <span class="glyphicon glyphicon-home"></span>
       </a>
       <a class="btn btn-danger" href="#/users">
           <span class="glyphicon glyphicon-user"></span>
       </a>
       <a class="btn btn-danger" href="#/dashboard">
           <span class="glyphicon glyphicon-signal"></span>
       </a>
       <button class="btn btn-default" ng-click="main.logout()">
            <span class="glyphicon glyphicon-log-out"></span>
        </button>
   </div>
</div>

Favor anchor tags over programmatically setting routes using $location. Programmatically setting routes breaks a lot of accepted UX patterns such as opening a new tab upon link click.

7.2.5. Review

We’ve just set up three routes for Angello so we can navigate to the storyboard view, the dashboard view, and the users view. To do this, we added ngRoute to our application, set up ngView, and defined our routes with $routeProvider. We added ngRoute to Angello so that the entire application could have routing functionality. We added ngView to the main HTML page so that ngRoute would know where to render the templates for each route. We then defined our routes in the module.config block using $routeProvider. And finally, we updated our navigation to point to the appropriate routes.

 

Using parameters with routes

We’re using routes to define the state of the application, and often we need to use routes to define dynamic portions of the application state. In Angello, we have a view that we want to use to show the user stories assigned to the user, so we need to know what user we need to render the view for (see figure 7.2).

Figure 7.2. Using route parameters to get detailed information

A route path can contain named groups that are delineated with a colon; for example, :userId. $route will attempt to match the path against $location.path and any matched parameters will be stored in the $routeParams service to be injected into the appropriate controller. See figure 7.3.

Figure 7.3. The anatomy of route parameters

If the current URL is /users/123, then $routeParams will have a userId property with a value of 123.

And so let’s add this exact capability to Angello by creating a route for a single user:

// client/src/angello/Angello.js
myModule.config(function ($routeProvider, $httpProvider, $provide) {
    $routeProvider
        //...
        .when('/users/:userId', {
            templateUrl: 'src/angello/user/tmpl/user.html',
            controller: 'UserCtrl',
            controllerAs: 'myUser'
        })
        //...
        .otherwise({redirectTo: '/'});
});

We’ll set the path to /users/:userId, which will attach a userId property to $routeParams so we can use it in the UserCtrl.

Now that we’ve defined the variable in our user route, we need to evaluate it so it’s available as a $scope variable in our UserCtrl:

// client/src/angello/user/controllers/UserController.js
angular.module('Angello.User')
    .controller('UserCtrl',
        function ('$routeParams') {
            var myUser = this;

            myUser.userId = $routeParams['userId'];
        });

To read route parameters, we need to inject the $routeParams service in our controller. The property userId exists on $routeParams, and we can assign the value to myUser.userId by evaluating $routeParams['userId'].

We’ve been approaching route parameters from the inside out, but how do you actually set a parameter on a route? How do you set userId so that you can use it in the users view? The solution is simply a matter of properly crafting a URL link, and angularjs makes this even easier!

<!-- client/src/angello/user/tmpl/users.html -->
<tr ng-repeat="user in users.users">
    <!-- ... -->
    <td>
        <button type="button" class="btn btn-link"
                ng-click="users.removeUser(user.id)">Remove</button>
        <a class="btn btn-link" href="#/users/{{user.id}}">View</a>
    </td>
</tr>

The entry point to the user view will be the users view, and in that page we know about all our users and their IDs. From within our ng-repeat, we’ll add a new link that points to #/users/, and because we have access to the user’s ID, we can bind to it via {{user.id}} to make a complete link of href="#/users/{{user.id}}

 

Review

You’ve just learned how to use the application’s URL as a mechanism for evaluating and passing values from one view to another. We used this technique in Angello to pass a userId variable from the users view to the user view so that we could get specific information for that user. We also saw data binding in action in our users view to dynamically construct the links to the user view with the appropriate userId value for each user.

7.4. Using resolve with routes

One challenge with Angello is that we want to load a user’s available information and a collection of stories to work with before we show the user view. angularjs allows us to handle this situation by defining dependencies on our routes that must be resolved before the route’s controller is instantiated.

At a high level, we want to make sure that the user view is given the correct user from the users view, as well as provide all available stories so that we can assign the correct stories to the provided user. We’ll use the resolve property on the route configuration object (the object that’s passed as the second argument to $routeProvider.when) to define this dependency. The resolve property is an object map that allows us to define multiple dependencies.

The interesting piece about this code is that because UsersModel.fetch(userId) and StoriesModel.all() are returning a promise, we can attach a then method to be called when the promise resolves. At this point we’ll be able to interact with both of these values in the controller. Boom!

And now let’s jump over to the UserCtrl to see the user and stories dependencies being used:

// client/src/angello/user/controllers/UserController.js
angular.module('Angello.User')
    .controller('UserCtrl',
        function ($routeParams, user, stories) {
            //...
            myUser.user = user.data;

            myUser.getAssignedStories = function (userId, stories) {
                var assignedStories = {};

                Object.keys(stories, function(key, value) {
                    if (value.assignee == userId) {
                        assignedStories[key] = stories[key];
                    }
                });

                return assignedStories;
            };

            myUser.stories =
            myUser.getAssignedStories(myUser.userId, stories);
        });

Dependencies defined in the route configuration object are injected just like other services. From within the controller, we can now use these properties at our discretion. In the preceding code, we simply bind the requested user’s data to myUser.user for use in the view; then we take that same data and, in conjunction with the injected stories collection, get all of the stories assigned to the requested user.

Flexibility

If you’re astute, you may have caught the fact that we’re using $routeParams to resolve the user property in the route configuration object, while using it in the UserCtrl as well to get the same value. This is strictly to illustrate the versatility and variations that are available in angularjs. In production, we’d choose the best option and use that exclusively.

What happens if a value returned by resolve makes a remote server call and that call fails? If the return value of a route resolve property is a promise, a $routeChange-Success event is fired when the promise is resolved and ngView will instantiate the appropriate controller and render the template. If the promise is rejected, then a $routeChangeError event is fired and additional handling is necessary.

7.4.1. Review

You’ve just learned how to use resolve in a route definition to create a route dependency that will be injected into our route’s controller. In our example, we called the UsersModel to fetch a user’s information and deliver it to the UserCtrl as a user dependency. We also used the StoriesModel to deliver all of the stories to the UserCtrl as a stories dependency.

 

Route events

At this point, Angello is really starting to take shape in terms of functionality, but there are some UX things that we can do to make the experience better. We have a LoadingService that sets flags on whether or not the application is loading, which is bound to a modal preloader. This provides the viewer with a visual cue that Angello is doing something behind the scenes, as shown in figure 7.4.

Figure 7.4. Using route events to give the viewer appropriate visual feedback

We want to show the loading animation when Angello is changing from one route to another. We can accomplish this by listening for the $routeChangeStart and $routeChangeSuccess events. We’ll set loading to true when the route starts to change, and set it to false when the route change is completed.

Let’s dig into the code to see how this would be accomplished:

7.5.1. Review

You just learned how to use $routeChangeStart and $routeChangeSuccess to detect when Angello was changing from one view to another. This allowed us to set a property on LoadingService to show and hide a modal while the new route was being loaded and resolved.

7.6. Testing

You’re beginning to see a pattern in testing: you define variables global to the test, inject necessary modules, inject the required dependencies, assign dependencies to your global variables, and then test some assertions. Testing a route follows the same pattern; you define your globals (including a variable that holds the URL of the route you want to test), and you inject the $location, $route, $templateCache, and $rootScope dependencies, assigning them for use later in the module. The one thing worth mentioning is that you have to manually grab the correct template and put it in the $templateCache before you can proceed:

client/tests/specs/routes/UserRoute.spec.js
describe('User Route', function () {
    var $route,
        $rootScope,
        $location,
        url = 'login';

    // Inject and assign the $route and $rootScope services.
    // Put the template in template cache.

    beforeEach(module('Angello'));

    beforeEach(inject(function
    (_$location_, _$route_, $templateCache, _$rootScope_) {
        $route = _$route_;
        $rootScope = _$rootScope_;
        $location = _$location_;

        $templateCache.put('src/angello/login/tmpl/login.html', '');
    }));
});

Now all we need to do is test whether our configuration is correct. We do this by using $location to navigate to our URL, invoking a digest cycle with $rootScope.$digest, and then asserting that the current route has the same controller, controllerAs, and templateUrl properties that we defined on the route in the first place.

client/tests/specs/routes/UserRoute.spec.js

describe('User Route', function () {

    //...

    it('should be defined with
    correct controller and templateUrl', function() {
        $location.path(url);
        $rootScope.$digest();

        expect($route.current.controller).toEqual('LoginCtrl');
        expect($route.current.controllerAs).toEqual('login');
        expect($route.current.templateUrl)
        .toEqual('src/angello/login/tmpl/login.html');
    });
});

 

 

Best practices

Your route structure should look like your file structure. In Tutorial 2, we said that a good file structure will often reflect the code structure, and this holds true for routes as well. If a developer can look at your route config and see the parallels between it and the file structure, you can be certain that developer will not only be able to rapidly get up to speed on the flow of your application, but will likely actually enjoy working on your application. Happy developers are productive developers. ‘Nuff said.

Use resolve to get resources via $routeParams whenever possible. In the interest of keeping fat models and skinny controllers, we like to interact with $routeParams within the confines of a resolve block within a particular route. This isn’t a hard-and-fast rule, it’s just the way we like to do things.

Multiple views and side views

Using ngRoute helped us build a solid foundation for routing in angularjs. But it doesn’t support features like multiple views and nested views. The go-to router for advanced routing is ui.router, and we strongly suggest learning how to use it. Visit https://github.com/angular-ui/ui-router/wiki to learn more.

7.8. Summary

Let’s review what we’ve covered in this Tutorial:

  • The main angularjs components that facilitate routings are $routeProvider, $route, $routeParams, and ngView.
  • $routeProvider is responsible for setting up the route definitions and does this in the config block of the application module.
  • $route is responsible for watching $location.path and finding matches with preexisting route definitions. Once a route has been matched, $route hands off the route configuration object to ngView to handle the setup.
  • ngView is responsible for loading the template for the route, compiling the template with the route’s controller, and resolving dependencies defined in the resolve object map.
  • URL parameters are mapped to variables and made available through the $routeParams service.

Practically speaking, we’ve set up multiple routes within Angello that allow us to navigate to various views such as the dashboard view, user view, storyboard view, and so on. You learned to pass values such as userId from one view to another, as in the case of navigating from the users view to the user view. We also used resolve to predetermine if a viewer was logged in and respond appropriately, as well as send in user information and stories to the user view. And finally, we used $routeChangeStart and $routeChangeSuccess to show and hide our loading modal in between route changes.

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
Tags: angularjs

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…

1 day 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…

3 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…

2 weeks 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…

2 weeks 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