SPA JumpStart – Architecture – Part 2
Any day now my new beginner level SPA JumpStart course for Pluralsight will be published. In this course I guide you through building a SPA step by step. I start with the new ASP.NET templates and quickly zero in on Hot Towel, my custom SPA template that was built for this course. From there, I break down all the core pieces of a SPA on the client.
While there are many ways to build a SPA, this course focuses on a specific architecture. I could spend days boring you with all of the options and combinations that you can combine into a witches brew to make a SPA. And frankly, that would be a lot of fun, but I highly doubt it would be productive as we’d all be left with no clear idea of where to start or how to proceed. Instead, I decided to pick a solid architecture that works well, has a gentle on ramp, and can be expanded on beyond the basics.
The Client Players
Here is a high level view of the players in the SPA on the client. (Follow the links to each of their NuGet packages)
- Breeze
- Q
- Durandal
- Sammy.js
- jQuery
- Knockout.js
- Moment.js
- require.js
- toastr
- Twitter Bootstrap
- Font Awesome
All of these, except Font Awesome, are part of Hot Towel. They all play an important role in a SPA and many depend on each other.
View Composition and App Life Cycle
It’s a lot easier to build an app when you have a tool that helps you do the simple, yet critical things. In a SPA it’s pretty darn important to be able to show a View, pair it with a ViewModel, and be able to hook into events in the app’s life-cycle. Durandal makes this super simple. You may not have heard of Durandal before as it is a newer player in this field, but it is built by the makers of Caliburn Micro, a very popular MVVM framework for .NET and XAML. It follows convention over configuration, which simply means if you name things certain ways, they just work. Kind of cool :) Of course you can override those conventions yourself too.
Durandal does much more too like helping with modal dialogs, event messaging, and creating custom widgets. But the biggest wins come with helping hook into the app life-cycle and composing your views. It makes very simple work out of these and allows you to focus on your app, not the plumbing. In fact, I rewrote my entire demo app in just a few days using Durandal and was able to reduce the code base considerably, and end up with a more fully featured app. The end result was well worth it and you will see Durandal through my course and as a featured player in Hot Towel.
Data Binding
Data binding makes it easy for your data to flow between your source (ViewModel) and target (HTML Views) on the client. Knockout makes data binding easy. For example, Knockout removes the need for you to write a line of code to read data from your viewmodel and then stuffing it into HTML, and then another line of code to pull it back out of HTML and into your viewmodel when you want to save it. If your View has dozens of data points, you can easily see how valuable Knockout can be to reducing your code base. This binding helps with single data points, arrays, and calculated properties.
<pre class=”prettyprint> // Assume firstName is a member of the viewmodel speakerVM var firstName = ko.observableArray(); </pre>
<pre class=”prettyprint> </pre>
If you want to learn more about Knockout, please watch my Knockout course at Pluralsight.
Routing
Your app is likely to have more than 1 View so it makes sense that you may want to handle paging between those different Views. One of the huge benefits of a SPA is that you keep the app running on the client in the browser, without making a roundtrip for every View change. This makes a solid routing library essential for a SPA. Sammy fits this bill quite nicely, and the good news is that Durandal wraps up Sammy to hide a lot of Sammy’s complexity. I worked hard on putting Durandal through the ringer and provided a lot of feedback on the router because I felt it was a critical piece of a SPA. I am very happy with how easy Durandal has made routing, which follows the same type of code that I built from scratch for my custom router in my intermediate SPA course.
You can set up Views and their routes in a variety of ways. One of the simpler ways is to assume the views and viewmodels are named to match each other (ex: speakers.js and speakers.html) and then add a route like this with router.mapNav
:
function boot() { router.mapNav('home'); router.mapNav('details'); return router.activate('home'); }
Require.js
Organizing your code, separating your JavaScript logic into units of similar roles and responsibilities, managing dependencies, and loading JavaScript files on demand. These are all key features to making your JavaScript app maintainable. How do we do this? I recommend using the Module Pattern (described in my course) and using the Require.js library, which follows the AMD (Asynchronous Module Definition) pattern. Sounds like a mouthful, but really all you need to do is change your code from this
var speakersViewModel = (function (datacontext) { var speakers = ko.observableArray();var activate = function () { return datacontext.getSpeakerPartials(speakers); }; var refresh = function () { return datacontext.getSpeakerPartials(speakers, true); }; var vm = { activate: activate, speakers: speakers, title: 'Speakers', refresh: refresh }; return vm;
})();
to this
define(['services/datacontext'], function (datacontext) { var speakers = ko.observableArray(); var activate = function () { return datacontext.getSpeakerPartials(speakers); }; var refresh = function () { return datacontext.getSpeakerPartials(speakers, true); }; var vm = { activate: activate, speakers: speakers, title: 'Speakers', refresh: refresh }; return vm; });
Notice we just wrapped the module with a define statement. You’ll learn more about this in my course (its introduced in my beginner course and then I add more detail in my intermediate course).
Rich Data
This is a topic that you may not think you need until you try to build a SPA without it. If you don’t believe me, go build a SPA and try to share data across multiple view models and handle object graphs. Try to write code that queries locally first then remotely. Yeah, you can do it. I have. But when I switched to Breeze I removed a massive amount of code.
This topic is pretty huge and I cover quite a bit of Breeze in the course. But here is a glimpse are what you can do with Breeze:
/* Query like LINQ */ // Define query for customers // starting with 'A', sorted by name, var query = breeze.EntityQuery .from("Customers") .where("CompanyName", "startsWith", "A") .orderBy("CompanyName"); manager.executeQuery(query);
Promises with Q
Breeze relies on Q, which is a small and simple library that implements promises. Promises are important so we can handle asynchronous programming that is pervasive in disconnected client apps like SPA’s. With Q we can write things like the code below, which executes a query against a remote server. When it is done executing successfully, the then condition executes. However, if the query failed the fail condition executes. You could go “Old School” too, and use callbacks, but promises just make it all much easier.
// Returns a promise return manager.executeQuery(query) .then(querySucceeded) .fail(queryFailed);
Alerts / Messages
Most apps needs a way to tell the user when something happens. We have a few styles of doing this: modal windows, non modal windows, and transient windows. Toastr (A library I wrote) helps handle the transient windows. It is ideal for telling the user when something happened and then it fades away into the night. It’s easy to use and very adaptable too.
toastr.info('Data saved successfully');
I also use toastr as a logging mechanism while debugging (see Hot Towel for an example implementation).
Dates
Dates can really suck. Moment.js makes dates suck less. If you get data that contains a date, but that date is in some oddball format, Moment can help convert it to whatever format you want. It’s easy to use and handles manipulation of the dates too. Add days, minutes, years, calculate the difference in dates, convert date formats, format dates to strings: Yep, it’s in there.
// Convert the start variable to a UTC date // Then format it var x = moment.utc(start).format('ddd hh:mm a');
It’s a small library with a singular focus. But that’s a good thing, as it does this one thing very well. I am hard-pressed to think of an app I have been involved with that did not have dates in it. I’ll gladly use moment in all future apps.
Styling
I’m no designer, so I will take all the help I can get with any design, including icons. Font Awesome helps add some simple icons that you can add easily to your app via CSS. For example, the following HTML adds a spinner icon.
Simply put, Font Awesome makes it simple to add icons for anywhere you need them, including buttons or spinners.
What about styles themselves? You may be used to jQuery UI or some other widget library, but for simple styles, layout, and responsive design I prefer Twitter Bootstrap. It uses CSS to style most elements, is easily configurable, and has some more complex components that are JavaScript based. Basically, Bootstrap allowed me to focus on writing the code for my app, and not worry so much about the design elements. I still customized some of the styles, but its ideal for getting off the ground quickly.
The Server Players
to round things out, here are the key players on the server. I won;t go into each of these deeply as the focus of this course is on the client. The key takeaway here is that you can build our back-end however you like as long as you expose the HTTP services (ideally serving JSON).
- Breeze Web API components
- Entity Framework
- ASP.NET Web API
- ASP.NET Razor
- ASP.NET
- ASP.NET Web Optimization