JavaScript Data Binding with AngularJS Part II – Binding a View to a Controller/ViewModel
Related Posts:
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}} {{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: