Thursday, April 25, 2013

Lesson 3: Angular Tutorial and Journey - Forms & CRUD

In Lesson 2 we created a basic structure for our Device Management application. We learned the following:
  1. How to modularize our application using Angular Modules & we created an angular Service which can encapsulate our backend device data. This service can later be extended to retrieve data from the backend services
  2. How to handle different views within the application using Angular Routes. We were able to split our main HTML template into multiple views and assemble at runtime using $routeProvider. We injected the controller and views to the route. 
  3. We setup nginx server to start serving our application behind a webserver
  4. Most importantly, we are also learning how to effectively use github and git

During this part of the journey, lets try to manage our device list. Currently, we have a static list which is stored as an array within a service module. I would like to handle this in a couple of steps... progressively.. [ You can access all the code in this lesson by doing git checkout v1.3.1]

  1. When there are no items in the list, it should display "You have no devices, would you like to add one". This will provide an entry point into adding a device. 
  2. Implement a form which will add a device to our list (although not persisted in a db at this time)
Step 1: Show/Hide a message using ng-show & ng-hide. Angular templating language does not have an if-else conditional keywords. This is mainly due to the fact that angular uses DOM as its templating engine. But ng-show, ng-hide & ng-switch provide a mechanism to do conditional logic. 

In our app, we want to show the message "You have no devices setup. Would you like to add?" at the right time.

Here is simple code addition to do that: (Note:- we will eliminate the need for this later. This is to just show the use of directives ng-hide & ng-show for conditional logic)


Step 2:
This is a bit more involved. If we want to add a form, we have to do a couple of things. First we need to create a HTML partial template, lets call it addDevice.html which will hold our form, then add a .RouteProvider to handle the URL (http://localhost/cotd/#/add), then we need to create a addDevice controller which take the model & insert it into our in memory database.

Here is a simple bootstrap template which just shows the form or angularjs partial. It does nothing at the moment. It uses class control-group, control-label & controls to make sure the forms are all aligned properly. One issue that tripped me was that if I did not have "!DOCTYPE html" in my main.html file, the bootstrap forms text input field width was smaller 18px. Once i added then doctype the sizes looked great.



Now lets add angular model hooks. Angular makes it easy to bind form data into our model. Just by adding ng-model into the input fields, these are now available in $scope within our controllers. This reduces a significant amount of coding, in my opinion. In the gist below, I show the form with the model hooks. Pretty simple way to bind input data so its available in our controller.



Now lets take a look at the controller. The goal here is to take the device and add it to our list. The first neat thing is that "device" javascript object is available to us in the controller with all the data filled in. You can log to the console to see whats in there.

It does not have the id element, so we need to add it, which we will do once I explain how in angularjs the data can being shared between controllers using services.

As we talked about before, Services in Angularjs allows us to encapsulate common functionality within the app, its a singleton so you know there is only one instantiation of it. We can also inject this into multiple controllers.

I did a little refactoring of our original service. First, I moved the data into a data property, then we add a method called query, which just returns the data, and also create an add function, which will push the newly added data into the array object.

The following gist shows the shared service code:



To wrap up, lets look at our controllers. We have 2 controllers, list & add. They are both injected with $scope & "Devices" shared angularjs service. The angularjs service encapsulates the data model.

One last thing we need to do is to figure out how to get back to the DeviceListing page once we are done adding. We can use the angularjs $location.path construct to redirect to the main page.

Update

For updating device information we can reuse the addDevice.html template. The request flow goes like this a) user selects item to edit from the list b) which sends a request in the format #/edit/:id which will be interpreted by the routeProvider & handed over to the editDeviceController which displays the device information c) on edit we update the deviceInfo using our Devices service abstraction. Lets take it one step at a time

Step 1: Reusing addDevice.html template, we will add a new Edit button. We need to hide the add button & show the edit button. Also, when adding a device, we need to show Add & hide Edit. Lets do this using the ng-show & ng-hide directives. Lets bind this using an addFlag variable attached to our scope. This should toggle Add & Edit buttons depending on the controller. 

AddDevice.html
  
 
                       
 


function addDeviceController($scope, $location, Devices)
{
    $scope.add=true;



function editDeviceController($scope, $location, $routeParams, Devices)
{
    $scope.add=false;

Lets add a route for handling edit and inject the editDeviceController & addDevice.html template.



    when('/edit/:id', {
        controller: editDeviceController,
        templateUrl: 'addDevice.html'
    }).





Lets now implement the editDeviceController. We need to do the following, based on the :id parameter, we get the device from the list & inject into $scope so that 2-way binding takes care of displaying the data in the template. Its important that we need to deep copy the device object into Scope, otherwise you will undefined error at runtime. Lets also add an update function which will be triggred when the user clicks on Edit.


Full implementation of the Edit Controller:
function editDeviceController($scope, $location, $routeParams, Devices)
{
    // get the device based on parameter id
    var device = Devices.query()[$routeParams.id];

    // set the add/edit flag
    $scope.add=false;
 
    // deep copies the selected item into scope
    $scope.device = angular.copy(device);

    $scope.update = function(device){
        if(!device) return;
        console.log("in EditCtrl add");

        Devices.update(device);

        // redirect to main screen
        $location.path('#/');
    }
}


Lets take a look at the update functionality within our Service. In this simple case, we just update an array. We will extend the service in later lessons to communicate with a restful backend. But, it solves our purposes.


    items.update = function(device){
        // find the selected device & update
        items.data.forEach(function(item, i){
            if(item.id == device.id){
                // update              
                items.data[i] = device;
                return;
            }
        });
    }




Conclusion


I am truly amazed at all the functionality that angularjs provides. The amount of code required to accomplish this is very low and that is the promise of angularjs.

So, we completed the following within our Angularjs CRUD model:
1. Create - Add Device is fully functional now
2. Read - List Device is fully functional
3 Update - Next Up
4. Delete - Fully functional

1 comment:

Anonymous said...

Hi ,

It seems that the layout of this page is broken. The code snippets near 'function addDeviceController($scope, $location, Devices)' aren't in code boxes anymore and pieces of the example code seem to be missing

Browser: Chrome