The goal of this chapter is to provide a quick introduction to some principles that will help you create high quality code, specifically aimed at frontend web application development. For a fuller study, I strongly recommend the article, Patterns For Large-Scale JavaScript Application Architecture, by Addy Osmani.
If you're reading this book, I'm going to assume you've done at least some programming work yourself, likely more than a little. During that time, I hope you've had the chance to see some great code. Perhaps it was your own, but more likely, at least for the first couple times you glimpsed it, it was someone else's masterpiece. You probably didn't necessarily know what made it great; you just knew that it was far better than anything you had ever been able to extract out of a keyboard.
On the other hand, almost anyone can identify bad code (unless it's their own, but that's a whole different book). The logical holes, the ignored errors, the horrifyingly inconsistent indentation; we've seen it all, often with our own name attached to the file, but somehow transforming that spaghetti mess into anything resembling those works of art that we'd previously marveled at continues to escape us. This book isn't about beautiful code, but it is about a framework which flexes its muscles most effectively when wielded in a manner optimized for frontend applications, and as such it's worthwhile for us to spend a chapter discussing some of the best practices for modern frontend web development.
For the purposes of our overview, we'll look at two basic tenets: modularity and data driven development. Before we examine those, however, I want to use the next section to address a common misunderstanding about frontend web development: adding more APIs is not always the answer.
Often, when a backend developer first begins working on a frontend project, they believe that they can simply create an awesome API in the backend, call it with the frontend code, and have a complete frontend web application. When I first started developing frontend web applications, I wrote a lot of code that looked like the following:
$('#nextLink').click(function () { $.get('api/next', function (nextPage) { displayPage(nextPage); }) });
While the frontend technically handles both the user interaction ($('#nextLink').click()
), and the display (displayPage(nextPage)
), the real driver here is the backend API. The API handles the logic, the state, and makes nearly all the decisions about how the application should actually function.
In contrast, the frontend applications built on top of data modeling frameworks allow us to move away from that paradigm and instead position the client-side code as the primary driver. This is awesome for two reasons:
It allows modern web developers to do 90 percent of the coding in the same language. This creates the potential for more code reuse, easier debugging, and all-round more efficient development, since even developers who speak both client and server-side languages fluently will lose some momentum when they have to switch between them.
The user experience vastly improves when everything we need to run the application is already downloaded and available. Because the majority of the logic and application processing is done client side, we are no longer dependent upon network requests or additional downloads before moving the user forward. And as the JavaScript engines in all modern browsers get continually faster with each release, even computationally intense processes are becoming less and less of a limiting factor.
These reasons can make a significant difference in even the smallest of applications, even if it's only in your own peace of mind while developing. As you begin to tackle larger projects, however, especially if you're working on a distributed team, modular code that all builds on top of the same data-model becomes mission-critical; without it, each bit of functionality might expect a different property, flag, or (brace yourself) classname to represent the appropriate state for your application. In contrast, when your code is data-driven, everyone can work off the same built-in value map, allowing different pieces to connect far more seamlessly.
Now that we've clarified what frontend development isn't, let's gets back to the key principles that lead to great frontend application code.
The principle of modularity is hardly specific to frontend web applications, and most developers these days recognize its usefulness, so I won't spend a lot of time here, but it's worth a quick overview.
The primary goal of modularity is to ensure that the code you write can be reused in different parts of the same application, or even in different applications entirely, without requiring extensive re-architecting. This also helps ensure that a change to the internal logic of one feature doesn't negatively impact the functionality of any other. In his article, Patterns For Large-Scale JavaScript Application Architecture, Addy Osmani describes it as:
Decouple app. architecture w/module,facade & mediator patterns. Mods publish msgs, mediator acts as pub/sub mgr & facade handles security.
In non-twitter speak, the basic goal is to make sure each feature/module keeps track of its own data/state/existence, is not dependent on the behavior of any other module to perform its own functionality, and uses messages to alert other modules to its own changes and appropriately respond to the changes of others.
We'll dive into modularity in great detail in the coming chapters, as it's one of the core principles of Angular Directives, so for now we'll leave this summary here, and continue to the next key principle for frontend web applications.
There are several different X-driven development ideologies in the world of software and web development, test-driven and behavior-driven being two of the most popular. data driven development (DDD from here on out) doesn't preclude any of these, and actually works simultaneously with many of them quite easily. DDD simply means using the structure of the data (or the model) as the foundation from which you build and make all other development design decisions. This is most easily explained by looking at an example, so let's start here, and then we will reverse the process to create a new application in the coming chapters.
In this example, we've created the quintessential frontend widget, a twitter feed display. This also serves as a good moment to highlight that not all web applications have to fill the entire page. Often, a web app is a simple widget like this, possibly something that can be embedded in third-party sites, and while there are some differences in structure, the basic organization and guidelines are still the same.
First, a quick snippet of some JSON data that we might use for this widget (we won't worry about the actual retrieving of data from Twitter right now):
[ { "author" : "mrvdot", "text" : "Check out my new Angular widget!", }, { "author" : "mrvdot", "text" : "I love directives!" } ... ]
The HTML:
<div ng-controller="WidgetController"> <h3>My Tweets</h3> <p ng-repeat="tweet in tweets"> @{{tweet.author}}: {{tweet.text}} </p> </div>
And finally the JavaScript:
function WidgetController ($scope) { $scope.tweets = [];//loaded from JSON data above }
While the preceding example does operate within the Angular framework, the basic structure here is representative of all good frontend architectures.
Let's first take a high-level view of what's happening here. The first thing to note is that the data itself is most important. I listed it first not because it was shortest, but to illustrate that the data is the foundation from which the rest of the code evolves. After the data, we move onto the HTML. This is most applicable in Angular, though it applies to other frameworks as well. In this model, once we have the data, we use the HTML to describe the view, how we want to display the data, and also (jQuery aficionados, brace yourselves) how we want the user to interact with that data. Only then, at the end, do we write the little JavaScript code needed to glue it all together as the controller. From here, let's walk through it stepwise to see how everything works together.
When we first initialize our application, the first thing we need to do is load our data. In Angular, this is most commonly done through a service, which, while a vital part of Angular development, is outside the scope of this book. For now, let's just assume that we've already loaded our data into $scope.tweets
. We'll dissect $scope
in great detail later in Chapter 5, Keeping it Clean with Scope, so for the purpose of this example, just know that it serves as the link between the view and our data.
Let's revisit the main element of our widget, the tweet paragraph tag:
<p ng-repeat="tweet in tweets"> @{{tweet.author}}: {{tweet.text}} </p>
The first part of the HTML code uses the ng-repeat
attribute to declare (again, remember we're building our HTML on top of the data-model, not receiving a model and remolding the HTML to reflect it) that we want to iterate through the array of tweets and print out for each the author's handle and their tweets in a paragraph tag.
Finally, because we've focused on building the HTML on top of the data itself, our JavaScript is only a few lines:
function WidgetController ($scope) { $scope.tweets = [];//loaded from JSON data }
With this approach, our JavaScript is nothing more than a function (attached to our element via ng-controller="WidgetController"
) that binds our tweets to a $scope
object. We'll discuss the specifics of scopes and controllers later, for now just know that the scope serves as a bridge between the controller and our HTML.
Consider how we might have done this with jQuery (or a similar DOM manipulation library). First we'd have to iterate through all of the tweets and build the HTML string to insert into the message list. Then we'd need to watch for any new changes to our tweet array, at minimum append or prepend new items, or possibly rebuild the entire list if we can't rely on all our tweets coming in order.
Don't misunderstand, jQuery is an amazing library, and we'll go into extensive detail about how to use it in conjunction with Angular in the chapter on linking. The problem, however, is that jQuery was never designed to be a data-model interaction layer. It's great for the DOM manipulation, but trying to keep track of the DOM and the data at the same time gets tricky very quickly, and as anyone who has previously built an application using this structure can attest, adequate testing is nearly impossible.
Hopefully by now you're beginning to see that frontend web applications are far more than just a collection of Ajax calls with a master backend still running the show. And as such, principles such as modularity and data driven development are vital to successful and efficient development. Modularity helps us plug features together without worrying about undocumented interactions breaking our entire app. And DDD ensures that every bit of our code stands on the foundation of the data-model itself, so we can be confident that the user's view and interactions accurately reflect the true state of the application.
If you're still not convinced about everything, that's ok, we'll explore both of these principles in more detail throughout the coming chapters. For now, though, let's take the next chapter to explore what distinguishes Angular.JS from many of the other common JavaScript MVC frameworks available today.