AngularJS is too humble to say you’re doing it wrong

December 2nd, 2012

For years, web application developers have used DOM manipulation tools like jQuery to control their user interface. Astute developers have taken it to the next level with client-side templating tools like Mustache and Handlebars.js to build sophisticated user interfaces on the client side.

And then AngularJS came along.

And we all realized we’ve been doing it wrong.

Way wrong.

The Old Way

Remember before you discovered AngularJS? Back when your code was organized like this:

  1. HTML that defines your page
  2. JavaScript that downloads AJAX data
  3. HTML that defines a client side template
    (yeah, this: <script type="text/html">...</script>)
  4. JavaScript that renders the client-side template
  5. JavaScript that injects the rendered template HTML into the DOM

You thought that was pretty cool. “Hey look,” you said, “I’ve off-loaded template rendering to the browser!”. Yeah, you were pretty cool.

But then AngularJS showed you how you were wrong. You could accomplish the same client-side magic with a lot less code.

The New Way

Under AngularJS, your code can be organized like this:

  1. HTML that defines your page and client-side templates inline
  2. JavaScript that downloads AJAX data

How does that even work? I mean, you’ve got like less than half of the bullet points now.

The answer: Data Binding.

Data Binding is the secret sauce of AngularJS (along with a couple dozen other delicious spices and condiments). Oh, and it’s not a secret at all. The code is actually pretty easy to read, despite being pure magic awesomeness on environmentally-friendly steroids.

Shall We Do Show-and-Tell?

The old way

This should be very familiar. You’ve got an HTML page, and you want to add some dynamic content with a client-side template:

Here’s the page HTML:

<html>
  <body>
    <div id="my-list-of-animals">
    </div>
  </body>
</html>

Let’s use Handlebars for the client-side template:

<script id="animal-template" type="text/x-handlebars-template">
  <div>
    <div>{{animalName}}</div>
    <div>{{favoriteFood}}</div>
  </div>
</script>

And you need some JavaScript to download the AJAX data, render the client side template, and inject it into the DOM:

$.get("/api/animals/", function(response) {
  var source = $("#animal-template").html();
  var template = Handlebars.compile(source);
  $.each(response, function() {
    var html = template(this);
    $("#my-list-of-animals").append(html);
  });
});

Put all that together somehow, and wow, that’s a lot of code to do something that should be very simple.

That covers all 5 bullet points above. You’re still cool, right?

The AngularJS Way

Remember how there are only two bullet points. Well, here they are:

First, your page HTML:

<html ng-app>
  <body>
    <div ng-controller="MyAnimalController">
      <div ng-repeat="animal in animals">
        <div>{{animal.animalName}}</div>
        <div>{{animal.favoriteFood}}</div>
      </div>
    </div>
  </body>
</html>

Did you see that? There’s no <script> hack. The template is right inside the page! Right there. In-line. Right where you wanted it all along, but never had the guts to ask. But I digress.

Second, the JavaScript:

function MyAnimalController($scope, $http) {
  $http.get("/api/animals/").success(function(response) {
    $scope.animals = response;
  });
}

And that’s it. It really is glorious. The AngularJS team really distilled the client-side template problem to its essence and produced an elegant solution. But don’t take my word for it. Check out the win list below.

Win List

Look at all the prizes you won by using AngularJS:

  1. CSS classes are just for CSS now.
    You used to abuse the HTML class attribute so you could find elements in the DOM with jQuery. Now, your class attributes are only for CSS. You don’t have to wonder anymore whether a particular class is used by JavaScript or CSS. The answer is always CSS: The way nature intended
  2. No <script> hacks.
    You don’t have to trick the browser into ignoring your client-side templates anymore. Don’t you feel cleaner?
  3. Client side templates are cohesive with your page
    You used to have a pile of client-side template files scattered throughout your project, or all crammed into your HTML <head>. Now they are right in the page, exactly where you wanted to read them in the first place.
  4. You don’t have to name every template
    You used to name each <script> element with an “id” attribute to make it findable by jQuery. You can nuke those strings in your JavaScript and HTML, and not worry about them getting out-of-sync.
  5. You have control over the scope of your JavaScript
    Previously you had to worry about your jQuery selectors casting too wide a net and selecting elements beyond what you intended. For example, suppose there happened to be an element in the page somewhere with class “animal”, and you didn’t know about it. Then you wrote a jQuery selector like $(“.animal”). Boom, you just mofidied an element you didn’t intend to. With AngularJS, your JavaScript cannot reach outside the ng-controller demarkation.
  6. You don’t have to remember to clean up HTML on refresh
    Let’s say I want to refresh the animal list in this example. Under jQuery, I have to remember to do this: $("#my-list-of-animals").html("") before I start .append()ing new animals. In AngularJS, I just replace $scope.animals with the newly downloaded list, and it automatically clears out the old HTML.
  7. Your JavaScript is cleaner
    Your JavaScript has no CSS selectors anymore. It used to be littered with strings to locate elemenents in the DOM. Now it’s just got business logic. Pure, sweet business logic.
  8. You can unit test your JavaScript without a DOM
    I should have mentioned this first. Notice how my JavaScript has no knowledge of a DOM? It doesn’t even know there’s HTML involved at all. This makes it much easier to unit test, because you don’t need to load a big chunk of fixture HTML to test your JavaScript. AngularJS provides some great docs on testing too.

Almost Too Humble

I started reading about AngularJS and re-working parts of my projects to use it in earnest only a few weeks ago. A day into the effort, the light bulb went on for me:

I’ve been doing this all wrong

At that point, I started to wonder why the AngularJS team didn’t write article after article with the same title: “You’re Doing It Wrong”. And that’s what inspired me to write this.

So there you have it. An opinionated blog post about an opinionated framework.

30 Responses to “AngularJS is too humble to say you’re doing it wrong”

  1. Mike Murray Says:

    Great post. We’ve been using KnockoutJS for a year or so now and found many of the same benefits regarding testability and better separation of concerns (for sure cleaner markup). I like a few of the differences in how AngularJS minimizes its footprint even more. Does Angular handle two-way bindings? That could be one key difference between it and Knockout. Good stuff, thanks for sharing.

  2. Dave Says:

    Yes, AngualrJS has two-way bindings. Example: <input type=”text” ng-model=”myName” />

  3. Eric Berry Says:

    Thanks for this post. I understand the power of using frameworks like this.. but how do you handle populating data initially? For example, if I wanted to create a table view full of data in a list (i.e. @books), how would you tell the app to load these initially? This question and the ability to maintain page state are the biggest concerns I have with angular and any other similar framework.

  4. Dave Says:

    Eric, that’s exactly what my example code does. It loads a list of animals, but the list comes from AJAX. If you have an initial list in hand already, just assign it to a $scope variable in your controller like this:

    function MyController($scope) {
      $scope.books = [
        {title: "Moby Dick", author: "Bob"},
        {title: "Twilight", author: "Joe"}
      ];
    

    And boom, it appears on the page immediately.

  5. Dave Says:

    Eric, also keep in mind that AngularJS is not for everyday content web sites. It’s meant for web applications (think Gmail not CNN.com).

  6. Jason Staten Says:

    If you do need to interact with the DOM, there is a defined place for that: directives. They are useful for if you have to hook in something like a jquery plugin or a reusable component (I use it for SVG charts).

    One key difference between Angular and Knockout is change tracking. Knockout uses ko.observable() for properties to watch. Angular uses dirty checking, by comparing a scope’s properties at specific times (think smart polling). A perk is that your code doesn’t need to use function invocation for reading and writing. A downside is to integrate from outside Angular requires wrapping assignment with scope.apply()

  7. Mike Murray Says:

    Thanks for that pros/cons look at Knockout vs. Angular, Jason.

  8. Evan Farrer Says:

    I don’t do web development so I don’t fully understand what you’re describing. However, the thing that stands out to me is how nice is is to have related bits of code all in the same file and preferably close to the same lines. I’ve been calling this trait syntactic locality (for lack of a better name). A major benefit of AngularJS appears to be that it increases the syntactic locality of your code. The same holds true for closures as call backs, inner classes, procedural programming (vs. goto’s). It would be interesting to see of one could quantify the syntactic locality of programs written in different styles and programming languages (perhaps by measuring line distance between consecutively executed statements) to see if it’s a good measure of the “goodness” of a language or framework.

  9. Dave Says:

    Evan, I think syntactic locality is a specific type of cohesion.

  10. Rick Says:

    The problem with declarative programming is edge cases. What happens when the angular custom tags (directives) don’t provide what you need? Well, I know the answer is to write a custom directive or use filters on a directive. But I wonder if I’ll run into limitations? I wonder how often the solution will be more difficult and verbose than the non-Angular way? I hope that Angular isn’t a client-side manifestation of now obsolete (for good reason) projects like Java Struts custom tags and ASP.NET web controls.

  11. Nick Lewis Says:

    I have been playing with Angular for a few weeks now, writing a project with it that is fairly ambitious and will be big when it’s ready. I tried writing the same project with other tools but each time it stalled for one reason or another. So I began again with Angular and it’s taking shape – because a lot of the annoying things have now been eleviated.

    Your article has reflected a whole flurry of thoughts that have been whirling around like one of those snowflake paperweights!

  12. Dave Says:

    Rick, I have been amazed at how many edge cases the AngularJS team has already thought about. I have yet to run into a show-stopper and my team is about to ship a complete 2-month rewrite of our web app UI this week.

  13. Sharon Says:

    Amen! I *love love love* the fact that I can look at an HTML template and see what elements will be doing what at a glance. Yeah, it might not be “pure” to markup the HTML with Angular directives, but it makes so much more sense than traversing through a collection of jQuery selectors trying to picture the various outcomes. And the data-binding is blinding in its awesomeness. Anything can be a model! If it changes anywhere, it changes everywhere!

  14. Schmulik Raskin Says:

    I want to point out that Angular does support external templates, i.e. in script tags or on the server, but it used for situations that really need that separation, such as dynamic views based on routing.

    Even then, Angular handles all the loading/locating, binding and insertion for you, so all you need to do is make sure the template exists.

  15. Dave Says:

    Schmulik, for performance I’ve considered adopting that approach. I envision the server returning all templates wrapped in script tags on initial page load, such that Angular never needs to download a partial while the user is navigating the app. I’m aware of $templateCache, but I want to turn N requests for partials into 1.

  16. Joe Says:

    I researched AngularJS for a few weeks prior to making the decision to use it on a large rewrite of an existing web application. I loved the concepts, separation of concerns, and structural organization that Angular offers and prescribes. Of course, testability is also key. Now that I’m 3 weeks into development, I’ve found that it really does live up to its promises.

    Scopes give you a place to put your data model. Controllers give you a place to put your business logic. We’ve been using directives to declaratively annotate component templates that give us the two way hook between DOM events and handlers. We created some custom events to pass messages and watches to perform actions on model changes across contexts.

    Adding in dependencies, such as ng-grid, a charting library, etc, have been pretty straightforward. Things are becoming more routine now, and I’ve developed confidence that I can develop complex features with relative ease. I’m completely satisfied that we made the right decision to go with Angular. People come by my desk and ask me why I’m excited by work these days, and it’s largely due to this good experience working with this framework that gets it right.

  17. Sacha Says:

    Nice write-up! But you can also use Meteor (http://meteor.com) and strike off one more bullet point from that list :)

  18. Vince Says:

    Good article.

    As an addition, if you wanted to get objects that can be manipulated over REST, use $resource instead of $http. Then you even don’t need a response handler:

    function MyAnimalController($scope, $resource) {
    var Animal = $resource(“/api/animals/:animalId”, {animalId:@id});
    $scope.animals = Animal.query();
    }

    Note that in order for this to work, you have to change your HTML file:

  19. LC_ Says:

    For initial data I use a global JS variable that the controller will treat like a ajax/json response.

    There have to be a better way but I have not found it yet (I think I read something about providing inital value to services, I don’t know).

  20. Sumeet Chawla Says:

    This is so true.. Am in love with AngularJS ever since I starte on my new project… Thought I have had a few bumps here and there because the design pattern is completely different from that of normal jquery usage.. But its fun! Most importantly, the speed in which the application behaves…tremendous!

  21. Martin Says:

    I agree with Rick. Bindings in the template markup are a critical design flaw that was made 10 years ago on the server-side. Angular like ASP.net and Java struts before it has been ‘done wrong’ in this regard. The binding must be template agnostic. Even Microsoft had to learn this lesson the hard way. My money is on the Require.js/backbone.js/bootstrap stack available through Yeoman scaffolding project.

  22. Peter Herz Says:

    Yeah this Angular realization came way too late for many web / javascript ui coders.
    However myself hailing from the CGI era of coding in Perl.. I recall reading a very
    similar epiphany by Larry Wall he made for himself or with a contributor’s insight
    that led Perl down a route of OOP using variable and object binding. Somehow
    it magically opened possibility and paradigmatic doors for Perl that were once
    cumbersome or non-existent language features.

  23. Russell Says:

    @Dave

    Enjoyed reading your blog post. In a previous comment you said:

    “AngularJS is not for everyday content web sites. It’s meant for web applications (think Gmail not CNN.com).”

    Could you expand on that a little more please? I was wondering why angluarjs would not be suitable for a content web site.

  24. Dave Says:

    Russell:

    Here’s what I meant.

    Content web sites usually want to have good search engine optimization. Since an Angular app will load all (most?) of its page content via JavaScript after the initial page load, most web crawlers will not see any of that content, and hence they will not index it.

    If you don’t care about SEO, and it’s okay for your content to load dynamically (after page load), then go for it.

    –Dave

  25. Yogesh Says:

    +1 for mentioning syntactic locality

  26. Danny Says:

    @Martin

    Do you have evidence to back up your claim that “bindings in the template markup are a critical design flaw”?

    The fact that struts or ASP.net have fallen by the wayside doesn’t prove your statement correct.

  27. frattaro Says:

    I don’t need to say much, the other comments summed it up that angular is on the wrong path that we covered 10 years ago and concluded that had to be done differently.

    I’ll just add that the author might consider reflecting on his exuberance before writing a future article as self-righteous as this one.

  28. brense Says:

    I coudn’t take AngularJS serious after reading this in the tutorial:

    Wtf… foreach loop with HTML? Just because HTML attributes don’t have a strict specification doesn’t mean it’s okay to start abusing them to put scripting logic inside HTML attributes…

  29. Brad Gearon Says:

    We all have. I have my own ideas on the why:

    https://github.com/bradgearon/cohesive

    Feedback welcome of course

  30. Team Blog | CasperJS for Java developers Says:

    […] abstraction of MV* pattern in frontend layer of any web application (we use Grails underneath). Here is a nice article with an 8-point Win List of Angular way of handling AJAX calls and updating the […]

Leave a Reply