OdeToCode IC Logo

AngularJS - Controllers, Dependencies, and Minification

Wednesday, March 13, 2013 by K. Scott Allen

In working through a series of posts with AngularJS I've been using a controller function like so:

var VideoController = function($scope, Video) {
    // ... stuff 
};

The problem is that the function is in global scope. Some people will wonder what sort of sick mind creates a framework that forces developers to pollute the global scope.

Fortunately, you can register a controller constructor with angular.module instead. This is the same module API we used to create a resource service in a previous post.

angular.module("videoApp", ["videoService"])
       .controller("VideoController", function ($scope, Video) {

    // stuff ...
    
});

Not the prettiest syntax, unfortunately, but the controller constructor is out of the global scope and the world is a safer place. However, there is (and was) a problem lurking under the surface. Stay in suspense while we first look at dependencies in AngularJS.

Dependencies

Angular provides services that allow you to register dependencies and inject dependencies as function parameters. If you've worked with IoC containers in a language like C#, you'll be comfortable with the dependency injection in Angular. There is one significant difference, however. In languages like C# a container can examine a constructor method with reflection and find the types of the constructor parameters. Knowing that a constructor needs an ILogger or an IRepository will usually give a container enough information to figure out how to satisfy the dependency.

One of the many challenges for an IoC container in JavaScript is that method parameters and variables don't have an associated type. The question to think about is what to do with a function like the following, where $http represents a dependency to inject.

var ctor = function($http) {
    // stuff ...         
};

With no type information to go on, Angular relies on the next best thing, which is the name of the parameter. $http is the right name to use when you want Angular's built-in service that wraps an XmlHttpRequest object. The $http service offers communication methods  like get, post, put, delete, and jsonp. At test time you can pass in your own test double for the real $http service to avoid network calls in unit tests. Every service registered with Angular will have a specific name, like $http, $timeout (wraps setTimeout), and $location (wraps the address bar). You can register custom services, too.

Using well known names seems simple enough until you start thinking about how Angular actually discovers the parameter names. There is nothing built into JavaScript that will let you grab the names of a function's parameters. The answer is in the Angular source where you'll find a function named annotate, which relies on 4 regular expressions:

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

The annotate function will take a function object and invoke it's toString method to get the code for the function. It then strips out all the comments, parses out the function arguments as a string, and splits the string on the comma character. The result is an array with parameter names inside. Sounds crazy, but it works!

Now to the caveat.

Minification

One of the techniques a JavaScript minifier will use to reduce the amount of code sent to the client is to change the names of local variables and parameters to be as small as possible. A parameter named "$http" might come out of a minifier with the name "n" to save at least 4 bytes.

Once you know how dependency injection with Angular relies on parameter names, you'll realize a minifier can destroy Angular's ability to inject dependencies. Indeed, once you've minified a file you might see exceptions like the following:

Error: Unknown provider: nProvider <- n

The solution is to provide 'annotations' – an array that contains the names of the dependency a function needs, and the function itself.

angular.module("videoApp", ["videoService"])
       .controller("VideoController", ["$scope", "Video", function($scope, Video) {
    // ... stuff
}]);

A minifier won't change the $scope and Video string literals in the array, but it can change the function parameter names (and that's ok, because Angular will now look at the names in the array to resolve dependencies in the same order). In the previous samples (with a constructor function in the global scope), we could have also solved the problem by adding an $inject property to the function. Angular will look for an $inject property and use the names here instead of parsing the names out of the function definition.

VideoController.$inject = ['$scope', 'Video'];

Summary

Heavy stuff in the post, so just remember two things:

- You don't need to declare functions in the global scope to work with AngularJS. Many of the tutorials use this approach just to simplify the code.

- Think about the impact of minification from the start. Since most script files end up minified at some point, start describing your dependencies using the array approach (or $inject approach) from day 1. More details here.

Glimpse 1.0.1!

Tuesday, March 12, 2013 by K. Scott Allen

Glimpse is the open source diagnostics platform of the web. You can use Glimpse to see what is happening inside a web application on both the server and client side. It's like Firebug but with configuration, routing, and profiling information from the server included. Nik and Anthony run the project, and they recently pushed out Glimpse v1.0.1.

To lean more, you can listen to a Herding Code episode, or watch a Channel 9 video.

Glimpse and ASP.NET MVC 4

The current version of Glimpse works well with ASP.NET MVC 4.

The first step is to install the Glimpse.MVC3 package using NuGet (yes, MVC3 will work with MVC4).

The second step is to use your web browser to visit /Glimpse.axd in your application and click the "Turn Glimpse On" button.

Turning On Glimpse

The last step is to visit any page in your application and click on the little Glimpse icon that will appear in the bottom right of the page. Clicking the icon opens up the Glimpse dashboard with server configuration, routing, profiling information, and other diagnostics showing in a tabbed interface.

Glimpse In Action

 

The direction and extensibility of the Glimpse project make Glimpse particularly exciting. There are very few restrictions on what Glimpse can't diagnose, so if there is a feature you'd like to see feel to jump in on the Glimpse GitHub project. I hope to cover some of the server-side extensibility points in a future post.

Enjoy!

DropDownListFor with ASP.NET MVC

Monday, March 11, 2013 by K. Scott Allen

Building <select> tags generates a few questions and is something I've written about before (see Dropdown Lists and ASP.NET MVC). Here's a quick example to examine the scenario from different perspectives with the DropDownListFor HTML helper.

Let's say you have a class to represent different flavors of ice cream.

public class IceCreamFlavor
{
    public int Id { get; set; }
    public string Name { get; set; }
}

With DropDownListFor, you typically want a view model that will contain at least 2 properties:

- one property to hold a collection of SelectListItems to build the drop down

- one property to hold the value selected by a user

public class ViewModel
{
    private readonly List<IceCreamFlavor> _flavors;
    
    [Display(Name = "Favorite Flavor")]
    public int SelectedFlavorId { get; set; }
    
    public IEnumerable<SelectListItem> FlavorItems
    {
        get { return new SelectList(_flavors, "Id", "Name");}
    }
}

Assuming the _flavors field is populated with real ice cream flavors from a database or elsewhere, then the following Razor code:

@Html.LabelFor(m=>m.SelectedFlavorId)
@Html.DropDownListFor(m => m.SelectedFlavorId, Model.FlavorItems)
@Html.ValidationMessageFor(m=>m.SelectedFlavorId)
<input type="submit" value="Submit" />                           

.. will give you this in a browser:

DropDownListFor

Do I Have To Use String Literals?

The constructor for a SelectList wants you to identify the data field and text field using strings ("Id" and "Name"). If you want strong typing, use LINQ to project ice cream flavors into SelectListItems.

public IEnumerable<SelectListItem> FlavorItems
{            
    get
    {
        var allFlavors = _flavors.Select(f => new SelectListItem
                                               {
                                                   Value = f.Id.ToString(),
                                                   Text = f.Name           
                                              });
        return allFlavors;
        
    }
}

What About Adding A "Select A Flavor" Entry?

Perhaps you want to prepend a "Select Something Here" item into the option list for the drop down.

public IEnumerable<SelectListItem> FlavorItems
{            
    get
    {
        var allFlavors = _flavors.Select(f => new SelectListItem
                                               {
                                                   Value = f.Id.ToString(),
                                                   Text = f.Name                
                                              });
        return DefaultFlavorItem.Concat(allFlavors);                
    }
}

public IEnumerable<SelectListItem> DefaultFlavorItem
{
    get { return Enumerable.Repeat(new SelectListItem
                                       {
                                           Value = "-1", Text = "Select a flavor"
                                       }, count: 1); }
}

Select a Flavor

How Did It Remember That?

Something you might notice if validation fails is that the drop down list seems to remember the last value selected without you doing any extra work.

In other words, if the user selects "Chocolate" and clicks Submit to post the form to the server, your controller might detect a validation error and re-render the form with validation messages. The drop down list will also re-build the <select> from scratch, but somehow "Choclate" is selected instead of "Select A Flavor". You'll see this behavior because DropDownListFor will peek in ModelState to see the last value for SelectedFlavorId, and build the drop down list with the last selected flavor marked as selected. It's the same magic Web Form programmers see with ViewState.

Hope that helps!

Favorite HTML 5 Links

Wednesday, March 6, 2013 by K. Scott Allen

Here are a few links I've collected over the last couple years to help work with the new web standards.

Can I use... – this site is invaluable for seeing which browsers support specific features. Type a feature name (like Web Sockets) or a description (like rounded) into the search box and the site updates instantly. The tabular output shows usable features for a given browser version in green, and if vendor prefixes are required.  Usage stats display what percentage of users should be able to use a given feature based on the current global browser usage.

HTML 5 differences from HTML 4 – as specification documents go, this page on the W3C is easy to follow and quickly gets to the heart of the matter. The document presents all of the new elements, attributes, and APIs for HTML 5 (as well as pointing out the obsolete and deprecated pieces).

The HTML 5 Test – hit this site with a web browser to see the features it supports. Chrome 25 scores 463/500. IE 10 scores 320/500.

HTML 5 Boilerplate and Initializr – these sites are template generators to give you a starting point for an HTML 5 application. Includes best practices for elements in the <head> of a document, as well as icons for Apple devices and a basic CSS reset. Optionally include libraries like Bootstrap, jQuery, and Modernizr.

HTML 5 Rocks – a great site for articles, tutorials, and the latest HTML 5 news.

CSS-Tricks.com – code snippets, a gallery of design ideas, loads of demos, and my favorite, the shapes of CSS (because drawing a space invader using only CSS is profound).

The HTML 5 Quiz – A fun diversion for HTML fans. You can also use this game to start random conversations with people in a pub.

Mapping an Angular Resource Service to a Web API

Thursday, February 28, 2013 by K. Scott Allen

Previously I was calling an ASP.NET Web API controller from an AngularJS controller using the $http service.

$http.get(serviceUrl).success(function (data) {
    $scope.videos = data;
});

With a little bit of work I can use a higher level of abstraction in Angular – a custom service based on $resource. $resource is a factory that can create an object with methods that map to the WebAPI action methods I need to invoke. If I scaffold a VideoController for the WebAPI, the default actions will let me:

- GET all videos

- GET a video by ID.

- POST a new video

- PUT an updated video

- DELETE a video

To configure a service that will map JavaScript methods to these controller actions on the server, I'll need to use the .module API.

angular.module("videoService", ["ngResource"]).
       factory("Video", function ($resource) {
           return $resource(
               "/api/videos/:Id",
               {Id: "@Id" },
               { "update": {method:"PUT"} }    
          );
      });

These lines of code are registering a videoService that depends on ngResource (another module in Angular). When a controller requires a video service, the factory method will take care of configuring the service using $resource. There are a few interesting lines of code here.

- "/api/videos/:Id" is the URL to interact with the resource on the server (you could send this value to the client instead of hard coding the value). The :id part tells Angular how part of the URL path is parameterized with data.

- {Id: "@Id"} tells Angular to grab the ID parameter for the URL from an object's Id property.

- { "update": {method:"PUT"}} tells Angular to add a custom method to the resource. The custom method has the name "update" and it will use HTTP PUT when sending a message to the server. The default methods for a resource include query, save, and delete, which map to GET, POST, and DELETE verbs, so we needed one more for PUT. 

Another call to .module will tell Angular that my app requires the videoService as a dependency.

angular.module("videoApp", ["videoService"]);

And now the controller can "ask" for Angular to inject a video service by adding another parameter named "Video" to the constructor (Video was the first parameter to .factory, above):

var VideoController = function ($scope, Video) {
    /// ...
}

What used to require three lines of code to grab all videos from the server now requires a single line of code*.

$scope.videos = Video.query();

The resource service returns an empty array in this scenario, but there is a promise working behind the scenes to populate the array with data (which data binding will then automatically push into the view). The delete, save, and update methods are also available on these objects once they return from the server.

$scope.createVideo = function (newVideo) {
    newVideo.$save();
    $scope.videos.push(newVideo);           
};

$scope.updateVideo = function(video) {                
    video.$update();
};

$scope.deleteVideo = function (video) {
    video.$delete();
    $scope.videos = _.without($scope.videos, video);
};

The code is naïve and assumes a call to the server will always work. There are a couple strategies for error handling, including passing an error handler to the resource methods, or registering an $http interceptor to process HTTP messages at a global level (that's foreshadowing). For now, the heart of the code is in an updated gist.

* Still not entirely comfortable with the naming conventions I want to use for this scenario.

jQuery 1.9.x and ASP.NET MVC 4 Applications

Wednesday, February 27, 2013 by K. Scott Allen

If you update or restore your NuGet packages in an ASP.NET MVC 4 application, you've probably discovered the breaking changes in jQuery 1.9.x.

From the upgrade guide:

jQuery 1.9 removes or modifies several APIs that behaved inconsistently or inefficiently in the past. The majority of these changes have been foreshadowed by their deprecation in previous versions of jQuery, particularly 1.7 and 1.8.

Depending on what other packages you've upgraded or installed (like jQuery UI, jQuery Mobile), you'll see various runtime errors in a JavaScript debugger. Here is the error thrown by the jquery.unobtrusive-ajax.js file authored by Microsoft to form a bridge between HTML helpers and JavaScript code.

Object has no method live

Update: If the only script problems you are seeing are in the jQuery.unobtrusive scripts, make sure to use the NuGet update tab to update all your unobtrusive script packages from Microsoft. Unfortunately, many packages with a dependency on jQuery say they require jQuery 1.4.4 to jQuery 2.0, so  with the breaking changes in 1.9 you’ll need to manually update packages. 

There are many other possible solutions if you can't change your own code right away, but here are two easy ones.

1) install-package jQuery.Migrate.

jQuery Migrate is a script you'll want to include in pages immediately after jQuery. The migration script will add back some of the features removed from jQuery 1.9 (like the live function). The development version also provides trace messages that will highlight the deprecated APIs in use so you can update your own code to the newer APIs.

jQuery Migrate trace messages

2) install-package jQuery –Version 1.8.3

You can always revert to a previous version of jQuery if you don't want to use the additional migration script.

Hope that helps!

Why Use AngularJS?

Tuesday, February 26, 2013 by K. Scott Allen

AngularJSSince I started a series of posts on AngularJS, I've had a few people ask me why I like the framework.

One year ago I wrote a post with the title "Plain Old JavaScript". This post originated after working with various frameworks and deciding that two-way data binding was not a feature I was willing to achieve by sacrificing JavaScript objects on the Altar Of Observability.

Specifically, the func-ifying of object properties to raise change notifications corrupts my models with infrastructure. The functions make the code harder to change, harder to work on as a team, and harder to debug. These are my personal feelings based on my own observations, and I'm not saying the approach is wrong or doesn't work.

You can like caramel, but I prefer chocolate.

We can both eat ice cream with a dessert topping.

Changes

One of the reasons I kept coming back to Angular was because it offers two-way data binding without funcing up the model. At first I couldn't believe the change detection could possibly work, but it does work well by using some smarts. There are watchers, listeners, and a snapshot strategy that will look for changes after your code executes and manipulates the model. The change detection feature includes my favorite comment of the entire Angular code base (in the $digest method):

// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!

So, it does work, and there are tests to prove it.

Pieces

Angular is broken down into components like controllers, views, models, and services. These names are familiar to anyone who has worked on projects using separated presentation patterns. There are also directives, filters, modules, and other abstractions that seem to achieve the right balance of complexity versus separation of concerns.

Patterns

Model View Controller and Dependency Injection are the two patterns that jump out at the start, but there are other comfortable, familiar patterns in Angular, including it's approach to view composition and dependency management. 

Tests

Angular help you write test friendly code. Even the tutorial demonstrates unit tests and end-to-end tests using Jasmine's BDD syntax.

What's Wrong with Angular?

Not every framework is perfect, and you'll find scenarios where Angular is not a good fit. There are certainly scenarios when change detection will be milliseconds slower than observing changes. It's not obvious how to track animations or respond to focus events. Form validation is subpar. Angular is not minimalistic and comes with strong opinions. Writing lower level components, like custom directives, requires some focused effort. You also need some understanding of prototypal inheritance to avoid getting into trouble in more advanced scenarios.

Parting Words

I like Angular because I have fun writing code on top of the framework, and the framework doesn't get in the way. Models are just models, and views have sexy {{data.binding}} expressions. I do have an affinity for { and }.

I also believe AngularJS is the framework in the best position to take advantage of future improvements in the JavaScript language and the DOM APIs.

That's why I like AngularJS, but frameworks are a personal choice.