Key Takeaways
- AngularJS is enhancing its routing capabilities with a new router, currently in progress for Angular 2, which will also be back-ported to Angular 1.4. This router addresses limitations of the ngRoute module, such as the inability to support complex scenarios like nested views, parallel views or a sequence of views.
- The new router simplifies route creation, allows for navigation between templates, and enables the management of multiple parallel views on a page. It also offers flexible control over the lifecycle of components, allowing for interception and control of navigation.
- Despite being a work in progress, the new router is worth experimenting with as it promises to simplify the transition to Angular 2 and is designed to handle complex application scenarios effectively.
AngularJS is one of the most popular JavaScript MV* frameworks and is widely used to build single-page applications (SPA). One of the challenging features in SPAs is routing. Routing on the client side involves changing a portion of the view and creating an entry in the browser’s navigation history. As a fully featured client-side framework, AngularJS has always had support for routing via the the ngRoute
module. Although this is good enough for basic routes, it doesn’t support more complex scenarios, such as nested views, parallel views or a sequence of views.
A new router for Angular 2 is currently in progress and it will be back-ported to Angular 1.4. In this article, we will see how the new router can be used to define routes and how it solves some of the problems that ngRoute
couldn’t.
As already stated, work on the new router is still in progress at the time of writing this article and some of the APIs may later change. The Angular team hasn’t thought of a name for the new router yet, so for now it is called the futuristic router.
Limitations of ngRoute
ngRoute
was not created with complex enterprise applications in mind. I have personally seen applications where certain portions of a page need to be loaded in several steps. Such applications can be built using ngRoute
, but it is almost impossible to have a URL state for every single change applied to the view.
The ng-view
directive can be used only once inside an instance of the ng-app
directive. This prevents us from creating parallel routes, as we cannot have two parallel views loading at the same time.
The view template to be rendered inside ng-view
cannot contain another ng-view
directive. This prevents us from creating nested views.
The new router addresses these issues and provides a flexible way of defining and using routes. The new router also embraces the Controller as
syntax . I highly recommend using the Controller as
syntax, as it is one of the conventions to be followed today to get ready for Angular 2.
Creating Simple Routes
The new router is being created with Angular 2 in mind. Angular 2 will simplify dependency injection by eliminating the module config phase, which means that we don’t need to write a config block to define routes—we can define them anywhere.
Every route to be added to the new router consists of two parts:
path
: URL of the route’s templatecomponent
: a combination of a template and a controller. By convention, both controller and template have to be named after the component
Routes are configured with the $router
service . As $router
is a service, we can define the routes anywhere in the app (other than in a provider or, config block). However, we need to make sure that the block of code defining routes is executed as soon as the app is loaded. For example, if the routes are defined in a controller (as we will do shortly), the controller has to be executed on page load. If they are defined in a service, the service method has to be executed in a run block.
Navigating Between Templates
Let’s define two simple routes and navigate between them using the new router. If you want to follow along with this code, you’ll need to grab a copy of the new router. Show me how.
You can install the new router on a per project basis via npm.
mkdir new-router && cd new-router
npm install angular-new-router
This will create a folder called node_modules
in your project directory. The new router can be found at node_modules/angular-new-router/dist/router.es5.min.js
. Include it in your project after AngularJS itself.
First off, let’s define a module and configure the routes:
angular.module('simpleRouterDemo', ['ngNewRouter'])
.controller('RouteController', ['$router', function($router){
$router.config([
{ path:'/', redirectTo:'/first' },
{ path:'/first', component:'first' },
{ path:'/second/:name', component:'second' }
]);
this.name='visitor';
}])
The controller in the above snippet defines three routes. Notice that the root route redirects to our first template and that the third route accepts a parameter in the URL. As you see, the syntax of specifying the parameter is same as it is for ngRoute
.
As already mentioned, every component requires a corresponding view template and a controller. By convention, the name of controller should be name of the component suffixed with “Controller” (so firstController
and secondController
in our case). The name of the view template has to be the same as the name of the component. It also has to reside in a folder with the same name as the component, inside a folder named components
. This would give us:
projectRoot/
components/
first/
first.html
second/
second.html
These conventions can be overridden using $componentLoaderProvider
. We will see an example of that later, but for now let’s stick to the conventions.
Next come the views for the components first
and second
used above. We’re defining them in-line using the ng-template
directive (so that we can recreate a runnable demo), but ideally they should be in separate HTML files:
<script type="text/ng-template" id="./components/first/first.html">
<h3>{{first.message}}</h3>
</script>
<script type="text/ng-template" id="./components/second/second.html">
<h3>{{second.message}}</h3>
</script>
As the views are very simple, so are the controllers:
angular.module('simpleRouterDemo')
.controller('FirstController', function(){
console.log('FirstController loaded');
this.message = 'This is the first controller! You are in the first view.';
})
.controller('SecondController', function($routeParams){
console.log('SecondController loaded');
this.message = 'Hey ' + $routeParams.name +
', you are now in the second view!';
});
As both of these controllers are created to be used with the Controller as
syntax, they don’t accept $scope
. The $routeParams
service is used to retrieve the values of the parameters passed in the route.
Now, we need to load this controller to have the routes registered:
<body ng-app="simpleRouterDemo" ng-controller="routeController as route">
</body>
Finally, we need to link these routes and load them into the page. The new router brings the ng-link
directive and ng-viewport
directive, which link views and load templates respectively. The ng-viewport
directive is similar to ng-view
; it’s a placeholder for part of your app loaded dynamically based on the route configuration.
The following snippet shows the usage of these directives:
<div class="col-md-3">
<ul class="nav">
<li>
<a ng-link="first">First</a>
</li>
<li>
<a ng-link="second({ name:route.name })">Second</a>
</li>
</ul>
</div>
<div class="col-md-9">
<ng-viewport></ng-viewport>
</div>
See the Pen Navigating Between Templates with Angular’s New Router by SitePoint (@SitePoint) on CodePen.
Dealing with Parallel Views
The directive ng-viewport
can be used any number of times inside an application. Consequently, it is possible to define multiple parallel views on a page. The viewports have to have unique identifiers, so as to load components into them through the route definition.
The following HTML snippet shows how to include multiple viewport directives:
<div class="col-md-3">
<ul class="nav">
<li><a ng-href="#/{{route.name}}">First and Second</a></li>
<li><a ng-href="#/swap/{{route.name}}">Second and First</a></li>
</ul>
</div>
<div class="col-md-9">
<div class="col-md-6" ng-viewport="left"></div>
<div class="col-md-6" ng-viewport="right"></div>
</div>
Imagine we wanted to place this code into a folder called parallel
and the view templates inside parallel/components
. This would give us:
projectRoot/
parallel/
components/
first/
first.html
second/
second.html
As the organization of the code would be different from the convention (by default Angular looks for the the components
folder in the project root), we need to tell the router to look for the views in a new folder. The following config block does this:
angular.module('parallelRouterDemo', ['ngNewRouter'])
.config(['$componentLoaderProvider', function($componentLoaderProvider){
$componentLoaderProvider.setTemplateMapping(function (name) {
return 'parallel/components/' + name + '/' + name + '.html';
});
}])
Routes for this page have to load two components and display them in the different viewports. The configuration object uses the viewports’ unique identifiers to specify which view template is rendered where.
$router.config([
{
path: '/:name', component: {
left: 'first',
right: 'second'
}
},
{
path: '/swap/:name', component: {
left: 'second',
right: 'first'
}
},
{
path: '/',
redirectTo: '/there'
}
])
See the Pen Dealing with Parallel Views Using Angular’s New Router by SitePoint (@SitePoint) on CodePen.
Managing the Lifecycle of Components
The new routing system allows us to intercept the lifecycle of components and control navigation. The interception can be applied by adding one of the following functions to an instance of the controller. These functions may return either Boolean values or, promises. A Boolean true
or a resolved promise would pass through the lifecycle event and a Boolean false
or a rejected promise would cancel further operation.
canReactivate
: Indicates if a view can be re-activated. It can be used to persist the state of the view and optimize the loading time of the view upon subsequent requests.canActivate
: Runs before activating a component. Activates the component when a resolved promise ortrue
is returned and cancels otherwise.canDeactivate
: Runs before deactivating a component. Unloads the component when a resolved promise ortrue
is returned and cancels otherwise.
Using the new router’s lifecycle methods of makes life easier than it was in Angular 1.x, as we don’t need to handle the aggregated events on $scope
to detect a lifecycle event.
Let’s examine how the canActivate
method works by means of an an example. This method can be used to check if a user can access a view before loading it.
this.canActivate = function(){
var hasAccess = userAccessInfo.hasAccessToSecondComponent;
if(!hasAccess){
$window.alert('You don\'t have access to this view.
Redirecting to previous view ...');
}
return hasAccess;
};
The same goes for the canDeactivate
method. This could be used to restrict a user from navigating away from a page with unsaved changes.
this.canDeactivate = function () {
if (this.sampleText) {
var alertResult = $window.confirm('You have unsaved changes.
Do you want to leave the page?');
return alertResult;
}
return true;
};
See the Pen Managing the Lifecycle of Components by SitePoint (@SitePoint) on CodePen.
Note: You will notice that in some circumstances the alert and the confirm dialogue fires twice. This appears to be a bug in the router and I have opened an issue on GitHub.
Conclusion
Angular’s new router has a promising set of features which make it a perfect fit for almost any complex scenario. Although work on the router is still in progress, it is worth experimenting with it today as, in addition to all the nice features it brings, the new router promises to make the transition to Angular 2 easier.
Frequently Asked Questions (FAQs) about AngularJS Routing
What is AngularJS Routing and why is it important?
AngularJS Routing is a core feature in AngularJS that allows you to create single page applications. It enables you to divide your application into multiple views and bind different views to different controllers. This is important because it allows users to navigate through different parts of your application without the need to reload the entire application. It enhances user experience by making your application faster and more responsive.
How do I set up AngularJS Routing?
To set up AngularJS Routing, you first need to include the AngularJS Route module in your application. This can be done by adding the ‘ngRoute’ module as a dependency in your application. Once this is done, you can configure the routes using the ‘$routeProvider’ service. Each route is associated with a template and a controller.
What is the role of $routeProvider in AngularJS Routing?
The $routeProvider is a service in the ngRoute module that is used to configure routes. It has methods like ‘when’ and ‘otherwise’ which are used to define routes. The ‘when’ method takes two arguments: the path and the route. The ‘otherwise’ method is used to define what should be displayed when no routes are matched.
How do I navigate between different views in AngularJS?
Navigation between different views in AngularJS is done using the ‘ng-view’ directive. This directive is used as a placeholder in your main layout where the content of different views will be loaded based on the current route.
Can I pass parameters in AngularJS Routing?
Yes, you can pass parameters in AngularJS Routing. This can be done by adding a colon followed by the parameter name in the route definition. The parameters can then be accessed in the controller using the ‘$routeParams’ service.
How do I handle route changes in AngularJS?
Route changes in AngularJS can be handled using the ‘$routeChangeStart’ and ‘$routeChangeSuccess’ events. These events are broadcasted on the root scope and can be listened to in your controllers.
What is the difference between AngularJS Routing and Angular Routing?
While both AngularJS Routing and Angular Routing serve the same purpose of dividing the application into multiple views, they are implemented differently. AngularJS uses the ngRoute module for routing, while Angular uses the RouterModule.
How do I handle 404 errors in AngularJS Routing?
404 errors in AngularJS Routing can be handled using the ‘otherwise’ method of the $routeProvider. This method is used to define what should be displayed when no routes are matched.
Can I use AngularJS Routing with other JavaScript frameworks?
Yes, AngularJS Routing can be used with other JavaScript frameworks. However, it is designed to work best with AngularJS and may not provide the same level of integration with other frameworks.
What are some common issues faced when using AngularJS Routing?
Some common issues faced when using AngularJS Routing include not including the ngRoute module, not configuring the routes correctly, and not using the ‘ng-view’ directive correctly. These issues can be resolved by ensuring that the ngRoute module is included, the routes are configured correctly, and the ‘ng-view’ directive is used correctly.
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like Angular JS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (ASP.NET/IIS) and DZone MVB awards for his contribution to the community.