Skip to main content

9 posts tagged with "angularjs"

View All Tags

NgValidationFor Baby Steps

I thought as I start the NgValidationFor project I'd journal my progress. I'm writing this with someone particular in mind: me. Specifically, me in 2 years who will no doubt wonder why I made some of the choices I did. Everyone else, move along now - nothing to see. Unless the inner workings of someone else's mind are interesting to you... In which case: welcome!

Getting up and running#

I've got a project on GitHub and I'm starting to think about implementations. One thing that bit me on jVUN was being tied to a specific version of ASP.Net MVC. For each major release of ASP.Net MVC I needed separate builds / NuGet packages and the like. A pain. Particularly when it came to bug fixes for prior versions - the breaking changes with each version of MVC meant far more work was required when it came to shipping fixes for MVC 4 / MVC 3.

So with that in mind I'm going to try and limit my dependencies. I'm not saying I will never depend upon ASP.Net MVC - I may if I think it becomes useful to give the users a nicer API or if there's another compelling reason. But to start with I'm just going to focus on the translation of data annotations to Angular validation directive attributes.

To that end I'm going to begin with just a class library and an associated test project. I'm going to try and minimise the dependencies that NgValidationFor has. At least initially I may even see if I can sensibly avoid depending on System.Web (mindful of the upcoming ASP.Net 5 changes). Let's see.

A little time passes.......

So what have we got?#

My first efforts have resulted in the implementation of the <a href="https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.requiredattribute(v=vs.110).aspx">RequiredAttribute</a>. This is the code right now. It's made up of:

  1. NgValidationFor.Core - the core part of the project which converts data annotations into AngularJS 1.x validation directive attributes.
  2. NgValidationFor.Core.UnitTests - the unit tests for the core
  3. NgValidationFor.Documentation - this is an ASP.Net MVC project which will become a documentation site for NgValidationFor. It also doubles as a way for me to try out NgValidationFor.
  4. NgValidationFor.Documentation.UnitTests - unit tests for the documentation (there's none yet as I'm still spiking - but when I'm a little clearer, they will be)

How can it be used? Well fairly easily. Take this simple model:

using System.ComponentModel.DataAnnotations;
namespace NgValidationFor.Documentation.Models
{
public class RequiredDemoModel
{
[Required]
public string RequiredField { get; set; }
}
}

When used in an MVC View for which RequiredDemoModel is the Model, NgValiditionFor can be used thusly:

@using NgValidationFor.Core
@using NgValidationFor.Documentation.Models
@model RequiredDemoModel
<input type="text"
name="userName"
ng-model="user.name"
@Html.Raw(Model.GetAttributes(x => Model.RequiredField))
>

Which results in this HTML:

<input type="text"
name="userName"
ng-model="user.name"
required="required"
>

Tada!!!! It works.

So what now?#

Yes it works, but I'm not going to pretend it's pretty. I don't like having to wrap the usage of NgValidationFor with Html.Raw(...). I'm having to do that because GetAttributes returns a string. This string is then HTML encoded by MVC. To avoid my quotation marks turning into &amp;quot; I need to actually be exposing an <a href="https://msdn.microsoft.com/en-us/library/system.web.ihtmlstring(v=vs.110).aspx">IHtmlString</a>. So I'm going to need to depend upon System.Web. That's not so bad - at least I'm not tied to a specific MVC version.

I'm not too keen on the implementation I've come up with for NgValidationFor either. It's a single static method at the minute which does everything. It breaks the Single Responsibility Priniciple and the Open/Closed Principle. I need to take a look at that - I want people to be able to extend this and I need to think about a good and simple way to achieve that.

Finally, usage. Model.GetAttributes(x =&gt; Model.RequiredField) feels wrong to me. I think I'm happy with having this used as an extension method but it needs to be clearer what's happening. Perhaps Model.NgValidationFor(x =&gt; Model.RequiredField) would be better. I need to try a few things out and come up with a nicer way to use NgValidationFor.

A tale of Angular, html5mode, ASP.Net MVC and ASP.Net Web API

So. You want to kick hash based routing to the kerb. You want real URLs. You've read the HTML5 mode section of the Angular $location docs and you're good to go. It's just a matter of dropping $locationProvider.html5Mode(true) into your app initialisation right?

Wrong.

You want your URLs to be shareable. If, when you copy the URL out of your browser and send it someone else, they do not get taken to the same position in the application as you do then I've got news for you: THAT'S NOT REALLY A URL. And just using $locationProvider.html5Mode(true) has done nothing useful for you. You want to ensure that, if the URL entered in the browser does not relate to a specific server-side end-point, the self-same HTML root page is always served up. Then Angular can load the correct resources for the URL you have entered and get you to the required state.

There are tips to be found in Angular UI's How to: Configure your server to work with html5Mode doc. However they required a little extra fiddling to get my ASP.Net back end working quite as I wanted. To save you pain, here are my cultural learnings.

ASP.Net MVC#

I had an ASP.Net MVC app which I wanted to use html5mode with. To do this is simply a matter of tweaking your RouteConfig.cs like so:

public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Here go the routes that you still want to be able to hit
routes.MapRoute(
name: "IAmARouteThatYouStillWantToHit",
url: "ThatsWhyIAmRegisteredFirst",
defaults: new { controller = "Hittable", action = "Index" }
);
// Everything else will hit Home/Index which serves up the root angular app page
routes.MapRoute(
name: "Default",
url: "{*anything}", // THIS IS THE MAGIC!!!!
defaults: new { controller = "Home", action = "Index" }
);
}

With this in place my existing routes work just as I would hope. Any route that doesn't fit that registered can be assumed to be html5mode related and will serve up the root angular app page as I'd hope.

ASP.Net Web API#

Later I realised that the app in question was mostly static content. Certainly the root angular app page was and so it seemed wasteful to require an ASP.Net MVC controller to serve up that static content. So I stripped out MVC from the app entirely, choosing to serve raw HTML instead. For the dynamic parts I switched to using Web API. This was "hittable" as long as I had my WebApiConfig.cs and my system.webServer section in my web.config lined up correctly, viz:

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// other stuff
}
}
<configuration>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="build/index.html" /> <!-- This is the root document for the Angular app -->
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<!-- Allows "api/" prefixed URLs to still hit Web API controllers
as defined in WebApiConfig -->
<add input="{REQUEST_URI}" pattern="api/" ignoreCase="true" negate="true" />
<!-- Static files and directories can be served so partials etc can be loaded -->
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

With this in place I can happily hit "api" prefixed URLs and still land on my Web API controllers whilst other URLs will serve up the root angular app page. Lovely.

Tonight I'll Start an Open Source Project...

Further posts on this topic#

I'm excited. Are you? I'm babysitting for a friend, I've my laptop, time to kill and (crucially) an idea...

The Idea#

You're likely aware of the various form element directives that AngularJS offers. For instance the input directive:

HTML input element control. When used together with ngModel, it provides data-binding, input state control, and validation.

You'll notice that I emphasised the word "validation" there. That's important - that's my idea. I'm using AngularJS to build SPA's and for the server side I'm using ASP.Net MVC / Web API. Crucially, my templates are actually ASP.Net MVC Partial Views. That's key.

When I send data back from my SPA back to the server it gets unmarshalled / deserialized into a C# class (view model) of some kind. When data goes the other way it's flowing back from a JSON'd view model and being used by my Angular code.

Now historically if I was building a fairly vanilla MVC app then I'd be making use of all the TextboxFor extension methods etc to generate my input elements. For example, with a view model like this:

using System.ComponentModel.DataAnnotations;
namespace App.ViewModels
{
public class RequiredModel
{
[Required]
public string RequiredField{ get; set; }
}
}

I'd have a view like this:

@model App.ViewModels.RequiredModel
@using (Html.BeginForm())
{
<div class="row">
@Html.LabelFor(x => x.TextBox, "Something must be entered:")
@Html.TextBoxFor(x => x.TextBox, true)
</div>
}

And that would generate HTML like this:

<form action="/Demo/Required" method="post">
<div class="row">
<label for="TextBox">Something must be entered:</label>
<input data-msg-required="The TextBox field is required."
data-rule-required="true"
id="TextBox" name="TextBox" type="text" value="" />
</div>
</form>

If you look at the HTML you'll see that the Required data annotations have been propogated into the HTML in the HTML in the form of data-rule-* and data-msg-* attributes. The code above is built using my jQuery.Validation.Unobtrusive.Native project which in turn was inspired by / based upon the Unobtrusive Client Validation in ASP.NET MVC. That's right - I've done this before - or at least something quite like it.

There's clearly a strong crossover between AngularJS's input directive parameters and unobtrusive client validation. I'm planning to take the principles (and maybe some of the code) that I used on that project and see if I can't make something useful with it here. Server side validation is jolly important but I can probably save a few compute cycles on the server by making use of client side validation as well. If I'm right then I should able to come up with a mechanism that saves me from manually duplicating my server validation on the client.

The Aim#

I want to be able to use HTML Helpers to propogate validation metadata from the server view models into angular form validation directive attributes. Quite a mouthful I know. What does that actually mean? Well I've got 2 ideas. Possibly I want to be able to code something like this:

@model App.ViewModels.RequiredModel
@using (Html.BeginForm())
{
<div class="row">
@Html.LabelFor(x => x.TextBox, "Something must be entered:")
@Html.NgTextBoxFor(x => x.TextBox)
</div>
}

And have HTML like this generated:

<form action="/Demo/Required" method="post">
<div class="row">
<label for="TextBox">Something must be entered:</label>
<input
ng-required="true"
id="TextBox" name="TextBox" type="text" value="" />
</div>
</form>

The reservation I have about this approach is that it rather takes you away from the HTML. Yes it works (and to your seasoned MVC-er it will feel quite natural in some ways) but it feels rather heavy handed. But I'd like what I'm building to be easy for users to plug into existing code without a ton of rework. So, the other idea I'm toying with is having HTML helpers that just return a string of attributes. So if I had an angular form that looked like this:

<div ng-controller="ExampleController">
<form>
<div class="row">
<label>Something must be entered:
<input name="RequiredField" type="text" value="" />
</label>
</div>
</form>

I could tweak it to push in the validation directive attributes like this:

@model App.ViewModels.RequiredModel
<div ng-controller="ExampleController">
<form>
<div class="row">
<label>Something must be entered:
<input name="RequiredField" type="text" value="" @Html.NgValidationFor(x => x.RequiredField) />
</label>
</div>
</form>

And end up with HTML like this:

<div ng-controller="ExampleController">
<form>
<div class="row">
<label>Something must be entered:
<input name="RequiredField" type="text" value="" ng-required="true" />
</label>
</div>
</form>

This is a simplified example of course - it's likely that any number of validation directive attributes might be returned from NgValidationFor. And crucially if these attributes were changed on the server view model then the validation changes would automatically end up in the client HTML with this approach.

The Approach#

At least to start off with I'm going to aim at creating the second of my approaches. I may come back and implement the first at some point but I think the second is a better place to start.

I'm kind of surprised no-one else has built this already actually - but I'm not aware of anything. I've had a little duckduckgo around and found no takers. The closest I've come is the excellent BreezeJS. BreezeJS does way more than I want it to - I'm planning to restrict the scope of this project to simply turning data annotations on my ASP.Net MVC server models into ng-* directive attributes in HTML. That's it.

So, general housekeeping.... I'm going to host this project on GitHub, I'm going to have Continuous Integration with AppVeyor and I'm planning to publish this via NuGet (when and if I've created something useful).

I just need a name and I'll begin. What shall I call it? Some options:

  • Angular ASP.Net MVC Extensions
  • angular-aspnet-mvc-extensions
  • Angular MVC Element Extensions
  • Angular Validation Html Helpers
  • NgValidationFor (the name of the HTML helper I made up)

Hmmmm.... None of them is particularly lighting my fire. The first four are all a bit RonSeal - which is fine.... Ug. The last one... It's a bit more pithy. Okay - I'll go with "NgValidationFor" at least for now. If something better occurs I can always change my mind.

And we're off!

Caching and Cache-Busting in AngularJS with HTTP interceptors

Loading On-Demand and Caching#

I've written before about my own needs for caching and cache-busting when using RequireJS. Long story short, when I'm loading static resources (scripts / views etc) on demand from the server I want to do a little URL fiddling along the way. I want to do that to cater for these 2 scenarios:

  1. In Development - I want my URLs for static resources to have a unique querystring with each request to ensure that resources are loaded afresh each time. (eg so a GET request URL might look like this: "/app/layout/sidebar.html?v=IAmRandomYesRandomRandomIsWhatIAm58965782")
  2. In Production - I want my URLs for static resources to have a querystring with that is driven by the application version number. This means that static resources can potentially be cached with a given querystring - subsequent requests should result in a 304 status code (indicating “Not Modified”) and local cache should be used. But when a new version of the app is rolled out and the app version is incremented then the querystring will change and resources will be loaded anew. (eg a GET request URL might look like this: "/app/layout/sidebar.html?v=1.0.5389.16180")

Loading Views in AngularJS Using this Approach#

I have exactly the same use cases when I'm using AngularJS for views. Out of the box with AngularJS 1.x views are loaded lazily (unlike controllers, services etc). For that reason I want to use the same approach I've outlined above to load my views. Also, I want to prepend my URLs with the root of my application - this allows me to cater for my app being deployed in a virtual folder.

It turns out that's pretty easy thanks to HTTP interceptors. They allow you to step into the pipeline and access and modify requests and responses made by your application. When AngularJS loads a view it's the HTTP service doing the heavy lifting. So to deal with my own use case, I just need to add in an HTTP interceptor that amends the get request. This is handled in the example that follows in the configureHttpProvider function: (The example that follows is TypeScript - though if you just chopped out the interface and the type declarations you'd find this is pretty much idiomatic JavaScript)

interface config {
appRoot: string; // eg "/"
inDebug: boolean; // eg true or false
urlCacheBusterSuffix: string; // if in debug this might look like this: "v=1412608547047",
// if not in debug this might look like this: "v=1.0.5389.16180"
}
function configureHttpProvider() {
// This is the name of our HTTP interceptor
var serviceId = "urlInterceptor";
// We're going to create a service factory which will be our HTTP interceptor
// It will be injected with a config object which is represented by the config interface above
app.factory(serviceId, ["$templateCache", "config",
function ($templateCache: ng.ITemplateCacheService, config: config) {
// We're returning an object literal with a single function; the "request" function
var service = {
request: request
};
return service;
// Request will be called with a request config object which includes the URL which we will amend
function request(requestConfig: ng.IRequestConfig) {
// For the loading of HTML templates we want the appRoot to be prefixed to the path
// and we want a suffix to either allow caching or prevent caching
// (depending on whether in debug mode or not)
if (requestConfig.method === "GET" && endsWith(requestConfig.url, ".html")) {
// If this has already been placed into a primed template cache then we should leave the URL as is
// so that the version in templateCache is served. If we tweak the URL then it will not be found
var cachedAlready = $templateCache.get(requestConfig.url);
if (!cachedAlready) {
// THIS IS THE MAGIC!!!!!!!!!!!!!!!
requestConfig.url = config.appRoot + requestConfig.url + config.urlCacheBusterSuffix;
// WE NOW HAVE A URL WHICH IS CACHE-FRIENDLY FOR OUR PURPOSES - REJOICE!!!!!!!!!!!
}
}
return requestConfig;
}
// <a href="http://stackoverflow.com/a/2548133/761388">a simple JavaScript string "endswith" implementation</a>
function endsWith(str: string, suffix: string) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
}]);
// This adds our service factory interceptor into the pipeline
app.config(["$httpProvider", function ($httpProvider: ng.IHttpProvider) {
$httpProvider.interceptors.push(serviceId);
}]);
}

This interceptor steps in and amends each ajax request when all the following conditions hold true:

  1. It's a GET request.
  2. It's requesting a file that ends ".html" - a template basically.
  3. The template cache does not already contain the template. I left this out at first and got bitten when I found that the contents of the template cache were being ignored for pre-primed templates. Ugly.

Interesting technique.... How do I apply it?#

Isn't it always much more helpful when you can see an example of code in the context of which it is actually used? Course it is! If you want that then take a look at app.ts on GitHub. And if you'd like the naked JavaScript well that's there too.

Unit Testing an Angular Controller with Jasmine

Anyone who reads my blog will know that I have been long in the habit of writing unit tests for my C# code. I'm cool like that. However, it took me a while to get up and running writing unit tests for my JavaScript code. I finally got there using a combination of Jasmine 2.0 and Chutzpah. (Jasmine being my test framework and Chutzpah being my test runner.)

I'm getting properly into the habit of testing my JavaScript. I won't pretend it's been particularly fun but I firmly believe it will end up being useful... That's what I tell myself during the long dark tea-times of the soul anyway.

I have a side project called Proverb. It doesn't do anything in particular - for the most part it's a simple application that displays the collected wise sayings of a team that I used to be part of. There's not much to it - a bit of CRUD, a dashboard. Not much more. Because of the project's simplicity it's ideal to use Proverb's underlying idea when trying out new technologies / frameworks. The best way to learn is to do. So if I want to learn "X", then building Proverb using "X" is a good way to go.

I digress already. I had a version of Proverb built using a combination of AngularJS and TypeScript. I had written the Angular side of Proverb without any tests. Now I was able to write JavaScript tests for my Angular code that's just what I set out to do. It should prove something of a of Code Kata too.

Whilst I'm at it I thought it might prove helpful if I wrote up how I approached writing unit tests for a single Angular controller. So here goes.

What I'm Testing#

I have an Angular controller called sagesDetail. It powers this screen:

sagesDetail is a very simple controller. It does these things:

  1. Load the "sage" (think of it as just a "user") and make it available on the controller so it can be bound to the view.
  2. Set the view title.
  3. Log view activation.
  4. Expose a gotoEdit method which, when called, redirects the user to the edit screen.

The controller is written in TypeScript and looks like this:

sagesDetail.ts#

module controllers {
"use strict";
var controllerId = "sageDetail";
interface sageDetailRouteParams extends ng.route.IRouteParamsService {
id: string;
}
class SageDetail {
log: loggerFunction;
sage: sage;
title: string;
static $inject = ["$location", "$routeParams", "common", "datacontext"];
constructor(
private $location: ng.ILocationService,
private $routeParams: sageDetailRouteParams,
private common: common,
private datacontext: datacontext
) {
this.sage = undefined;
this.title = "Sage Details";
this.log = common.logger.getLogFn(controllerId);
this.activate();
}
// Prototype methods
activate() {
var id = parseInt(this.$routeParams.id, 10);
var dataPromises: ng.IPromise<any>[] = [this.datacontext.sage.getById(id, true).then(data => this.sage = data)];
this.common.activateController(dataPromises, controllerId, this.title)
.then(() => {
this.log("Activated Sage Details View");
this.title = "Sage Details: " + this.sage.name;
});
}
gotoEdit() {
this.$location.path("/sages/edit/" + this.sage.id);
}
}
angular.module("app").controller(controllerId, SageDetail);
}

When compiled to JavaScript it looks like this:

sageDetail.js#

var controllers;
(function (controllers) {
"use strict";
var controllerId = "sageDetail";
var SageDetail = (function () {
function SageDetail($location, $routeParams, common, datacontext) {
this.$location = $location;
this.$routeParams = $routeParams;
this.common = common;
this.datacontext = datacontext;
this.sage = undefined;
this.title = "Sage Details";
this.log = common.logger.getLogFn(controllerId);
this.activate();
}
// Prototype methods
SageDetail.prototype.activate = function () {
var _this = this;
var id = parseInt(this.$routeParams.id, 10);
var dataPromises = [this.datacontext.sage.getById(id, true).then(function (data) {
return _this.sage = data;
})];
this.common.activateController(dataPromises, controllerId, this.title).then(function () {
_this.log("Activated Sage Details View");
_this.title = "Sage Details: " + _this.sage.name;
});
};
SageDetail.prototype.gotoEdit = function () {
this.$location.path("/sages/edit/" + this.sage.id);
};
SageDetail.$inject = ["$location", "$routeParams", "common", "datacontext"];
return SageDetail;
})();
angular.module("app").controller(controllerId, SageDetail);
})(controllers || (controllers = {}));
//# sourceMappingURL=sageDetail.js.map

Now for the Tests#

I haven't yet made the move of switching over my Jasmine tests from JavaScript to TypeScript. (It's on my list but there's only so many things you can do at once...) For that reason the tests you'll see here are straight JavaScript. Below you will see the tests for the sageDetail controller.

I have put very comments in the test code to make clear the intent to you, dear reader. Annotated the life out of them. Naturally I wouldn't expect a test to be so heavily annotated in a typical test suite - and you can be sure mine normally aren't!

Jasmine tests for sageDetail.js#

describe("Proverb.Web -> app-> controllers ->", function () {
// Before each test runs we're going to need ourselves an Angular App to test - go fetch!
beforeEach(function () {
module("app"); // module is an alias for <a href="https://docs.angularjs.org/api/ngMock/function/angular.mock.module">angular.mock.module</a>
});
// Tests for the sageDetail controller
describe("sageDetail ->", function () {
// Declare describe-scoped variables
var $rootScope,
getById_deferred, // deferred used for promises
$location, $routeParams_stub, common, datacontext, // controller dependencies
sageDetailController; // the controller
// Before each test runs set up the controller using inject - an alias for <a href="https://docs.angularjs.org/api/ngMock/function/angular.mock.inject">angular.mock.inject</a>
beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _datacontext_) {
// Note how each parameter is prefixed and suffixed with "_" - this an Angular nicety
// which allows you to have variables in your tests with the original reference name.
// So here we assign the injected parameters to the describe-scoped variables:
$rootScope = _$rootScope_;
$q = _$q_;
$location = _$location_;
common = _common_;
datacontext = _datacontext_;
// Our controller has a dependency on an "id" property passed on the $routeParams
// We're going to stub this out with a JavaScript object literal
$routeParams_stub = { id: "10" };
// Our controller depends on a promise returned from this function: datacontext.sage.getById
// Well strictly speaking it also uses a promise for activateController but since the activateController
// promise just wraps the getById promise it will be resolved when the getById promise is.
// Here we create a deferred representing the getById promise which we can resolve as we need to
getById_deferred = $q.defer();
// set up a spy on datacontext.sage.getById and set it to return the promise of getById_deferred
// this allows us to #1 detect that getById has been called
// and #2 resolve / reject our promise as our test requires using getById_deferred
spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
// set up a spy on common.activateController and set it to call through
// this allows us to detect that activateController has been called whilst
// maintaining existing controller functionality
spyOn(common, "activateController").and.callThrough();
// set up spys on common.logger.getLogFn and $location.path so we can detect they have been called
spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));
// create a sageDetail controller and inject the dependencies we have set up
sageDetailController = _$controller_("sageDetail", {
$location: $location,
$routeParams: $routeParams_stub,
common: common,
datacontext: datacontext
});
}));
// Tests for the controller state at the point of the sageDetail controller's creation
// ie before the getById / activateController promises have been resolved
// So this tests the constructor (function) and the activate function up to the point
// of the promise calls
describe("on creation ->", function () {
it("controller should have a title of 'Sage Details'", function () {
// tests this code has executed:
// this.title = "Sage Details";
expect(sageDetailController.title).toBe("Sage Details");
});
it("controller should have no sage", function () {
// tests this code has executed:
// this.sage = undefined;
expect(sageDetailController.sage).toBeUndefined();
});
it("datacontext.sage.getById should be called", function () {
// tests this code has executed:
// this.datacontext.sage.getById(id, true)
expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
});
});
// Tests for the controller state at the point of the resolution of the getById promise
// ie after the getById / activateController promises have been resolved
// So this tests the constructor (function) and the activate function after the point
// of the promise calls
describe("activateController ->", function () {
var sage_stub;
beforeEach(function () {
// Create a sage stub which will be used when resolving the getById promise
sage_stub = { name: "John" };
});
it("should set sages to be the resolved promise values", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
// tests this code has executed:
// this.sage = data
expect(sageDetailController.sage).toBe(sage_stub);
});
it("should log 'Activated Sage Details View' and set title with name", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
// tests this code has executed:
// this.log("Activated Sage Details View");
// this.title = "Sage Details: " + this.sage.name;
expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
});
});
// Tests for the gotoEdit function on the controller
// Note that this will only be called *after* a controller has been created
// and it depends upon a sage having first been loaded
describe("gotoEdit ->", function () {
var sage_stub;
beforeEach(function () {
// Create a sage stub which will be used when resolving the getById promise
sage_stub = { id: 20 };
});
it("should set $location.path to edit URL", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
sageDetailController.gotoEdit();
// tests this code has executed:
// this.$location.path("/sages/edit/" + this.sage.id);
expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
});
});
});
});

My Unrequited Love for Isolate Scope

I wrote a little while ago about creating a directive to present server errors on the screen in an Angular application. In my own (not so humble opinion), it was really quite nice. I was particularly proud of my usage of isolate scope. However, pride comes before a fall.

It turns out that using isolate scope in a directive is not always wise. Or rather – not always possible. And this is why:

Error: [$compile:multidir] Multiple directives [datepickerPopup, serverError] asking for new/isolated scope on: &lt;input name="sage.dateOfBirth" class="col-xs-12 col-sm-9" type="text" value="" ng-click="vm.dateOfBirthDatePickerOpen()" server-error="vm.errors" ng-model="vm.sage.dateOfBirth" is-open="vm.dateOfBirthDatePickerIsOpen" datepicker-popup="dd MMM yyyy"&gt; Ug. What happened here? Well, I had a date field that I was using my serverError directive on. Nothing too controversial there. The problem came when I tried to plug in UI Bootstrap’s datepicker as well. That’s right the directives are fighting. Sad face.

To be more precise, it turns out that only one directive on an element is allowed to create an isolated scope. So if I want to use UI Bootstrap’s datepicker (and I do) – well my serverError directive is toast.

A New Hope#

So ladies and gentlemen, let me present serverError 2.0 – this time without isolated scope:

serverError.ts#

(function () {
"use strict";
var app = angular.module("app");
// Plant a validation message to the right of the element when it is declared invalid by the server
app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel" // supply the ngModel controller as the 4th parameter in the link function
};
return directive;
function link(scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelController: ng.INgModelController) {
// Extract values from attributes (deliberately not using isolated scope)
var errorKey: string = attrs["name"]; // eg "sage.name"
var errorDictionaryExpression: string = attrs["serverError"]; // eg "vm.errors"
// Bootstrap alert template for error
var template = '<div class="alert alert-danger col-xs-9 col-xs-offset-2" role="alert"><i class="glyphicon glyphicon-warning-sign larger"></i> %error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(() => ngModelController.$error.server), showHideValidation);
function showHideValidation(serverError: boolean) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
var errorDictionary: { [field: string]: string } = scope.$eval(errorDictionaryExpression);
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", (event) => {
scope.$apply(() => { ngModelController.$setValidity("server", true); });
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch<T extends Function>(expression: T) {
return () => {
try {
return expression();
}
catch (e) {
return null;
}
};
}
})();

serverError.js#

(function () {
"use strict";
var app = angular.module("app");
// Plant a validation message to the right of the element when it is declared invalid by the server
app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel"
};
return directive;
function link(scope, element, attrs, ngModelController) {
// Extract values from attributes (deliberately not using isolated scope)
var errorKey = attrs["name"];
var errorDictionaryExpression = attrs["serverError"];
// Bootstrap alert template for error
var template = '<div class="alert alert-danger col-xs-9 col-xs-offset-2" role="alert"><i class="glyphicon glyphicon-warning-sign larger"></i> %error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(function () {
return ngModelController.$error.server;
}), showHideValidation);
function showHideValidation(serverError) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
var errorDictionary = scope.$eval(errorDictionaryExpression);
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", function (event) {
scope.$apply(function () {
ngModelController.$setValidity("server", true);
});
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch(expression) {
return function () {
try {
return expression();
} catch (e) {
return null;
}
};
}
})();

This version of the serverError directive is from a users perspective identical to the previous version. But it doesn’t use isolated scope – this means it can be used in concert with other directives which do.

It works by pulling the name and serverError values off the attrs parameter. name is just a string - the value of which never changes so it can be used as is. serverError is an expression that represents the error dictionary that is used to store the server error messages. This is accessed through use of scope.$eval as an when it needs to.

My Plea#

What I’ve outlined here works. I’ll admit that usage of $eval makes me feel a little bit dirty (I’ve got “eval is evil” running through my head). Whilst it works, I’m not sure what I’ve done is necessarily best practice. After all the Angular docs themselves say:

*Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app. *

But as we’ve seen this isn’t always an option. I’ve written this post to document my own particular struggle and ask the question “is there a better way?” If you know then please tell me!

AngularJS meet ASP.Net Server Validation

So. You're using AngularJS to build your front end with ASP.Net running on the server side. You're a trustworthy dev - you know that validation on the client will only get you so far. You need to validate on the server.

My particular scenario is where you have a form which you are saving. Angular serves you well when it comes to hooking in your own client side validation. But it doesn't really ship with anything that supports nicely presenting server side validation on the client. Invariably when you look around you find people duplicating their server side validation on the client and presenting all their server side validation in a &lt;div&gt; at the top of the screen.

This works but it's not as helpful to the user as it might be. It groups together all the validation from the server into one place. What I want is field level validation from the server that's presented on a field level basis on the screen. Like this:

I know. A thing of beauty is a joy forever. Let us travel together to this promised land...

What do we need client side?#

Well, let's start with a directive which I'll call serverError. This plants a validation message just after the element being validated which is displayed when that element is declared invalid by the server. (That is to say when the ngModel has a $error.server set.) When the element is changed then the $error.server is unset in order that validation can be hidden and the form can be revalidated against the server.

I'm using TypeScript with Angular so for my JavaScript examples I'll give you both the TypeScript which I originally wrote and the generated JavaScript as well.

TypeScript#

interface serverErrorScope extends ng.IScope {
name: string;
serverError: { [field: string]: string };
}
app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel", // supply the ngModel controller as the 4th parameter in the link function
scope: { // Pass in name and serverError to the scope
name: "@",
serverError: "="
}
};
return directive;
function link(scope: serverErrorScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelController: ng.INgModelController) {
// Bootstrap alert template for error
var template = '<div class="alert alert-danger" role="alert">' +
'<i class="glyphicon glyphicon-warning-sign"></i> ' +
'%error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(() => ngModelController.$error.server), showHideValidation);
function showHideValidation(serverError: boolean) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
// Aliasing serverError and name to make it more obvious what their purpose is
var errorDictionary = scope.serverError;
var errorKey = scope.name;
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", (event) => {
scope.$apply(() => { ngModelController.$setValidity("server", true); });
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch<t extends="" function="">(expression: T) {
return () => {
try {
return expression();
}
catch (e) {
return null;
}
};
}
</t>

JavaScript#

app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel",
scope: {
name: "@",
serverError: "="
}
};
return directive;
function link(scope, element, attrs, ngModelController) {
// Bootstrap alert template for error
var template = '<div class="alert alert-danger" role="alert">' +
'<i class="glyphicon glyphicon-warning-sign"></i> ' +
'%error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(function () {
return ngModelController.$error.server;
}), showHideValidation);
function showHideValidation(serverError) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
// Aliasing serverError and name to make it more obvious what their purpose is
var errorDictionary = scope.serverError;
var errorKey = scope.name;
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", function (event) {
scope.$apply(function () {
ngModelController.$setValidity("server", true);
});
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch(expression) {
return function () {
try {
return expression();
} catch (e) {
return null;
}
};
}

If you look closely at this directive you'll see it is restricted to be used as an attribute and it depends on 2 things:

  1. The value that the server-error attribute is set to should be an object which will contain key / values where the keys represent fields that are being validated.
  2. The element being validated must have a name property (which will be used to look up the validation message in the server-error error "dictionary".

Totally not clear, right? Let's have an example. Here is my "sageEdit" screen which you saw the screenshot of earlier:

<section class="mainbar" ng-controller="sageEdit as vm">
<section class="matter">
<div class="container-fluid">
<form name="form" novalidate role="form">
<div>
<button class="btn btn-info"
ng-click="vm.save()"
ng-disabled="!vm.canSave">
<i class="glyphicon glyphicon-save"></i>Save
</button>
<span ng-show="vm.hasChanges"
class="dissolve-animation ng-hide">
<i class="glyphicon glyphicon-asterisk orange"></i>
</span>
</div>
<div class="widget wblue">
<div data-cc-widget-header title="{{vm.title}}"></div>
<div class="widget-content form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2">Name</label>
<input class="col-xs-12 col-sm-9"
name="sage.name" ng-model="vm.sage.name"
server-error="vm.errors" />
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2">Username</label>
<input class="col-xs-12 col-sm-9"
name="sage.userName" ng-model="vm.sage.userName"
server-error="vm.errors" />
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2">Email</label>
<input class="col-xs-12 col-sm-9"
type="email"
name="sage.email" ng-model="vm.sage.email"
server-error="vm.errors" />
</div>
</div>
</div>
</form>
</div>
</section>
</section>

If you look closely at where server-error is used we have a name attribute set (eg "sage.email") and we're passing in something called <em>vm.</em>errors as the server-error attribute value. That's because we're using the "controller as" syntax and our controller is called vm.

On that controller we're going to have a dictionary style object called errors. If you wanted to you could put that object on the scope instead and omit the "vm." prefix. You could call it wrongThingsWhatISpottedWithYourModel or barry - whatever floats your boat really. You get my point; it's flexible.

Let's take a look at our sageEdit Angular controller:

TypeScript#

module controllers {
"use strict";
interface sageEditRouteParams extends ng.route.IRouteParamsService {
id: number;
}
interface sageEditScope extends ng.IScope {
form: ng.IFormController;
}
class SageEdit {
errors: { [field: string]: string };
log: loggerFunction;
logError: loggerFunction;
logSuccess: loggerFunction;
sage: sage;
title: string;
private _isSaving: boolean;
static $inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
constructor(
private $location: ng.ILocationService,
private $routeParams: sageEditRouteParams,
private $scope: sageEditScope,
private common: common,
private datacontext: datacontext
) {
this.errors = {};
this.log = common.logger.getLogFn(controllerId);
this.logError = common.logger.getLogFn(controllerId, "error");
this.logSuccess = common.logger.getLogFn(controllerId, "success");
this.sage = undefined;
this.title = "Sage Edit";
this._isSaving = false;
this.activate();
}
// Prototype methods
activate() {
var id = this.$routeParams.id;
var dataPromises: ng.IPromise<any>[] = [this.getSage(id)];
this.common.activateController(dataPromises, controllerId, this.title)
.then(() => {
this.log("Activated Sage Edit View");
this.title = "Sage Edit: " + this.sage.name;
});
}
getSage(id: number) {
return this.datacontext.sage.getById(id).then(sage => {
this.sage = sage;
});
}
save() {
this.errors = {}; //Wipe server errors
this._isSaving = true;
this.datacontext.sage.save(this.sage).then(response => {
if (response.success) {
this.sage = response.entity;
this.logSuccess("Saved " + this.sage.name + " [" + this.sage.id + "]");
this.$location.path("/sages/detail/" + this.sage.id);
}
else {
this.logError("Failed to save", response.errors);
angular.forEach(response.errors, (errors, field) => {
(<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
this.errors[field] = errors.join(",");
});
}
this._isSaving = false;
});
}
// Properties
get hasChanges(): boolean {
return this.$scope.form.$dirty;
}
get canSave(): boolean {
return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
}
}
var controllerId = "sageEdit";
angular.module("app").controller(controllerId, SageEdit);
}

JavaScript#

var controllers;
(function (controllers) {
"use strict";
var SageEdit = (function () {
function SageEdit($location, $routeParams, $scope, common, datacontext) {
this.$location = $location;
this.$routeParams = $routeParams;
this.$scope = $scope;
this.common = common;
this.datacontext = datacontext;
this.errors = {};
this.log = common.logger.getLogFn(controllerId);
this.logError = common.logger.getLogFn(controllerId, "error");
this.logSuccess = common.logger.getLogFn(controllerId, "success");
this.sage = undefined;
this.title = "Sage Edit";
this._isSaving = false;
this.activate();
}
// Prototype methods
SageEdit.prototype.activate = function () {
var _this = this;
var id = this.$routeParams.id;
var dataPromises = [this.getSage(id)];
this.common.activateController(dataPromises, controllerId, this.title).then(function () {
_this.log("Activated Sage Edit View");
_this.title = "Sage Edit: " + _this.sage.name;
});
};
SageEdit.prototype.getSage = function (id) {
var _this = this;
return this.datacontext.sage.getById(id).then(function (sage) {
_this.sage = sage;
});
};
SageEdit.prototype.save = function () {
var _this = this;
this.errors = {}; //Wipe server errors
this._isSaving = true;
this.datacontext.sage.save(this.sage).then(function (response) {
if (response.success) {
_this.sage = response.entity;
_this.logSuccess("Saved " + _this.sage.name + " [" + _this.sage.id + "]");
_this.$location.path("/sages/detail/" + _this.sage.id);
} else {
_this.logError("Failed to save", response.errors);
angular.forEach(response.errors, function (errors, field) {
_this.$scope.form[field].$setValidity("server", false);
_this.errors[field] = errors.join(",");
});
}
_this._isSaving = false;
});
};
Object.defineProperty(SageEdit.prototype, "hasChanges", {
// Properties
get: function () {
return this.$scope.form.$dirty;
},
enumerable: true,
configurable: true
});
Object.defineProperty(SageEdit.prototype, "canSave", {
get: function () {
return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
},
enumerable: true,
configurable: true
});
SageEdit.$inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
return SageEdit;
})();
var controllerId = "sageEdit";
angular.module("app").controller(controllerId, SageEdit);
})(controllers || (controllers = {}));

Okay - this is a shedload of code and most of it isn't relevant to you. I share it as I like to see things in context. Let's focus in on the important bits that you should take away. Firstly, our controller has a property called errors.

Secondly, when we attempt to save our server sends back a JSON payload which, given a validation failure, looks something like this:

{
"success":false,
"errors":{
"sage.name":["The Name field is required."],
"sage.userName":[
"The UserName field is required.",
"The field UserName must be a string with a minimum length of 3 and a maximum length of 30."
],
"sage.email":["The Email field is not a valid e-mail address."]
}
}

So let's pare back our save function to the bare necessities (those simple bare necessities, forget about your worries and your strife...):

TypeScript#

save() {
this.errors = {}; //Wipe server errors
this.datacontext.sage.save(this.sage).then(response => {
if (response.success) {
this.sage = response.entity;
}
else {
angular.forEach(response.errors, (errors, field) => {
(<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
this.errors[field] = errors.join(",");
});
}
});
}

JavaScript#

SageEdit.prototype.save = function () {
var _this = this;
this.errors = {}; //Wipe server errors
this.datacontext.sage.save(this.sage).then(function (response) {
if (response.success) {
_this.sage = response.entity;
} else {
angular.forEach(response.errors, function (errors, field) {
_this.$scope.form[field].$setValidity("server", false);
_this.errors[field] = errors.join(",");
});
}
});
};

At the point of save we wipe any server error messages that might be stored on the client. Then, if we receive back a payload with errors we store those errors and set the validity of the relevant form element to false. This will trigger the display of the message by our directive.

That's us done for the client side. You're no doubt now asking yourself this question:

How can I get ASP.Net to send me this information?#

So glad you asked. We've a simple model that looks like this which has a number of data annotations:

public class Sage
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[StringLength(30, MinimumLength = 3)]
public string UserName { get; set; }
[EmailAddress]
public string Email { get; set; }
}

When we save we post back to a Web API controller that looks like this:

public class SageController : ApiController
{
// ...
public IHttpActionResult Post(User sage)
{
if (!ModelState.IsValid) {
return Ok(new
{
Success = false,
Errors = ModelState.ToErrorDictionary()
});
}
sage = _userService.Save(sage);
return Ok(new
{
Success = true,
Entity = sage
});
}
// ...
}

As you can see, when ModelState is not valid we send back a dictionary of the ModelState error messages keyed by property name. We generate this with an extension method I wrote called ToErrorDictionary:

public static class ModelStateExtensions
{
public static Dictionary<string, IEnumerable<string>> ToErrorDictionary(
this System.Web.Http.ModelBinding.ModelStateDictionary modelState, bool camelCaseKeyName = true)
{
var errors = modelState
.Where(x => x.Value.Errors.Any())
.ToDictionary(
kvp => CamelCasePropNames(kvp.Key),
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage)
);
return errors;
}
private static string CamelCasePropNames(string propName)
{
var array = propName.Split('.');
var camelCaseList = new string[array.Length];
for (var i = 0; i < array.Length; i++)
{
var prop = array[i];
camelCaseList[i] = prop.Substring(0, 1).ToLower() + prop.Substring(1, prop.Length - 1);
}
return string.Join(".", camelCaseList);
}
}

That's it - your solution front to back. It would be quite easy to hook other types of validation in server-side (database level checks etc). I hope you find this useful.

HotTowel-Angular meet TypeScript

I've recently ported John Papa's popular Hot Towel Angular SPA Template to TypeScript. Why? Because it was there.

If you'd like to read more about HotTowel-Angular then have a read of John Papa's post. You can find my port on GitHub here.

What is this port you speak of?#

It is intentionally a "bare bones" port of the HotTowel-Angular JavaScript code across to TypeScript. It's essentially the same code as John's - just with added type annotations (and yes it is noImplicitAny compliant).

You could, if you wanted to, go much further. You could start using a whole host of TypeScripts functionality: modules / classes / arrow functions... the whole shebang. But my port is deliberately not that; I didn't want to scare your horses... I wanted you to see how easy it is to move from JS to TS. And I'm standing on the shoulders of that great giant John Papa for that purpose.

If you wanted an example of how you might go further in an Angular port to TypeScript then you could take a look at my previous post on the topic.

What's in the repo?#

The repo contains the contents of HotTowel-Angular's app folder, with each JavaScript file converted over to TypeScript. The compiled JavaScript files are also included so that you can compare just how similar the compiled JavaScript is to John's original code.

In fact there are only 2 differences in the end:

1. sidebar.js's getNavRoutes#

...had the filtering changed from this:

return r.config.settings && r.config.settings.nav;

to this: ```ts return (r.config.settings && r.config.settings.nav) ? true : false;

This was necessary as TypeScript insists that the array `filter` predicate returns a `boolean`. John's original method returns a number (`nav`'s value to be clear) which actually seems to work fine. My assumption is that JavaScript's filter method is happy with a truth-y / false-y test which John's implementation would satisfy.
### 2\. common.js's `$broadcast`
...had to be given a rest parameter to satisfy the TS compiler. John's original method exposed no parameters as it just forwards on whatever arguments are passed to it. This means that `$broadcast` has a bit of unused code in the head of the generated method:
```js
var args = [];
for (var _i = 0; _i < (arguments.length - 0); _i++) {
args[_i] = arguments[_i + 0];
}

If you want to use this#

Then simply follow the instructions for installing HotTowel-Angular and then drop this repo's app folder over the one just created when HotTowel-Angular was installed. If you're using Visual Studio then make sure that you include the new TS files into your project and give them the BuildAction of TypeScriptCompile.

You'll need the following NuGet packages for the relevant DefinitelyTyped Typings:

Install-Package angularjs.TypeScript.DefinitelyTyped
Install-Package angular-ui-bootstrap.TypeScript.DefinitelyTyped
Install-Package jquery.TypeScript.DefinitelyTyped
Install-Package spin.TypeScript.DefinitelyTyped
Install-Package toastr.TypeScript.DefinitelyTyped

And you're good to go. If you're not using Visual Studio then you may need to add in some &lt;reference path="angular.d.ts" /&gt; etc. statements to the TypeScript files as well.

If you're interested in the specific versions of the typings that I used then you can find them in the packages.config of the repo.

Thanks#

To John Papa for creating HotTowel-Angular. Much love.

And my mum too... Just because.

Migrating from AngularJS to AngularTS - a walkthrough

It started with nuns. Don't all good stories start that way? One of my (many) aunts is a Poor Clare nun. At some point in the distant past I was cajoled into putting together a simple website for her convent. This post is a walkthrough of how to migrate from AngularJS using JavaScript to AngularJS using TypeScript. It just so happens that the AngularJS app in question is the one that belongs to my mother's sister's convent.

TL;DR - grab what you need#

For reference the complete "before" and "after" projects can be found on GitHub here. This is available so people can see clearly what changes have been made in the migration.

The content of the site is available for reference only

. (Not that I can really imagine people creating their own "Poor Clares" site and hawking it to convents around the globe but I thought I'd make the point.) It looks like this:

Background#

I've been quietly maintaining this website / app for quite a while now. It's a very simple site; 95% of it is static content about the convent. The one piece of actual functionality is a page which allows the user of the website to send a prayer request to the nuns at the convent:

Behind the scenes this sends 2 emails:

  • The first back to the person who submitted the prayer request assuring them that they will be prayed for.
  • The second to the convent telling them the details of what the person would like prayer for.

Right now you are probably thinking this is an unusual post. Perhaps it is, but bear with me.

Over time the website has had many incarnations. It's been table-based layout, it's used Kendo UI, it's used Bootstrap. It's been static HTML, it's been ASP.Net WebForms, it's been ASP.Net MVC and it's currently built using AngularJS with MVC on the back-end to handle bundling / minification and dispatching of emails.

I decided to migrate this AngularJS app to use TypeScript. As I did that I thought I'd document the process for anyone else who might be considering doing something similar. As it happens this is a particularly good candidate for migration as there's a full unit test suite for the app (written with Jasmine). Once I've finished the migration these unit tests should pass, just as they do currently.

You are probably thinking to yourself "but TypeScript is just about adding compile-time annotations right? How could the unit tests not pass after migration?" Fair point, well made. Well that is generally true but I have something slightly different planned when we get to the controllers - you'll see what I mean...

It's also a good candidate for documenting a walkthrough as it's a particularly small and simple Angular app. It consists of just 3 controllers, 2 services and 1 app.

Before I kick off I thought I'd list a couple of guidelines / caveats on this post:

  • I don't intend to say much about the architecture of this application - I want to focus on the migration from JavaScript to TypeScript.
  • The choices that I make for the migration path do not necessarily reflect the "one true way". Rather, they are pragmatic choices that I am making - there may be alternatives approaches here and there that could be used instead.
  • I love Visual Studio - it's my IDE of choice and the one I am using as I perform the migration. Some of the points that I will make are Visual Studio specific - I will try and highlight that when appropriate.

Typings#

The first thing we're going to need to get going are the Angular typing files which can be found on Definitely Typed here. Since these typings are made available over NuGet I'm going to pull them in with a wave of my magic Install-Package angularjs.TypeScript.DefinitelyTyped.

As well as pulling in the typing files Visual Studio 2013 has also made some tweaks to my PoorClaresAngular.csproj file which it tells me about:

And these are the TypeScript specific additions that Visual Studio has made to PoorClaresAngular.csproj:

<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
<TypeScriptToolsVersion>1.0</TypeScriptToolsVersion>
<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

I'm going to add one extra of my own:

<TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>

This prevents you having variables of type any in your TypeScript codebase without you implicitly specifying the type. You can live without this but I've found it's useful to catch where you're missing out on the benefit of static typing. Further to that, this option can be particularly useful when performing a migration. It will become obvious why this is the case as we go on.

I decline the kind opportunity to further search NuGet as I'm already on my way typing-wise. So let's review what has happened. Below you can see the typing files that have been pulled in and that the project and packages files were amended:

Changing JS files to TS files#

This really should be as simple as changing all the JavaScript files underneath the js directory to have the suffix ts. So going from this:

To this:

And if you're not using Visual Studio it is. But if you are using Visual Studio there's a certain amount of fiddling required to include the generated .js and .js.map files associated with each .ts file. The easiest (hah!) thing to do is to crack open the project and wherever you find a &lt;TypeScriptCompile Include="js\somePath.ts" /&gt; to add in 2 Content statements, one for each generated file which states the dependency on the TypeScript file. For example:

<TypeScriptCompile Include="js\services\siteSectionService.ts" />
<Content Include="js\services\siteSectionService.js">
<DependentUpon>siteSectionService.ts</DependentUpon>
</Content>
<Content Include="js\services\siteSectionService.js.map">
<DependentUpon>siteSectionService.ts</DependentUpon>
</Content>

It's a bit of a pain to have to do this at the moment. Hopefully the Visual Studio tooling will catch up so this sort of tweaking becomes unnecessary.

Recap#

So, where are we? Well, we've got our project ready for TypeScript, we've pulled in the Angular typings from Definitely Typed and we've turned all our JavaScript files in the js directory into TypeScript files.

Now we can actually start working through our TypeScript files and ensuring we're all typed correctly. Please note that because I'm working in Visual Studio I get the benefit of implicit referencing; I don't have to explicitly state the typing files each TypeScript file relies on at the head of the file (eg /// &lt;reference path="angularjs/angular.d.ts" /&gt;). If you aren't working in Visual Studio then you'd need to add these yourself.

TypeScriptify app.ts#

Opening up app.ts we're presented with a few red squigglies:

These red squigglies are the direct result of my earlier opting in to NoImplicitAny. So in my view it's already paid for itself as it's telling me where I could start using typings. So to get things working nicely I'll give $routeProvider the type of ng.route.IRouteProvider and I'll explicitly specify the type of any for the 2 params parameters:

// ...
function ($routeProvider: ng.route.IRouteProvider) {
function getTheConventTemplateUrl(params: any) {
var view = params.view || "home";
return "partials/theConvent/" + view + ".html";
}
function getMainTemplateUrl(params: any) {
var view = params.view || "home";
return "partials/main/" + view + ".html";
}
// ...
}
// ...

TypeScriptify siteSectionService.ts#

Opening up siteSectionService.ts we're only presented with a single squiggly, and for the same reason as last time:

This error is easily remedied by giving path the type of string.

What's more interesting / challenging is thinking about how we want to enforce the definition of siteSectionService. Remember, this is a service and as such it will be re-used elsewhere in the application (in both navController and mainController). What we need is an interface that describes what our (revealing module pattern) service exposes:

"use strict";
interface ISiteSectionService {
getSiteSection: () => string;
determineSiteSection: (path: string) => void;
}
angular.module("poorClaresApp.services").factory(
"siteSectionService",
[ // No dependencies at present
function (): ISiteSectionService {
var siteSection = "home";
function getSiteSection() {
return siteSection;
}
function determineSiteSection(path: string) {
var newSiteSection = "home";
if (path.indexOf("/theConvent/") !== -1) {
newSiteSection = "theConvent";
}
else if (path !== "/") {
newSiteSection = "main";
}
siteSection = newSiteSection;
}
return {
getSiteSection: getSiteSection,
determineSiteSection: determineSiteSection
};
}]);

As you can see the ISiteSectionService interface is marked as the return type of the function. This ensures that what we return from the function satisfies that definition. Also, it allows us to re-use that interface elsewhere (as we will do later).

TypeScriptify prayerRequestService.ts#

Opening up prayerRequestService.ts we're again in NoImplicitAny country:

This is fixed up by defining $http as ng.IHttpService and email and prayFor as string.

As with siteSectionService we need to create an interface to define what prayerRequestService exposes. This leaves us with this:

"use strict";
interface IPrayerRequestService {
sendPrayerRequest: (email: string, prayFor: string) => ng.IPromise<{
success: boolean;
text: string;
}>;
}
angular.module("poorClaresApp.services").factory(
"prayerRequestService",
["$http",
function ($http: ng.IHttpService): IPrayerRequestService {
var url = "/PrayerRequest";
function sendPrayerRequest(email: string, prayFor: string) {
var params = { email: email, prayFor: prayFor };
return $http.post(url, params)
.then(function (response) {
return {
success: response.data.success,
text: response.data.text
};
});
}
return {
sendPrayerRequest: sendPrayerRequest
};
}]);

TypeScriptify prayerRequestController.ts#

Opening up prayerRequestController.ts leads me to the conclusion that I have no interesting way left of telling you that we once more need to supply types for our parameters. Let's take it as read that the same will happen on all remaining files as well eh? Hopefully by now it's fairly clear that this option is useful, even if only for a migration. I say this because using it forces you to think about what typings should be applied to your code:

We'll define $scope as ng.IScope, prayerRequestService as IPrayerRequestService (which we created just now) and prayerRequest as { email: string; prayFor: string }. Which leaves me with this:

"use strict";
angular.module("poorClaresApp.controllers").controller(
"PrayerRequestController",
["$scope", "prayerRequestService",
function ($scope: ng.IScope, prayerRequestService: IPrayerRequestService) {
var vm = this;
vm.send = function (prayerRequest: { email: string; prayFor: string }) {
vm.message = {
success: true,
text: "Sending..."
};
prayerRequestService.sendPrayerRequest(prayerRequest.email, prayerRequest.prayFor)
.then(function (response) {
vm.message = {
success: response.success,
text: response.text
};
})
.then(null, function (error) { // IE 8 friendly alias for catch
vm.message = {
success: false,
text: "Sorry your email was not sent"
};
});
}
}]);

I could move on but let's go for bonus points (and now you'll see why the unit test suite is so handy). To quote the Angular documentation:

In Angular, a Controller is a JavaScript constructor function that is used to augment the Angular Scope.

So let's see if we can swap over our vanilla contructor function for a TypeScript class. This will (in my view) better express the intention of the code. To do this I am essentially following the example laid down by my Definitely Typed colleague Basarat. I highly recommend his screencast on the topic. Also kudos to Andrew Davey whose post on the topic also fed into this.

"use strict";
module poorClaresApp.controllers {
class PrayerRequestController {
static $inject = ["$scope", "prayerRequestService"];
constructor(
private $scope: ng.IScope,
private prayerRequestService: IPrayerRequestService) {
}
message: { success: boolean; text: string };
send(prayerRequest: { email: string; prayFor: string }) {
this.message = {
success: true,
text: "Sending..."
};
this.prayerRequestService.sendPrayerRequest(prayerRequest.email, prayerRequest.prayFor)
.then((response) => {
this.message = {
success: response.success,
text: response.text
};
})
.then(null, (error) => { // IE 8 friendly alias for catch
this.message = {
success: false,
text: "Sorry your email was not sent"
};
});
}
}
angular.module("poorClaresApp.controllers")
.controller("PrayerRequestController", PrayerRequestController);
}

My only reservation with this approach is that we have to declare the TypeScript class outside the angular.module... statement. To avoid cluttering up global scope I've placed our class in a module called poorClaresApp.controllers which maps nicely to our Angular module name. It would be nice if I could place the class definition in an IIFE to completely keep this completely isolated but TypeScript doesn't allow for that syntax (for reasons I'm unclear about - the output would be legal JavaScript).

For a small class this seems to add a little noise but as classes grow in complexity I think this approach will quickly start to pay dividends. There are a few things worth noting about the above approach:

  • The required injectable parameters have moved into the class definition in the form of the static $inject statement. I personally like that this no longer sits outside the code it relates to.
  • Because we're using TypeScript arrow functions (which preserve the outer "this" context) we are now free to dispose of the var vm = this; mechanism we're were previously using for the same purpose. Much more intuitive code to my mind.
  • We are not actually using $scope at all in this controller - maybe it should be removed entirely in the long run.

TypeScriptify navController.ts#

navController can be simply converted like so:

"use strict";
interface INavControllerScope extends ng.IScope {
isCollapsed: boolean;
siteSection: string;
}
angular.module("poorClaresApp.controllers").controller(
"NavController",
["$scope", "siteSectionService",
function ($scope: INavControllerScope, siteSectionService: ISiteSectionService) {
$scope.isCollapsed = true;
$scope.siteSection = siteSectionService.getSiteSection();
$scope.$watch(siteSectionService.getSiteSection, function (newValue, oldValue) {
$scope.siteSection = newValue;
});
}]);

I'd draw your attention to the creation of a the INavControllerScope interface that extends the default Angular $scope of ng.IScope with 2 extra properties.

Let's also switch this over to the class based approach (there is less of a reason to on this occasion just looking at the size of the codebase but I'm all about consistency of approach):

"use strict";
module poorClaresApp.controllers {
interface INavControllerScope extends ng.IScope {
isCollapsed: boolean;
siteSection: string;
}
class NavController {
static $inject = ["$scope", "siteSectionService"];
constructor(
private $scope: INavControllerScope,
private siteSectionService: ISiteSectionService) {
$scope.isCollapsed = true;
$scope.siteSection = siteSectionService.getSiteSection();
$scope.$watch(siteSectionService.getSiteSection, function (newValue, oldValue) {
$scope.siteSection = newValue;
});
}
}
angular.module("poorClaresApp.controllers").controller("NavController", NavController);
}

TypeScriptify mainController.ts#

Finally, mainController can be converted as follows:

"use strict";
angular.module("poorClaresApp.controllers").controller(
"MainController",
["$location", "siteSectionService",
function ($location: ng.ILocationService, siteSectionService: ISiteSectionService) {
siteSectionService.determineSiteSection($location.path());
}]);

Again it's just a case of assigning the undeclared types. For completeness lets also switch this over to the class based approach:

"use strict";
module poorClaresApp.controllers {
class MainController {
static $inject = ["$location", "siteSectionService"];
constructor(
private $location: ng.ILocationService,
private siteSectionService: ISiteSectionService) {
siteSectionService.determineSiteSection($location.path());
}
}
angular.module("poorClaresApp.controllers").controller("MainController", MainController);
}

Did it work? Drum roll...#

In unit tests we trust. Let's run them...

Success! I hope you found this useful.