The new Angular Router – a simple example

by
Tags: , ,

NOTE: This is very out of date, and you should not refer to this article to use the current Angular 2.0 router. This router API was ditched and replaced with two other variants before the Angular team settled on the released router with version 2.0.0.

One of the features we’ve talked about from ng-conf is the new Angular router. I am giving a talk on the new router for Philly JS (link to come once announced) and so I’ve been working on a demo program.

The new router’s API is a bit unfinished, but it will work against Angular 1.4 and 2.0. This is nice because the router provides a kind of MVC bridge between the two technology stacks.

In this post, we’ll look at how the router is configured, and how you can use it to route to a view area on a page. We’ll follow up with more sophisticated examples later, but for now, this is a simple ‘getting started’ post. I’ve taken a copy of a release candidate of Angular 1.4.0, the Router as of the week of 3/15/2015, and the latest version of Angular Material Design – a nice and simple UI layout API that is a nice alternative to Twitter Bootstrap. I used it for a simple image gallery display program.

Now, this is not yet anything to write home about but shows you some of the basic syntax of the router. The GitHub repo will be used as my example, and I’ll post a link to the repo at the bottom of the post.

Getting started – getting the router

First, the router is only available right now via npm, so you’ll have to install it via:

npm install angular-new-router

I then copied the JavaScript libraries (router.es5.js and router.js) from node_modules/angular-new-router/dist to my web application’s www/lib directory.

Next, I configured Bower to download all of the rest of my libraries, and place them in www/lib/bower_components. I did this by setting up a .bowerrc file:

{
  "directory" : "www/lib/bower_components"
}

I then installed AngularJS, the Angular Material Design libraries, Angular ARIA, and Angular Animate, using bower. I used the latest release candidate of 1.4.0 for each of the Angular libraries and the latest Material Design library.

Next, I set up an HTML page to host the content, configuring the appropriate libraries along the way:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet"
     href="lib/bower_components/angular-material/
           angular-material.css">
</head>
<body layout="column"
     ng-app='photoreview'
     ng-controller='AppController as app'>
<md-toolbar layout="row">
<div class="md-toolbar-tools">
    <span>Gallery App</span>
</div>
</md-toolbar>
<div flex layout="row">
    <md-sidenav md-is-locked-open="$mdMedia('gt-sm')"
      class="md-whiteframe-z2">
     <ul>
       <li><a ng-link="galleries()">Galleries</a></li>
     </ul>
   </md-sidenav>
   <md-content flex id="content">
     <ng-viewport></ng-viewport>
   </md-content>
</div>
<script src="lib/bower_components/angular/angular.js">
</script>
<script src="lib/bower_components/angular-mocks/angular-mocks.js">
</script>
<script src="lib/bower_components/angular-animate/angular-animate.js">
</script>
<script src="lib/bower_components/angular-aria/angular-aria.js">
</script>
<script src="lib/bower_components/angular-material/angular-material.js">
</script>
<script src="lib/router.es5.js">
</script>
</body>
</html>

There are several things we’ve done here to configure the application. First, we’ve named the app itself photoreview, we’ve mounted a top-level controller named AppController and are using the controllerAs syntax to rename it as app on the page. Finally, beyond the directives around material design (<md-content>, <md-toolbar>, <md-sidenav>) there are some new directives.

New Angular Router directives

  • ng-viewport – this directive takes the place of the <ng-view> directive when using the Angular router. It supports multiple (sibling and child) view names, so you can use it much like the ui-router project’s routing directive. This is where the content is displayed.
  • ng-link – this directive navigates to a route component. In simple cases, it gives each routing component a simple method name – like the route to galleries() above on the sidenav. In more complex cases, where a path needs to be configured, the method name of the component will take an object to map the parameters, which we’ll see when we implement the drill-down to a given gallery.

Side note on Angular Material

The directives for Angular Material Design help you style your application without a lot of css or container divs. We’re going to use a few – md-card which creates a nice card layout component in the container, md-sidenav, which provides a sidebar for navigation (which we’ll be using more at a later date), md-toolbar to provide our titlebar, and md-content which provides a content pane for our view area.

Routing instructions

Let’s jump into the AppController and review the routing configuration. Unlike the traditional ngRoute router, which uses a provider to do the configuration, the new router uses a controller – which is our AppController. In app/photo-review-app.js:

  angular.module('photoreview',
	['ngNewRouter', 'ngAnimate', 'ngAria', 'ngMaterial'])
  .controller('AppController', ['$router', AppController]);
  function AppController($router) {
     $router.config([
       { path: '/', redirectTo: '/galleries' },
       { path: '/galleries', component: 'galleries' },
       { path: '/gallery/:id', component: 'gallery' }
     ]);
  }

The router takes an array of routing instructions, each of which has a path, and a component. A component in Angular 1.4 is defined as a directory with the proper component name beneath a directory named components (more about customizing that later). So, we have defined two directories:

components/
   galleries/
      galleries.js
      galleries.html
   gallery/
      gallery.js
      gallery.html

The Galleries component

Let’s take a look at a component – the first one is the galleries component. It is comprised of a controller and template. The controller just requests a collection of galleries from a service:

angular.module('photoreview.galleries', [])
.controller('GalleriesController',
  function(galleryRepositoryService) {
	var vm = this;
	// establish reference
	vm.galleries = galleryRepositoryService.galleries;
  })

Note that I’ve put the controller component in its own module – this is a best practice for the Angular router, as once the team implements lazy loading, they will make it possible to activate the module only when requested. We’ll adjust our main photoreview application to reference it:

  angular.module('photoreview', 'photoreview.galleries',
	['ngNewRouter', 'ngAnimate', 'ngAria', 'ngMaterial'])
...

Now the template. We are using the controllerAs approach – assigning model objects to the controller instance itself, rather than to a $scope object. This is because Angular 2.0 removes scope (and the basic concept of a controller component), and you’ll assign objects to this instead.

<h2>Galleries</h2>
<md-card ng-repeat="gallery in galleries.galleries">
<h3>
   <a ng-link="gallery({ id : $index })">
  {{ gallery.title}}</a>
</h3>
<p>Contains {{ gallery.images.length }} images.</p>
</md-card>

Beyond a little additional work to configure the service, we’re ready to go.

The Gallery component

How do we process routes with parameters? Let’s review that with the Gallery component. First, the fragment from the galleries.html template with the link in it:

{{ gallery.title}}

As I had mentioned above, the ng-link directive provides a link for the router to process. Unlike the original ngRoute router, this one uses a function expression to represent routes. Each route is a named component, with an aliased function. You call the function with an object containing keys from the URL, and values that map to each key, and Angular matches them up. Here is the route instruction for loading a specific gallery (from the photo-review-app.js file above):

{ path: '/gallery/:id', component: 'gallery' }

From there we just inject our old favorite, $routeParams, and pluck the parameter out by its name. Here is the controller:

angular.module('photoreview.galleries.gallery', [])
.controller('GalleryController',
  function($routeParams, galleryRepositoryService) {
    var vm = this;
    var galleryId = $routeParams.id;
    vm.gallery = galleryRepositoryService.galleries[galleryId];
  });

We alias the this to vm, something I picked up from ng-conf. The reason is that we want to assign something to the controller instance, but if we deal with events later on (like a response from a promise) the answer coming back won’t assign this to the controller – it may be assigned to window, for example. The vm alias is a naming convention from John Papa’s Angular Style Guide. He’s also working on the Angular 2.0 version.

Now, our template:

<h2>{{ gallery.gallery.title }}</h2>
<hr/>
<md-card ng-repeat="image in gallery.gallery.images">
<h2>{{ image.name }}</h2>
<p><img src="content/{{ image.path }}"/></p>
</md-card>

Now, I will eventually come up with a better name for these things, but for now, the controller instance automatically gets aliased to gallery. So gallery.gallery is a bit redundant, and doesn’t match vm in the controller. We’ll find a way to address this in a later post.

Testing

I’ve included a NodeJS application to test the router – just clone the repository, use npm install and then run the application with node app.js. The application is very simple, and just crawls the www/content directory looking for galleries. It expects a simple file structure. Browse to http://localhost:4000/#/galleries – I haven’t gotten a default route to work just yet, as it seems there is no otherwise baked in (or I can’t find it, the docs are almost non-existent currently so I’m reading the code).

Wrap-up

So that’s a basic introduction to the routing engine. In our next post I’ll show you some more features, including how to use named viewports and set up master/detail routes. I’ll also clean up the styles and adjust our Angular Material UI a bit. If you want to review the GitHub repository, which currently is kind of ugly but includes a NodeJS server to experiment with, head over to https://github.com/krimple/angular-new-router-demo-galleries.