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 theui-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 togalleries()
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.