JavaScript Data Binding with AngularJS Part II – Binding a View to a Controller/ViewModel

 

In my previous post I showed how the AngularJS framework could be used to bind data into a webpage. By using special directives such as ng-model combined with moustache data binding syntax (i.e. {{ name }}) it’s easy to get started binding data into a page. In this post I’ll walk you through the process of creating a controller that stores data and provides functions that can be called from a page. The controller will reference and rely on a special object named $scope that acts as a type of “View Model” (a common term to those who use the MVVM pattern) and is responsible for monitoring changes to properties. Let’s take a look at the steps required to create an AngularJS controller and bind its properties into a page’s controls.

 

Step 1: Add the ng-app Directive and the AngularJS Script

To get started you’ll need to add the ng-app directive into the page (adding it on the root <html> tag makes AngularJS functionality available throughout the entire page) and add a script reference to AngularJS. You can grab it directly from the CDN location at https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js if you want – just change the version to the latest as needed.


<!DOCTYPE html>
<html ng-app>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js"></script>
    </head>
    <body>




...
</body>
</
html>

 

Step 2: Create a Controller/ViewModel Script


Add a new JavaScript file named peopleCtrl.js into the site with the following code:

 

function PeopleCtrl($scope)  {
    $scope.people = [
        {firstName: 'John', lastName: 'Doe', address: {city: 'Chandler', state: 'AZ', zip: 85248}},
        {firstName: 'Jane', lastName: 'Doe', address: {city: 'Chandler', state: 'AZ', zip: 85248}},
        {firstName: 'Johnny', lastName: 'Doe', address: {city: 'Phoenix', state: 'AZ', zip: 85003}}
    ];

    $scope.addNew = function() {
        $scope.people.push({ firstName: $scope.firstName, lastName: $scope.lastName,
                             address: {city: $scope.city, state: $scope.state, zip: $scope.zip} });
    }
}


This example defines a people property that has an array of objects assigned to it as well as a function named addNew() that can be used to add additional people to the array. The property will be bound into the view. In looking through the code you’ll note that a $scope parameter is passed into PeopleCtrl. This parameter acts as the “glue” between the view (the webpage) and the controller and handles watching the model for changes (think of it as the view model). Any model changes that are detected are propagated back to the view or to the controller depending on which side initiated the model change. The $scope parameter is automatically passed by AngularJS into the controller.

The $scope parameter is used inside of the controller to define properties and functions. This allows the controller to be completely isolated from the view and the DOM which is necessary if you’d like to do unit testing. Using $scope also allows properties such as people in this example to automatically be monitored for changes without having to write any quirky code inside of it. Any property that needs to be monitored for changes can be associated with $scope to have its changes pushed down to the view. If the property is bound to a control such as a textbox in the view, any changes made by the end user will cause the appropriate controller property to automatically be updated. This provides an excellent mechanism for writing data-oriented code as opposed to control-oriented code.

 

Step 3: Define the Controller/ViewModel in the Page

To associate the controller with the view the ng-controller directive can be added into the HTML as shown next.


Note that AngularJS directives can be prefixed with data- if you prefer which can be helpful when running the code against validators that don’t like custom attributes and instead require that any custom attributes be prefixed with data-. For example, data-ng-controller=”PeopleCtrl” is perfectly acceptable with AngularJS as is data-ng-app.

 

<div ng-controller="PeopleCtrl">        
    <div id="peopleContainer">



</
div> <div> <h4>Add New Person:</h4> <table style="width: 300px"> <tr> <td style="width: 30%;">First Name:</td> <td style="width: 70%;"><input type="text" ng-model="firstName" /></td> </tr> <tr> <td style="width: 30%;">Last Name:</td> <td style="width: 70%;"><input type="text" ng-model="lastName" /></td> </tr> <tr> <td style="width: 30%;">City:</td> <td style="width: 70%;"><input type="text" ng-model="city" /></td> </tr> <tr> <td style="width: 30%;">State:</td> <td style="width: 70%;"><input type="text" ng-model="state" /></td> </tr> <tr> <td style="width: 30%;">Zip:</td> <td style="width: 70%;"><input type="text" ng-model="zip" /></td> </tr> <tr> <td colspan="2"> <button>Add</button> </td> </tr> </table> </div> </div>


This code handles referencing the controller as well as some textboxes and a button that the user can use to add a new person. Each textbox has an ng-model attribute on it to allow the values to easily be accessed in the controller. You’ll see how the textbox values are accessed in a later step.

 

Step 4: Iterate Through the people Property Values


Once the PeopleCtrl is referenced in the view you can add code to iterate through the controller’s people property. Iteration can be accomplished using AngularJS’s ng-repeat directive as shown next.

 

<div id="peopleContainer">
    <ul>
        <li ng-repeat="person in people">
            <span class="bold">{{person.firstName}} {{person.lastName}}</span>
            <br />
            {{person.address.city}}, {{person.address.state}}&nbsp;&nbsp;{{person.address.zip}}
        </li>
    </ul>
</div>


The ng-repeat directive iterates through each object in the people property of the controller and assigns each object to a variable named person. Each person object is then bound into the view using the mustache syntax (for example, {{person.lastName}} binds the lastName property of each person into the view) resulting in multiple <li> elements being created dynamically.

 

Step 5: Calling Controller Functions


The controller has a function named addNew() that needs to be called when the Add button is clicked. To wire up the button to the controller function the ng-click directive can be used:

<button ng-click="addNew()">Add</button>

 

When the button is clicked the values from the textboxes are accessed in the controller through the $scope and the new person is added to the people property array. This is made possible due to the presence of the ng-model directive on each each textbox.

 

$scope.addNew = function() {
    $scope.people.push({ firstName: $scope.firstName, lastName: $scope.lastName,
                            address: {city: $scope.city, state: $scope.state, zip: $scope.zip} });
}



In this example we’re accessing each textbox value directly through the $scope variable. It’s important to note that no code is written to manually find textbox controls by their ID (something I call control-oriented programming) and then extract the value. Instead, we’re relying on JavaScript data binding which significantly reduces the amount of code that has to be written.

While this works well, it can be cleaned up a little by having AngularJS create a JavaScript object literal for us automatically that is then added into the controller’s people collection. By prefixing the ng-model directive with person. an object literal will be created which can then be added more easily into $scope.people in the controller. Here’s an example of how the modified HTML looks – notice the change to each ng-model directive on the different textboxes.

<table style="width: 300px">
    <tr>
        <td style="width: 30%;">First Name:</td>
        <td style="width: 70%;"><input type="text" ng-model="person.firstName" /></td>
    </tr>
    <tr>
        <td style="width: 30%;">Last Name:</td>
        <td style="width: 70%;"><input type="text" ng-model="person.lastName" /></td>
    </tr>
    <tr>
        <td style="width: 30%;">City:</td>
        <td style="width: 70%;"><input type="text" ng-model="person.address.city" /></td>
    </tr>
    <tr>
        <td style="width: 30%;">State:</td>
        <td style="width: 70%;"><input type="text" ng-model="person.address.state" /></td>
    </tr>
    <tr>
        <td style="width: 30%;">Zip:</td>
        <td style="width: 70%;"><input type="text" ng-model="person.address.zip" /></td>
    </tr>
    <tr>
        <td colspan="2">
            <button data-ng-click="addNew(person)">Add</button>
        </td>
    </tr>
</table>

 

This will create the following object literal behind the scenes automatically:

 

{
    firstName: '...',
    lastName: '...'
    address: {
        street: '...',
        city: '...',
        zip: '...'
    }
}



Code in the addNew() function can be reduced quite a bit by having the object literal available. This is nice especially when you consider that with a control-oriented approach you’d have to write code to access each control by ID and grab the value. Using this approach, addNew() in the webpage becomes addNew(person) and the function in the controller changes to the following:

 

$scope.addNew = function(person) {
    $scope.people.push(person);
    $scope.person = {}; //Clear out person object
}



A live example of the demo code in action is shown next. Click the Result tab to run it.


 

Conclusion

In this post you’ve seen how an AngularJS controller/view model can be created and how its properties can be bound to a view. By using a data-oriented framework like AngularJS (see my previous post for a list of additional frameworks) you can minimize the amount of code that has to be written and simplify the overall application. In future posts I’ll keep digging into the AngularJS framework and provide additional examples of some of the great features it provides.


Pluralsight Subscribers: Would you be interested in seeing a course on AngularJS? Help me out by completing the simple 1-question survey here. If there’s enough interest I’ll add it to my list of future courses to consider building.



Pluralsight Courses:

comments powered by Disqus

11 Comments

  • Thanks Igor...really appreciate the feedback. For those reading through the comments, Igor is one of the main guys behind AngularJS so what he says about it matters.

    Now that you mention it (and I think about it more) $scope is definitely the ViewModel since it handles change notifications and things. Hadn't thought of it as the ViewModel until you mentioned it since its a different way of doing things (I like it though) but I'll get that updated along with the other things you mention. A lot of it is pure "demo" code of course but I agree with your suggestions.

    Dan

  • How do you compare it with KnockoutJs?

  • Jignesh:

    Both KnockoutJS and AngularJS provide excellent data binding functionality so I think it comes down to personal choice if that's specifically what you're after. However, AngularJS is more of a client-side framework that's capable of much more than just data binding and templates. I list many of the main features in this post if you haven't seen it yet:

    http://weblogs.asp.net/dwahlin/archive/2012/07/29/javascript-data-binding-with-angularjs-getting-started.aspx

    Dan

  • Thanks Dan for pointing to that blog. AngularJs is looking very promising. I will try it tomorrow.

  • Great article yet again Dan! This Angular stuff really excites me and Ive been trying to incorporate it into my current project as it seems like the cure for my main problem (not using data-oriented design in the first place). It would be great if you could provide an example of more UI type interactivity with data (and not cookie-cutter CMS type stuff). Like perhaps using Angular with a data-bound UI that reacts to user manipulation withing being a direct 1:1 with a form to the data object. (if that eve made any sense?!)

  • Hi Dan

    I've started having a look at Angular, read through the tutorial on GitHub, but what I can't find is a simple answer to this question. How do I return a computed value from two inputs, something which is fairly simple in Knockout.

    $scope.fullname = function(){
    return $scope.firstname + $scope.lastname;
    };

    All that does however is return a function. I can't seem to find any clues online so any advice would be great.

    Thanks
    Jon

  • Jon,

    That should work actually but you'll need to use {{ fullname() }} in your template rather than {{ fullname }}. If it turns out there's a better way that I just haven't seen yet I'll let you know here.

    Dan

  • JasonK:

    Glad you enjoyed the post. I'm hoping to build a more involved sample to blog about once I get past the starter stuff.

    Dan

  • Very nice write-up!

    There's a slight bug in that the $scope.person remains data-bound after adding it, causing textbox changes to be reflected in the previously added item. And if you click Add more than once, it's the same object instance being rendered multiple times. I believe this can be fixed by $scope.person = {}; at the end of the addNew function.

    I'm really looking forward to your next posts on AngularJS!

  • Oran:

    Great catch! Thanks for reporting that because you're right on. I've updated the code to account for that.

    Dan

  • Nice post Dan, Thanks. ... after reading the comments, most of my doubts are now cleared :-)

Comments have been disabled for this content.