Making an Angular-powered frontend with Umbraco

by René Pjengaard & Filip Bech

Have you ever wanted to make a really cool website with cool transitions instead of those tedious pageloads? Maybe even as cool as the Umbraco 7 backoffice? Here is our recipe. I'm sure there are other ways to do it, but now that you have to learn Angular to extend the backoffice, why not use that?

Routing

First off we have to tame Umbraco and hand over the routing to Angular. This could be done very simple by creating a new UrlRedirect which always uses the same template. This can be done by adding this rule to the UrlRewriting.config file:

The rest of the requests (unless you do a browser refresh) will be taken care of by Angular.

The startview template

It may sound a little odd that we use the same template no matter what url we hit, but that's because Angular also takes care of the templating (more on that later). So all we want is to load an (almost) empty template along with AngularJS and some styling.

Since you are reading this I presume you are a cool dude or dudette, so of course you will be using Umbraco 7 + MVC. So go ahead and create a template in your View-folder and call it e.g. "StartView.cshtml". Insert the following markup and save your file.

The StartView is plain simple. In real life we would also handle navigation via the API, but for now we want to keep it simple.

The partial view in the bottom is for rendering out javascript variables that are constants. E.g. addresses and phonenumbers or dictionary items for multilingual sites; it could be anything that is constant for the whole site.

In this example i have made a GlobalModel which the partial renders out as a javascript JSON variable.

I use the ModelMetadata to loop through the GlobalModel properties, and render out the propertyname (first char lower, or else my frontender will kill me) and the value of the property.

Now we are all set to start creating the Models and Controllers for Angular to communicate with Umbraco. Are you still awake and alert? Here we go!

The API

What I normally do is create a model per doctype. So to begin with I create a model for the frontend doctype. In a real situation I would create a master model first where I would put all the properties I want on all my models, but for this example we just create this single model to keep it simple:

We now have our controller. Please notice that I added an "Access-Control-Allow-Origin" in the start of the method. This is not necessary if you don't want to access the method from another domain. I also decorated the class with [JsonOnlyConfiguration]. This attribute decoration came from the Skybrud.WebApi.Json NuGet package i have installed (read further down). This means that even though my controller is a UmbracoApiController, and thereby can return both xml and json, it will only return json. The rest of the method is self-expanatory. It looks up the content by urlname and returns the found data as a JSON-object.

The controller will try to find the url specified and either return the model in JSON-format or a 404 HttpStatusCode with an error message you define.

All the magic that is used to convert our models to JSON is provided by Anders Bjerners NuGet package Skybrud.WebApi.Json

That's all folks! Now you are ready to go on with the Angular implementation. So I hand over the rudder to my good friend and colleague Filip Bruun who will navigate you through the seas of Angular. Aye aye captain!

Angular routing

Cheers everybody.

Now that René has set everything up on the server I'll try to walk you through the simplest possible solution I've found to generic routing and templating with Angular (for the sake of simplicity I won't go into handling server errors or 404s)

The Angular team have built a very basic router, which they moved out of the core and into its own module in Angular 1.2. That means we need to declare it as a dependency to use it, but it also means that you are free to use other routers (there is really only alternative at the moment: the ui.Router). I prefer the latter and this example is made for ui.Router, but all the features I use in this example also works with the ngRoutemodule from the Angular team. (although the syntax is a little different, so I put up a ngRoute version in the github repo too).

Angular routers work by reading the url and then intercepting all internal links so clicks will push url changes without reloading the page (while keeping the addressbar and history up to date). When the location changes the router will figure out what to display from settings in an object. So our task at hand is to set up a route object that will match any url and then do an ajax request with the path to the API that you just built in the previous section.

Now all we need from the html is to include the javascript files, provide a base-tag (so all resources (images, css, etc) are fetched from the right location) and a DOM node with a ui-view attribute (and a DOM node with an ng-app attribute to kick off Angular). We also bind the title-tag to a pageTitle value so it can easily be updated. All of this goes in Renés startview...

If you have played around with Angular this won't look to scary to you. The $stateProvider.state() method takes a name as the first parameter and the config object as the second (this is where ngRoute differs a little from ui.Router).

The url value is some special syntax that basically means match anything and capture it (regex-style) into a variable named path.

Resolve is an object of methods that needs to be resolved before the route will instantiate. If you return anything other than a promise (or false) the router will take that as being resolved. If you return a promise the route won't be initiated before that promise is resolved. We return the $http-call directly as that itself returns a promise. That means the route will run on a successful response to that ajax-call.

The routes are static so we can't change the template based on data we receive. We work around this by having the minimal possible template that then uses the ngInclude directive to render a template depending on a url value (that you can set/change at the response of the server).

All that's left is the controller that takes the data the resolve (with the key "getData") returns, and makes that available to the template. At this time we also update the pageTitle value of the $rootScope so the page title is updated.

Voila, there you have it! Angular now controls the routing and templating on the frontend, and you can still have editors change the content and even the template.

Remember to include ngSanitize to allow angular to bind html content from an ajax-response to your view.

The article could easily end here, but I thought we should give you the basics of actually animating the page transitions as well.

Page transitions

Angular also has a module (also not included in the core but made by the team) for animation; it's called ngAnimate. You include its source and set it as a dependency in your module. This will enable two things for you: It will 1) add some css hooks for you when it's transitioning in and out (see the doc in the previous link), and 2) provide you with some javascript hooks for the same thing. The page transition we use could easily be done with a css transition, but I'll implement it the more empowering alternative way.

The hooks are available as methods on an object that the .animation() method returns. We'll add '.view' as our first parameter (the elements that has the hooks) and as the second a function that returns the hooks for enter and leave-animations. The methods will be called by Angular when it's changing the view, and will be called with the element (or rather, an angular.element/jqLite wrapper that contains the element) and a done function that you'll call when your animation is done. This tells angular that you are done animating, so it can remove the element from the DOM at the right time. For the sake of simplicity I'll use the GreenSock Animation Platform to actually tween the values and handle the callback (for the TweenLite calls i use element[0] to get the native element instead of the angular-wrapped one).

I hope you can grasp the power in this humble module and start imagining what great opportunities this gives you. Good luck! I hope to see more Angular powered Umbraco one-pages in the future!

A couple of tips that could make your one-page even more awesome:

A spinner: You could add a spinner or something to indicate that you are loading the next page. Add it in the resolve getData method and remove it from the controller.

Save requests: Cache and inline your client side templates so you won't need to make an extra request for them. We do this with a grunt task named ngTemplates. Tweet at me (@filipbech), and I'll send you some configuration if you would like.

Error handling: The router will broadcast an event named "$stateChangeError" when resolves fail (eg. header 404, 500 etc). Listen for that event and act accordingly.