Working on a project recently, I went through a process where I converted jQuery-style selectors and DOM manipulation to “The Angular Way”, and I thought I’d share my results.
This is one part of AngularJS I feel every web designer should become familiar with, as it shows how much easier it can be to understand JavaScript written by someone else when you use AngularJS. The reason AngularJS can be easier to understand is a result of its declarative nature, meaning you define click handlers right in your HTML, which in a larger project, can make it much easier to understand just what is going on under the hood.
This example will use a few simple buttons to show and hide content, and I’ll first demonstrate how we might accomplish this with jQuery, and then convert it to Angular. Suppose we were creating an app that allows users to create blog posts, and has three associated views. We might have the following buttons, and content sections:
<button id="btnCompose">Compose</button> <button id="btnPreview">Preview</button> <button id="btnPost">Post</button> <div id="blogCompose"> <h1>Blog Compose</h1> </div> <div id="blogPreview" class="hide"> <h1>Blog Preview</h1> </div> <div id="blogPost" class="hide"> <h1>Blog Post</h1> </div>
In jQuery, we would typically have to get all our buttons and assign a click handler that manages the display of the content (probably using a class attribute). Given the above HTML markup, our jQuery might look something like this:
$('button').on('click', function() { var target = $(this).attr('id'); }); if (target == '#btnCompose') { $('#blogCompose').removeClass('hide'); $('#blogPreview, #blogPost').addClass('hide'); } else if (target == '#btnPreview') { $('#blogPreview').removeClass('hide'); $('#blogCompose, #blogPost').addClass('hide'); } else if (target == '#btnPost') { $('#blogPost').removeClass('hide'); $('#blogCompose, #blogPreview').addClass('hide'); }
We’re getting all the buttons on the page, and finding their id attribute. We then conditionally add or remove classes based on which button we’re clicking. The major drawback here is that it relies heavily on the structure of the HTML. If the HTML structure changes in the future, the jQuery selectors all have to be rewritten. Let’s take a look at how we might convert this to AngularJS.
First, here’s how we would change the markup:
<button id="btnCompose" ng-click="composeBtn()">Compose</button> <button id="btnPreview" ng-click="previewBtn()">Preview</button> <button id="btnPost" ng-click="postBtn()">Post</button> <div id="blogCompose" ng-show="state.blogCompose"> <h1>Blog Compose</h1> </div> <div id="blogPreview" ng-show="state.blogPreview"> <h1>Blog Preview</h1> </div> <div id="blogPost" ng-show="state.blogPost"> <h1>Blog Post</h1> </div>
You’ll notice two main changes here. First, we added what looks like an ‘ng-click’ attribute to the buttons. ngClick is an Angular directive that calls a function in the associated controller. Next, we removed the classes from the content, and replaced with ngShow. ngShow is another Angular directive that shows or hides content based on its value. In this case, the values are state.sectionName. We’ll see what the state values are when we get to the JavaScript.
For now, take a moment and compare the jQuery and Angular HTML. The Angular markup gives you clues that clicking a button calls a function (and once you know more about Angular, you’ll know instinctively this function will live in the controller associated with this section of the DOM), while the jQuery buttons could have associated click handlers, but we don’t know without looking through the JavaScript, and we don’t have any assumptions about where those click handlers might reside in our JavaScript.
The “hide” classes we use with jQuery do communicate functionality, but can’t actually accomplish anything functionally. They’re more than likely associated with a CSS class with the display property set to “none”. The Angular ngShow directive allows us to define any number of conditions for the display of the content sections, and like ngClick, we know to look for this functionality in our associated controller.
Given this Angular markup, you might find the associated code in our controller for managing the application’s state:
//Defaults, page load $scope.state = { blogCompose: true, blogPreview: false, blogPost: false }; //Manage state per button $scope.composeBtn = function () { $scope.state.blogCompose = true; $scope.state.blogPreview = false; $scope.state.blogPost = false; }; $scope.previewBtn = function () { $scope.state.blogCompose = false; $scope.state.blogPreview = true; $scope.state.blogPost = false; }; $scope.postBtn = function () { $scope.state.blogCompose = false; $scope.state.blogPreview = false; $scope.state.blogPost = true; };
Here, we set the default state so the “compose” content is shown. This is what will be displayed when the app initially loads. We then manage the display of the content through functions assigned to the buttons. The “magic” here is that Angular handles assigning the functions to the buttons, and the state of the app. When we set the state property to either true or false, Angular knows to show or hide the associated content, via the ng-show directive in our markup.
The real beauty here is that we as developers aren’t responsible for parsing the DOM and making sure we have the right buttons or content sections based on the DOM structure. There’s no conditional logic in our JavaScript; we define a few variables and functions, and Angular takes care of the rest.
This is a very simple example, but you can imagine how it can make things easier as the size of your app scales and you have multiple developers changing your HTML markup. It not only makes it less error-prone, but its easier to understand what’s going on simply by reading the HTML or JavaScript, without having to actually run the app and interact with it.
I’ve created a functional JSFiddle demonstrating the AngularJS version, if you’re interested in seeing it in action: http://jsfiddle.net/xna5odq7/1/