AngularJS - Introducing Routes

AngularJS is a Javascript MVC framework from the fine folks over at Google. The focus of Angular is building complex HTML based client applications. Its design philosophy is data first, where your data will be updating the DOM. Contrast this to a framework like JQuery where the DOM will update your data.

AngularJS Logo

This is the seventh in a series of posts on AngularJS where we are using Chemistry data from the periodic table to help us understand the framework. The others posts are

  1. AngularJS - Introduction
  2. AngularJS - Introducing AngularJS Controllers
  3. AngularJS - Introducing NG-Repeat
  4. AngularJS - More with NG-Repeat
  5. AngularJS - Image Binding
  6. AngularJS - Introducing Templates
  7. AngularJS - Introducing Routing
  8. AngularJS - Introduction to Services
  9. AngularJS - Introduction to Directives
  10. AngularJS - Further with Directives
  11. AngularJS - Best Practices
  12. AngularJS - CSS Animations

Note: AngularJS does not allow for more than one ng-app directive. When I have multiple angular posts on the home page of my blog, only one application will work. I need to refactor the entire site to account for this. All of that to say this, you are best clicking on a single article so you can see the pages in action.

AngularJS is, at its heart, a Single Page Application (SPA) platform. While you can use it in many ways for client side development, it is great for SPA apps, and that means routing. In this post, we will introduce AnuglarJS routing. More information can be found at the official site on routing.

Routes are a way for multiple views to be used within a single HTML page. This enables you page to look more "app-like" because users are not seeing page reloads happen within the browser. To setup a page for routing there are several steps, but nothing overly difficult.

First, AngularJS requires a service, the route service, which is not part of the default Angular library. You will need to load a separate javascript file, angular-route.js, as part of your script loading. This file can be downloaded as part of minified file from the AngularJS.Org site or via CDN. After downloading, we update our scripts reference to include the routing file as so

<script type="text/javascript" src="../js/lib/angular-route.min.js"></script>

Second, this is our first non-trivial look at the Dependency Injection model in Angular. Angular's Dependency Injection model is crazy slick. When the application starts up, an injector is created that is responsible for loading modules. The injector is unaware of any of our services until we inject them. To take advantage of this, we inject the route service when we create our app module via a parameter. Our chemistryApp.js now looks like

'use strict';

var chemistryApp = angular.module('chemistryApp', ['ngRoute']));

We will be digging deeper into Dependency Injection down the road, but for now it is enough to ensure we pass in our ngroute parameter, which ensures that AngularJS will load the route service.

The next step is to create the routes for our application. For us, we are going to create two routes. The first shows our list of chemical elements, and the second is responsible for displaying details about a given chemical element. The way the routes work, we do a couple of things. First, identify the url that we will be routing. This the HTML # (hashtag) standard to allow us to navigate within the page. While not 100% required, it IS needed for old browsers that don't support the new HTML 5 standard. If you are so inclined, you can setup the route to bypass this with $locationProvider.html5Mode(true). For our example, the URL for a list will be http://site/#/chemlist. Once we have the URL we are mapping to, we define two other items. The first is the controller that the route maps to, in our case chemListCtrl and then finally the templateURL for our HTML fragment that will be loaded\injected into a view container on our page.

We also want to identify a route for a given chemical element, essentially a master-detail approach. This is done by creating a second route where we identify a parameter on the URL that can then be used to retrieve an array element. For our example, we will take the atomicNumber and pass it to a second controller that is responsible for loading a chemical element from our array and making it available to a detail view.

Both of these routes are then chained to our creation of our app like so

'use strict';

var chemistryApp = angular.module('chemistryApp', ['ngRoute']).
    config(function($routeProvider) {
        $routeProvider
            .when('/', {
                templateUrl:'templates/chemList.html',
                controller: 'chemListCtrl'
            }).when('/chemList/:atomicNumber', {
                templateUrl:'templates/chemItem.html',
                controller: 'chemItemCtrl'
            })
            .otherwise({redirectTo: '/'});
    });

Notice, we define a default route for when we are unable to match on a URL.

The next step, other than actually creating our controllers and HTML views is to add a view to our page markup for Angular to inject our template into. That is as simple as

        <data-ng-view></data-ng-view>

Our controllers and templates are very similar to what we have done before. Our controller class is now

chemistryApp.controller('chemListCtrl',
    function chemCtrl($scope) {
        $scope.periodic = periodicData;
    }
).controller('chemItemCtrl',
    function chemCtrl($scope, $log,$routeParams) {
        var elementNumber  = $routeParams.atomicNumber;
        for (var i=0;i<periodicData.elements.length;i++) {
            if (periodicData.elements[i].atomicNumber == elementNumber) {
                $scope.element = periodicData.elements[i];
                break;
            }
        }

    }
);

The most interesting thing to note is that we are now injecting a $routeparams variable when creating our controller for the view we use for an individual item. This allows us to get the parameter for the ID from the route. For example, the url http://site/#/chemList/:5 allows us to grab the parameter for the atomic number using a statement such as var elementNumber = $routeParams.atomicNumber;.

Just to give a quick look at the template and how we form our URL, the template that builds the list is

<div id="elements1" ng-controller="chemListCtrl" class="container">
        <ul>
            <li ng-repeat="element in periodic.elements">
                <a ng-href="#/chemList/{{element.atomicNumber}}">{{element.name}}</a>
            </li>
        </ul>
</div>

Now, we are dynamically loading data and swapping views based on the route we select. Very nice!

Watch it in action

You can either visit http://angularperiodic.azurewebsites.net/ to see the code in action and as always find the code out on GitHub


 

AngularJS - Introducing Templates

AngularJS is a Javascript MVC framework from the fine folks over at Google. The focus of Angular is building complex HTML based client applications. Its design philosophy is data first, where your data will be updating the DOM. Contrast this to a framework like JQuery where the DOM will update your data.

AngularJS Logo

This is the sixth in a series of posts on AngularJS where we are using Chemistry data from the periodic table to help us understand the framework. The others posts are

  1. AngularJS - Introduction
  2. AngularJS - Introducing AngularJS Controllers
  3. AngularJS - Introducing NG-Repeat
  4. AngularJS - More with NG-Repeat
  5. AngularJS - Image Binding
  6. AngularJS - Introducing Templates
  7. AngularJS - Introducing Routing
  8. AngularJS - Introduction to Services
  9. AngularJS - Introduction to Directives
  10. AngularJS - Further with Directives
  11. AngularJS - Best Practices
  12. AngularJS - CSS Animations

Note: AngularJS does not allow for more than one ng-app directive. When I have multiple angular posts on the home page of my blog, only one application will work. I need to refactor the entire site to account for this. All of that to say this, you are best clicking on a single article so you can see the pages in action.

AngularJS has a lot of great features for working with HTML that allows you to split up development when you are doing team projects. The binding approach and directives allows for a nice separation between developers and designers. Another great piece of the platform is the ability to separate out HTML that you are using over and over again into a template file that can then be called by multiple different pages.

In our previous example, we were consistently displaying the same repeatable line in our ng-repeat directive. This is a great example of repeatable markup that we can extract from our page and consolidate into a single location using a template.

To do this, we take our HTML line we were using for the data binding.

    <td><a href="http://en.wikipedia.org/wiki/{{element.name}}" Title="Wikipedia article on {{element.name}}" target="_top">{{element.name}}</a></td>
            <td>{{element.atomicNumber}}</td>
            <td >{{element.atomicWeight}}</td>
            <td>{{element.phase}}</td>
            <td>{{element.ionization}}</td>
            <td>{{element.melting}}</td>
            <td>{{element.boiling}}</td>

and we move it into a separate HTML file, in our case we will call it element1.html. Since we are only dealing with a fragment of HTML, we only want to include the markup we will use for binding, and no other semantically correct markup like the HTML header, etc.

In our controller file, we setup an object that identifies the path to our template as in the following code snippet

$scope.template =  {name: 'template1.html', url: 'js/templates/element1.html'};

Within the HTML page, we then reference the template file using the ng-include directive like so

   <tr data-ng-repeat="element in periodic.elements | filter:{name:elementNameOnlyTable} | limitTo:15" data-ng-include="template.url">

Our page then renders the same as before, but we can now use the template in multiple places. For large teams, a most excellent added benefit is you have removed contention issues on the file. Note: We are only displaying 15 results to limit the page size.

{{$$index}}
NameAtomic NumberWeightPhaseIonization EnergyMelting PointBoiling

Since we our look and feel is controlled by templates, we can also have programmatic control over the look and feel by dynamically setting the template. In our last post, we had committed crimes against web design by using Packer colors.

We can do something similar here by having a second template get loaded by our page. To do this, we update our controller to have an array of templates, and choose which one we want, in this case, an ugly Gold.

controller('chemTemplateCtrl2',
    function chemCtrl($scope) {
        $scope.periodic = perioicData;

        $scope.templates =
            [ { name: 'template1.html', url: 'js/templates/element1.html'}
                , { name: 'template2.html', url: 'js/templates/element2.html'}];
        $scope.template = $scope.templates[1];

    }

Our table now looks even more unattractive

{{$$index}}
NameAtomic NumberWeightPhaseIonization EnergyMelting PointBoiling

Next, given that we have an array of template objects, we can dynamically change the look and feel on the fly using a drop down list to select the template for display. In the example below, we are taking our array of templates and binding to it in a select list. The value of the selected item is then used as the template for page display, dynamically changing the UI.

<div id="periodicTable3"  data-ng-controller="chemTemplateCtrl2" >
    <form id="frmChem3">
        <select data-ng-model="templateSelect" ng-options="template.name for template in templates"></select>


        <input type="text" data-ng-model="elementNameOnlyTable"/>
        <table class="table table-striped table-bordered table-hover table-condensed">
            <tr>
                <th>Name</th><th>Atomic Number</th><th>Weight</th><th>Phase</th><th>Ionization Energy</th><th>Melting Point</th><th>Boiling</th>
            </tr>
            <tr data-ng-repeat="element in periodic.elements | filter:{name:elementNameOnlyTable} | limitTo:15" data-ng-include="templateSelect.url">

            </tr>
        </table>
    </form>
</div>
NameAtomic NumberWeightPhaseIonization EnergyMelting PointBoiling

We can also hide and show various columns based on templates. We will add a third template to our list of elements, but this template will just show the element name, the atomic weight and the atomic number. Since our table header rows exist outside of our template, what we want to be able to do is dynamically hide table header rows when our subset template, element3.html, is selected. We can do this by using a ng-show attribute on the table header elements that binds to our template name like so

<th>Name</th><th>Atomic Number</th><th>Weight</th><th data-ng-show="templateSelect.name!='template3.html'">Phase</th><th data-ng-show="templateSelect.name!='template3.html'">Ionization Energy</th><th data-ng-show="templateSelect.name!='template3.html'">Melting Point</th><th data-ng-show="templateSelect.name!='template3.html'">Boiling</th>
NameAtomic NumberWeightPhaseIonization EnergyMelting PointBoiling

Having "logic" like this, where we are setting columns based on the selected template is not the most correct way to do this though. You rarely (and I mean rarely) want any of the logic that controls the display of the page in the page. We can easily create a function in our controller to set if we are displaying all of the data or just a subset based on the selected template. You can see this in the displayAll function in the final version of our controller

controller('chemTemplateCtrl3',
    function chemCtrl($scope) {
        $scope.periodic = perioicData;

        $scope.templates =
            [ { name: 'template1.html', url: 'js/templates/element1.html'}
                , { name: 'template2.html', url: 'js/templates/element2.html'}
                , { name: 'template3.html', url: 'js/templates/element3.html'}];
        $scope.templateSelect = $scope.templates[0];

        $scope.displayAll =  function() {
            if ($scope.templateSelect.name == $scope.templates[2].name) {
                return false;
            }
            else {
                return true;
            }
        }
    }

and our HTML gets updated to

<th>Name</th><th>Atomic Number</th><th>Weight</th><th data-ng-show="displayAll()">Phase</th><th data-ng-show="displayAll()">Ionization Energy</th><th data-ng-show="displayAll()">Melting Point</th><th data-ng-show="displayAll()">Boiling</th>
NameAtomic NumberWeightPhaseIonization EnergyMelting PointBoiling

As you can see, templates in AngularJS offer a lot of functionality. They allow for splitting up of work across large development teams. You can also dynamically update the look and feel based on the template the user selects. This is all happening client side too, reducing round trips to the server. Good stuff!

You can either visit http://angularperiodic.azurewebsites.net/ to see the code in action and as always find the code out on GitHub


 

AngularJS - Image Binding in AngularJS with Lou Reed

AngularJS is a Javascript MVC framework from the fine folks over at Google. The focus of Angular is building complex HTML based client applications. Its design philosophy is data first, where your data will be updating the DOM. Contrast this to a framework like JQuery where the DOM will update your data.

AngularJS Logo

This is the fifth in a series of posts on AngularJS where we are using Chemistry data from the periodic table to help us understand the framework. The others posts are

  1. AngularJS - Introduction
  2. AngularJS - Introducing AngularJS Controllers
  3. AngularJS - Introducing NG-Repeat
  4. AngularJS - More with NG-Repeat
  5. AngularJS - Image Binding
  6. AngularJS - Introducing Templates
  7. AngularJS - Introducing Routing
  8. AngularJS - Introduction to Services
  9. AngularJS - Introduction to Directives
  10. AngularJS - Further with Directives
  11. AngularJS - Best Practices
  12. AngularJS - CSS Animations

Note: AngularJS does not allow for more than one ng-app directive. When I have multiple angular posts on the home page of my blog, only one application will work. I need to refactor the entire site to account for this. All of that to say this, you are best clicking on a single article so you can see the pages in action.

If you have spent any time with me in the real world, you quickly come to realize I am a music fan. If you have spent a little time with me, especially when I was a younger, you realize I am a huge Lou Reed fan too. My boss, Tim VanHammond still holds it against me the one time I played Lou Reed's the Possum song without headphones when he was in the office we shared. The reality is, I probably spent WAY too much time listening to Lou Reed and Velvet Underground music when I was a teenager. So, having said all that, soon after Lou Reed died, I was doing a presentation at the NorthEast Wisconsin Developer UserGroup (aka, NEWDUG and yes it is a lot to type) and I thought it would be fun to have a little Lou Reed shout out. Since the talk was about AngularJS, it of course had an Angular flavor. So we did a little Lou Reed segue, and talked about binding images using AngularJS. Most important and of interest to you dear reader, is how I found out a way to deal with missing images!

Lou Reed

Instead of using chemistry data, we used Lou data. This data was a JSON object of record releases. Lou put out a lot of albums, and we are loading images, so to keep things quick, we are just doing the first few releases. One of our Lou objects looks like

 {
            "album": "Lou Reed",
            "artist": "Lou Reed",
            "releaseDate": 6/1/1972,
            "image": "http://upload.wikimedia.org/wikipedia/en/8/88/Lour72.jpg",
            "label": "RCA"
        }

Essentially what we are doing is the same stuff that came before, instantiating the app and referencing our controller and looping through the albums and making a little table. This is quickly becoming old hat for us!

Lou Reed Albums

AlbumReleaseProducerLabel
{{album.album}} {{album.releaseDate|date}} {{album.producer}} {{album.label}} {{album.album}} Cover Image

Note: There is an issue with the markup being generated by DocPad prepending the local URL. You can see the actual correct HTML at http://angularperiodic.azurewebsites.net/Demo05-ImageBindingWithLouReed/lou.html until the I fix this.

Again, we have done this before. The interesting thing to note though is the album release date. JavaScript and dates are something that make me a little crazy, but having said that, AngularJS does a nice job. You can pass a JSON date object to a filter and have it format, {{album.releaseDate|date}} as above. More info on the date filter at the AngularJS site.

The second thing to notice is we are using a src attribute in our img tag. Things are loading fine, but if you look at the page load, you will actually see a 404 error that happens.

404 Error on image Get

This is because the AngularJS functionality is executed AFTER the page loads. We are still making the get request that the src attribute had identified because we have yet to get into the javscript processing on the page. With src attributes and href tags you that reference AngularJS objects, this is something you need to be cognizant of. The fix is easy enough, we just prepend a ng tag on our attribute. This will then be part of the page processing that happens during the Angular load. Our image reference is now

<img ng-src="{{album.image}}" alt="{{album.album}} Cover Image" width="300px;" height="300px;">

Adding ng-src

AlbumReleaseProducerLabel
{{album.album}} {{album.releaseDate|date}} {{album.producer}} {{album.label}} {{album.album}} Cover Image
The last thing to hit on is on the image loading, and a cool little trick I learned. One of the customer sites I work at it is using AngularJS and they are integrating with a third party data source. What they wanted to be able to do is not show a broken or missing image. I sat there and thought to myself that this is going to be pretty difficult. Our client side scripting language was going to have to not call the source directly, but go through some kind of nutty proxy we would develop that would check if the image existed and then send that image or else a proxy for the missing image, etc. It wasn't going to be fun. However, a little time on the Google found a MUCH easier way to get by this. To expand on our discussion, our images are coming from Wikipedia and reference images they have for entries on Lou Reed albums. If one of these images changes, we would get an error and display a broken image marker on our page. To illustrate, I have created a second controller where we change the image for Metal Machine Music and Transformer so they would return 404s. We now are receiving missing image links in our page. This is NOT use friendly 404 Error on Album Cover You can see the example below

Missing Images in Javascript Object

AlbumReleaseProducerLabel
{{album.album}} {{album.releaseDate| date:'medium'}} {{album.producer}} {{album.label}} {{album.album}} Cover Image

The fix is easy enough though! We can add an on error function within our img tag. First, we want to add a default local image we can reference. So let us use the Wikipedia image that I have at the top of our page.

Within our img tag we add an onerrror function like so

 onerror="this.src='Lou_Reed.jpg'"

so that our entire img tag is now

<img ng-src="{{album.image}}" alt="{{album.album}} Cover Image" width="300px;" height="300px;"  onerror="this.src='Lou_Reed.jpg'">

and we now handle missing images on page load (with ng-src) and missing images in our data source, with onerror by placing a default placeholder image for missing images.

Resolve missing images

AlbumReleaseProducerLabel
{{album.album}} {{album.releaseDate| date:'medium'}} {{album.producer}} {{album.label}} {{album.album}} Cover Image

So the when I gave my presentation someone asked me favorite Lou Reed album... Hard one, I think at various times most of them have been a favorite, but I will go with New York. Favorite song, same as album, it changes, but let's call it Street Hassle.

You can either visit http://angularperiodic.azurewebsites.net/ to see the code in action and as always find the code out on GitHub

(I am working on some display issues with the post with the Markdown/DocPad combo, I should have updated soon)


 

John Ptacek I'm John Ptacek, a software developer for Skyline Technologies. This blog is my contains my content and opinionss, which are not those of my employer.

Currently, I am reading The Dark Forest by Cixin Liu (刘慈欣)

@jptacekGitHubLinkedInStack Overflow