Skip to content

Large AngularJS Applications: The Missing Model

2015 October 18
Comments Off on Large AngularJS Applications: The Missing Model
by Alec

Angular is still enjoying quite a bit of popularity these days, and there have undoubtedly been some HUGE apps built with the framework at its core. It’s a joy to get started hacking away at your app, attaching data to your $scope and seeing the magic of two-way binding; but once you start building mid-sized or large apps, some best practices will help you avoid controller bloat and maximize testability and code reuse.

I’ve been building apps with Angular for three years (since version 1.0!) and have incrementally improved my code organization process, lately with a MASSIVE app with 25+ pages and hundreds of directives. The breakthrough for me was to thin down controllers as much as possible. It’s so easy to declare variables on your scope that are immediately available in your templates (just about every example Angular app does this), but you should be absolutely brutal with paring down these variables. Treat your controller as an actual controller — initializing data, delegating events to services/models, and dealing with the page changes. Which gets us to the golden rule:

No business logic in the controllers!

Let me repeat: Keep business logic out of your controllers! No ifs, no maps, and absolutely no math!

But Alec, where do I put all that code? Everybody else just puts it in their controllers!

Stop whining. Angular provides zero guidance as to the Models in your app, but here is a simple, flexible, scalable approach:

The Fat Model Service

Typically, you have two types of data in a web app: Persisted data and view data. Persisted data is what’s getting loaded, manipulated, and saved, and view data is either hard-coded or loaded from the server, but not generally manipulated by the user. For example, take an address form. City, state, and zip are persisted data, while a list of states to be selected from would be view data. For most pages, you’ll need to request some data, either through something like Restangular, $resource, or plain old $http. For anything but the most trivial apps, all code having to do with persistence should be isolated into separate services, to decouple persistence-related code from any DOM-related code (e.g. controllers, directives etc). We’re more interested in what to do with this data, however: how to make it available to the main page template as well as any nested directives within. Let’s look at an example:

In this example, we’re glossing over the data retrieval and just hardcoding a Session object containing the user data we’re interested in. The interesting piece is the UserModel service. Here, we’re exposing a constructor function which initializes an object mainly serving as a wrapper for a piece of data, in this case, a User. In this example we are passing in a Session, which is just a user object, but we could just as easily asynchronously load a User object from the server and instantiate the UserModel within the fulfilled promise. The important thing is that we have defined a place for all the business logic concerning a User. These UserModel properties and functions are now available in the template on the userModel $scope attribute, which itself can be passed to any directives or used in any other controllers in the app. So far, this is great: We have the business logic concerning a User decoupled from anything tied to any view. But it’s equally important to strictly define what shouldn’t go in this new model. For instance, it’s tempting to write something like this:

Note the added save() function in UserModel. All it does is call an internal function to clean the user object up for persistence, persist via the new UserDataService, and redirect to a “user” page. We can even call it directly from the template, leaving us with a one-line controller! Awesome, right?

The problem is that we’ve started mixing concerns. Most egregiously, we’ve injected the $location service into our model, so that every time save() is called, the user is redirected to another page. Let’s say that in a few months there comes a new requirement that users can view and edit their information from a sidebar no matter which page they’re on in the app. On saving their changes from the sidebar, the user would be erroneously redirected: This would require refactoring every piece of code in the app using that function.

The $location service is very much related to view, and now our Model not only knows about page navigation, but initiates it. This is the job of the controller. Let’s look at a cleaner implementation:

Now we’ve moved the triggering of the save operation to the controller. The controller asks the model which properties are appropriate for persistence, then delegates the actual save operation to the UserDataService. On success, the controller redirects to the next page. Now, the model has zero external dependencies, and the controller is triggering state changes as well as persistence, but not handling any business logic. Subsequently, it has become trivially easy to unit test, a surefire sign of any well-designed module. Which brings us to rule #2:

Models should have few external dependencies

I can see injecting other models, and possibly the $filter service, but that should be it. If you’re injecting more than one or two things into your model, there’s a good chance you’re doing it wrong.

AngularJS is an amazing framework for getting quick SPAs off the ground quickly, but the lack of direction regarding Models requires very careful diligence when designing a non-trivial app. I have provided a solution above that has worked very well in several large apps, but the possibilities are endless. Let me know what you think in the comments below.

AngularJS: Overridable directive templates

2014 December 18
Comments Off on AngularJS: Overridable directive templates
by Alec

Sometimes it’s nice to define a default template for a directive, while also allowing the template to be overridden. This isn’t a built-in feature with Angular: if you specify a template it will always be rendered, and transcluded scopes don’t by default have access to the directive’s scope [1].

The key piece of code (as usual) is in the link function:

transcludeFn(scope, function cloneFn(cElement) {
  if (cElement.length) {
    element.empty();
    element.append(cElement);
  }
});

Here, we’re utilizing the transclude function provided by angular: basically, the result of calling $compile on a new sibling scope to the directive’s scope. However, we pass in the directive’s scope so that the transcluded content has access to whatever we define on it. Then, if any content is inside this directive (represented by cElement, an array of zero or more jqLite elements), replace the default template with the transcluded HTML. VoilĂ !

Edit (Jan 15, 2015): This is the first case I have come across where it may be appropriate to use the scope.$parent attribute within the transcluded (overriding) template. I can’t think of any clean way of accessing the data above the directive otherwise, and the accessible scope attributes should be easily seen (and maintained) in the template file html.

[1] More information here: http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html

AngularJS: A Simple, Flexible Filter for Per-page Authorization

2014 April 30
by Alec

One very common use case in any role-based application is to restrict certain operations to users with predefined roles. Say, we have an administration page for managing users, that only users with the “Administrator” role can access. We likely want to hide any links that go to this page from any unauthorized roles, and also prevent direct access through the URL. These could both be implemented with a simple filter:

/* Accepts either a role string such as "Author", a role expression such as
   "'Author' || 'Manager'", or even something like 
  "('Author' && 'Manager') || 'Administrator'"
*/
filter('hasRole', function($parse, Constants) {
  function evalExpression(roleName) {
    var roles = Constants.get("currentUser").roles;
    return _.findWhere(roles, {roleName: roleName}) !== undefined;
  }
 
  return function(roleExpr) {
    // Replace all instances of " with '
    roleExpr = roleExpr.replace(/"/g, "'");
    if(roleExpr.indexOf("'") < 0) {
      return evalExpression(roleExpr);
    } 
 
    var newExpr = roleExpr.replace(/'(\w+)'/g, "evalExpression('$1')");
    return $parse(newExpr)({
      evalExpression: evalExpression,
    });
  };
})

Let’s go through what’s going on above. In this example, we pull a roles array from the current user which is accessible from a generic Constants service. I have a Constants service in every Angular app I write, mainly to keep application-wide constants such as role names, common date formatting strings, and other static data provided by the server. Alternatively, you could easily add a ‘user’ parameter to the filter and pass that to the evalExpression function for additional flexibility.

In the actual returned filter implementation function, after the string.replace function, we check to see if the role expression string has a quote. If it doesn’t, we assume that the expression is just a simple role, and call evalExpression with it. This function uses underscore’s findWhere utility to compare the role with the roleName attribute on each of the user’s roles. Otherwise, if the expression does have a quote, we use a little regular expression trickery to replace all quoted text with a call to the evalExpression function, with the replaced text as the single paramater. For instance, if we use the filter like so:

$filter('hasRole')("'Planner' || 'Administrator'")

the expression in the parameter would become

"evalExpression('Planner') || evalExpression('Administrator')"

which retains the original logic in the expression. The nice thing about this implementation is that the evalExpression function can be as complex as you like, and it will still work, assuming all required parameters are provided.

Finally, we use Angular’s wonderful $parse service to evaluate the just-built expression. $parse returns a function that can be called with a scope object containing values for any variables referenced in the expression. In this case, the only variable is the evalExpression function itself.

Usage

In my last post I talked about ways to enhance Angular’s routing mechanism. This filter would be a prime candidate to use in the $routeChangeSuccess event callback discussed in that post. Something like:

// app.js
.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/facilities/:id', {
    templateUrl: '/templates/facility/view.html', 
    controller: 'FacilityController',
    app: {
      loading: "facility",
      roles: "'Manager' || 'Administrator'"
    }
  });
})
.controller('AppController', function($rootScope, $filter, $location, Messages) {
  $rootScope.$on("$routeChangeSuccess", function(event, current, previous) {
    if(current.app.roles && !$filter('hasRole')(current.app.roles)) {
      Messages.add(Messages.error, "You are not authorized to view that page.");
      $location.path('/');
    }
    ...
  });
});

Which is just one example of a route definition which is restricted to users with either a Manager or Administrator role. The AppController is expected to be an application-wide base controller as explained here.

Another great usage is in a template itself. One caveat: Since the role expression requires each role to be in single quotes, we have to escape each single quote, like so:

<a class="btn btn-primary right" href="#/facility/{{ facility.id }}/edit"
    ng-show="!creatingFacility && ('\'Planner\' || \'Administrator\'' | hasRole)">
  Edit facility
</a>

Don’t forget to enclose the filter expression in parenthesis (as above) when it is part of another logical expression.

AngularJS routing tips and tricks

2014 February 25
by Alec

Routing is a necessary component to any single-page web application. In a traditional web application, links are requested from the server, which renders complete new pages. In a typical Angular app, though, the browser requests both HTML fragments and some data to operate on and display in the HTML. URLs are prefixed with a hash (#), or in “HTML5 mode,” can just be appended to the root URL (with a little server configuration).

One of AngularJS’s primary criticisms is its routing framework. It is certainly lacking, mostly because you can only have one ng-view tag defined — every route’s template will be inserted here. Have another section of HTML that you’d like to change based on the current link? Too bad, you’ll have to hack your own solution using e.g. ng-show.

Once I understood the inherent limitations, though, it became clear that placing additional app-specific configuration under each route definition could be quite powerful. For instance:

$routeProvider.when('/request/document', {
  templateUrl: templatesDir + '/request/document.html', 
  controller: 'RequestDocumentController',
  kts: {
    title: "Request New Document",
    role: "Requestor"
  }
});

The first two configurations for the URL /request/document (templateUrl and controller) are common, and required by $routeProvider. However, the next two are not. I provide a hardcoded title and role, too. For templates with static titles, this works great, and seems like a logical place to define this information. This title can be used both in the <title> tag, and in another section of HTML outside the ng-view definition. The role definition represents a role that is required in order for the user to be able to access the ‘page.’ Here is a snippet of code showing them in use:

.controller('AppController', function($scope, $rootScope, $filter, $location) {
  $rootScope.$on("$routeChangeSuccess", function(event, current, previous) {
    var ktsConfig = current.kts;
    if(ktsConfig) {
      $scope.setTitle(ktsConfig.title || "");
      if(ktsConfig.role && !$filter('hasRole')($scope.user, ktsConfig.role)) {
        $location.path('/unauthorized');
      }
    }
  });
 
  $scope.setTitle = function(title) {
    $rootScope.title = title;
  };
});

AppController in this case is a parent controller to that controlled by the router. In other words, the ng-view tag is inside a div with ng-controller="AppController". After each “page load” (meaning a triggered route), Angular fires a $routeChangeSuccess event. If there is a title defined, it is set on the $rootScope[1] such that html tags outside of the ng-view declaration can display it. Additionally, we do a role check, redirecting the user to an ‘unauthorized’ page if they don’t possess the required role(s) [2].

Clearly, this technique could be extended to define much more route-specific data. I have used it to customize a generic loading dialog per-route, show or hide a map, etc. Use your imagination. Any similar data you keep repeating on most (or all) templates might be a good candidate.

Footnotes:
[1] In general, it is poor practice to share scope variables between controllers, as it breaks encapsulation. However, I don’t know of any other way to use interpolation inside the <title> tag.
[2] Your app may require more stringent security &emdash; for instance, in this case, the user could still hit the template URL directly through a browser and load the non-interpolated html. In my apps, I figure that in this case we’re likely only showing hard-coded templates without any sensitive application data.

AngularJS scoping best practices

2014 January 28
Comments Off on AngularJS scoping best practices
by Alec

Like every other javascript framework out there, AngularJS was designed with certain tradeoffs in mind. It’s easy to get a simple app up and running, and to create moderately complex applications without a deep understanding of the javascript language or any design patterns. You can bind plain javascript objects to templates, and most of the time, it just works. This initial ease of use (happily) insulates us from the complexity of the framework itself, but after repeated use, can expose baffling bugs for certain use cases. There are a few constructs unique to Angular that can easily lead to frustration. While best understood through experience, I’ll attempt to demystify the most common: Seemingly broken two-way bindings on $scope attributes.

Play around with the example below. Notice how all the input fields are referencing the same primitive, amount.

If you change the top field, the rest of the fields update accordingly. But if you change any of the other fields, none of the others update. Furthermore, if you change one of the bottom fields, then change the top field, the previous field doesn’t update anymore! WTF? Two-way binding is broken!? The horror!

Well, not exactly. Let’s dive into what’s going on here. First of all, it’s important to realize that ng-repeat creates a new scope for every instance. So, when the scope is first initialized, the only attributes that exist are amount, people, and three child scopes, one for each instance of the people array. Each child scope references amount in its parent’s scope. If you change the value of the top field, amount is updated, and the three child scopes pick this up and are notified appropriately.

The problem arises when you try and change Jimmy’s allowance. By design, the child scope won’t look through the scope hierarchy for an attribute called amount. Rather, it creates amount on itself and sets the value to be what you just entered. Now, you have an amount attribute on both the parent and child’s scope. These are separate attributes, not in any way tied to one another. The only thing they share is a name. If amount is changed on the parent scope, Jimmy’s scope’s amount isn’t updated. If Jimmy’s amount changes, nothing else is notified since there isn’t any other field referencing it.

So what do we do? You can always reference $parent on any scope, right? Let’s just bind to that! Well, sure, that would work, but it’s generally poor style to reference $scope‘s internal traversal properties such as $parent, $children, etc. For instance, if you were using the ng-repeat section of HTML on several pages, it’d makes sense to pull it out into its own template, then use ng-include to pull it in. Well, since ng-include also creates its own scope, the $parent reference wouldn’t work anymore, and you’d have to change it to $parent.$parent.amount! As your templates get more and more complicated, and you start abstracting code into directives and separate templates, such bindings become increasingly brittle.

So what if the child scope’s input is bound to an attribute on a separate object? Then, when the user updates the input field, it won’t find that object in the current scope, and will have to use the one in the parent scope, right? Bingo. This, in fact, is the best practice as suggested by the AngularJS community.

Now, try editing the fiddle. Create a data object on the main scope, and move amount to that object. Then change the references in the template to data.amount. Voila! Two-way binding as expected.

AngularJS: Fixed-header scrollable table

2013 December 19
by Alec

For the last couple projects I’ve worked on, there has been a need for a Bootstrap-based scrollable table with a fixed header. A pretty common requirement, I’d say. Common enough to inspire quite a few solutions in my current favorite client-side framework, AngularJS.

At the time, there weren’t many options, so I wrote my own directive with a few requirements in mind:

  • Static table height. This is to allow room for more content on the page regardless of how many rows the table has.
  • Pretty column headers. Often, there isn’t enough screen real estate to display the full text for each column header, so when one is too long, truncate it and add ellipsis. None of the existing Angular implementations handle this well, and it turned out to not be an easy feature to implement.
  • Sortable headers. Specify an attribute to sort on and an optional comparator function.
  • Scroll-to-row. Specify an identifier for each row, and the directive will respond to “rowSelected” events, scrolling to display the row.

Everything else was left alone, so that you still have full control over the (transcluded) HTML and can style it or add event handlers wherever you please.

The CSS was inspired by: http://salzerdesign.com/blog/?p=191

Here is the directive in action, complete with sortable headers:

As with any software, some tradeoffs were made to meet my requirements. Here are some of them:

  • Header height is fixed. Overly-long headers are truncated. I typically add a title=”” attribute so users can still see the full text.
  • Headers are no longer included in the calculation of column width. The best workaround here is to add min-widths to table cells.
  • Dynamically adding row content may shift columns so that the header is no longer accurate. There is a watch attribute required by the directive that must be updated in order for the column headers to re-render.

Edit Jan 2, 2014: The code is now on GitHub, for your forking pleasure (ahem).

AngularJS multi-select widget

2013 August 22
by Alec

In most web applications, there are entities that have many-to-many relationships. The most common example is a user-role scenario. In this case, there are typically many roles in a system, as well as many users, and each exist independent of the other. A user can be associated with zero or more roles, while a role can be applied to zero or more users.

So how do we present a user-friendly interface for modifying these relationships? This is a common problem for web applications, and there seems to exist a common interface with which most users are familiar. You simply present two lists of entities side-by-side, with “add” and “remove” buttons to move objects from one side to the other. The left side represents “selected” entities, and the right the “available” ones not already selected.

I wasn’t able to find an Angular directive implementing such an interface, so, naturally, I made my own. It relies on bootstrap for styling and uses a
<select type="multiple"> to represent each list of entities. Click “Result” below to see it in action:

The directive expects two arrays: An “available” array of entities, and “ng-model”, which should be a subset of those entities. These arrays don’t even need to have been initialized yet, the directive will wait until they are first initialized before creating the widget. Also, comparisons are done by reference, not equality, so the ng-model entities should be the same objects as those in “available.” This could easily be changed through usage of angular’s angular.equals method.

Try it out, let me know what you think, and fork away!

Update (Oct 7, 2013): I tweaked this to use a list of checkboxes rather than <select type="multiple">. This functions much better on mobile devices, and as arguably a better user experience overall. See the fiddle here.

Update (Jan 2, 2014): Added more flexibility for configuring the display of an item. The directive now accepts a simple expression (e.g. 'user as user | fullName') for the display attribute. Also, refactored the directive into its own module and added to GitHub.

AngularJS: A lazily-loaded recursive tree widget

2013 August 19
by Alec

Note (July 2014): I can no longer recommend this directive as there are far better tree directives out there. As always, YMMV.

Original post:
I had the chance to use the wonderful AngularJS javascript framework for another project, so I jumped on it. One requirement was a tree representation of some data, where children could be loaded dynamically. A quick google search revealed a dearth of tree widgets, so I hacked something together myself. Inspiration came from Andy Joslin’s recursive tree implementation.

Often, a dataset is too large to be efficiently preloaded on the client-side, so it makes sense to lazily load a node’s children on demand. In this implementation, the loading function can either return a promise or an array of child nodes.

Features:

  • Node selection. Catch a “nodeSelected” event on the controller’s scope. You can also pass an ID for the node which should be initially selected.
  • Dynamic loading of children. Define a function that will be called with the node that is being expanded.
  • Auto-expansion. Define a comma-separated hierarchy of node ids to expand.

This is a very rough implementation, surely replete with bugs, but has functioned thus far for my uses. Usage, suggestions and forks are all welcomed. Try it out:

Edit (2/28/14): Finally posted the source code to GitHub. I should also mention that of all the modules I’ve open-sourced, this is by far the least-tested. It is the only one not to have seen extensive production use.

Providing Django template variables as constants to AngularJS

2012 November 16
by Alec

Over the past few weeks I’ve been using AngularJS to rewrite an enterprise-level configuration web UI containing 90 or so fields. Two-way binding was a natural fit and keeping the data model as a javascript object has made the code quite clean and concise. For a while I was struggling with a good way to handle constants in Angular, including variables coming from the server as Django template variables. This is what I came up with as a solution.

First, redefine the interpolation symbols since they conflict with Django’s:
# module.js

angular.module('app.services', []);
angular.module('app', ['app.services'], 
  function($interpolateProvider) {
    $interpolateProvider.startSymbol('{[');
    $interpolateProvider.endSymbol(']}');
  }
);

Then, create a Constants service which can be injected into controllers on demand. I like the factory() function as it allows you to create private variables as well as exposing a public API:
# services/constants.js

angular.module('app.services')
.factory('Constants', function(DjangoConstants) {
  // define UI constants here
  var constants = {
    customSelect: "Custom",
    keyAudio: "audio"
  };
  // pull in the django constants
  angular.extend(constants, DjangoConstants);
 
  return {
    get: function(key) {
      return constants[key];
    },
    // this is a handy way to make all constants available in your HTML 
    // e.g. $scope.c = Constants.all() 
    all: function() {
      return constants;
    }
  };
});

Finally, pass the django constants to the main module via the constant() method:
# snippet of config.html, a django partial

<script src='{{ STATIC_URL }}js/lib/angular/angular.js'></script>
<script src="{{ STATIC_URL }}js/config/module.js"></script>
<script type="text/javascript">
  // inject some constants from django
  angular.module('app').constant("DjangoConstants", {
    serverName: '{{ server.host }}',
    csrfToken: '{{ csrf_token }}'
  });
</script>

Usage:
# controllers/input.js

function InputCtrl($scope, Constants) {
  $scope.c = Constants.all();
  $scope.audio = $scope.m.in[Constants.get("keyAudio")];
  ...
});

And voila! You now have a central place to access all constants, with zero global variables!

New Site!

2012 September 12
Comments Off on New Site!
by Alec

Recently I’ve had a bit of a glut of work (who knew marketing was important? Sheesh) so I took the time to redo my website. I designed the first iteration, and I’m not much of a designer, so every time I looked at it it seemed crappier and crappier. Working with top-class designers for the past two years has also really helped me appreciate good graphic design.

So, the new layout was built from a suggestion by a friend and designer, Richard Quay. While fairly simple, it’s a dramatic improvement from the old design. Thanks Richard! I integrated a few interactive controls and simplified the copy to better represent my strengths. I also migrated to Rails 3 and put all the portfolio pieces into a sqlite database to simplify updates. There’s no back-end to speak of, I can just add entries to .yaml files and load them into the database manually. Everybody knows Django is the go-to platform for CMSs anyway!

Check it out here, and let me know what you think.

Edit (9/13/12): I tweaked the CSS so that the site should scale and look good on devices, especially the iPhone. Good ol’ CSS Media Queries and Responsive Design ftw! Check it out on your phone.