Tuesday, January 20, 2015

AngularJS Tip 3 - JavaScript Properties in AngularJS

One of the frequent questions that people ask me is how to connect data from the business service to the controller, so it will be easy to bind it to HTML tags.

Option 1:

function UserCtrl(userBL){
    this._bl = userBL;
    this.name    = userBL.name;
    this.address = userBL.address; // address is an object.
}

<div ng-controller="UserCtrl as vm">

    <input type="text" ng-model="vm.name" placeholder="name"><br>

    <input type="text" ng-model="vm.address.street" placeholder="street"><br>

    <input type="text" ng-model="vm.address.house" placeholder="house"><br>    

    <br>

    name : {{vm._bl.name}}<br>
    Address : {{vm._bl.address | json }}<br>
</div>

JavaScript copies by value primitive types, so it copies the value of “userBL.name” to “this.name”. Because of that, the ng-model connects the input value to “this.name” but the “userBl.name” is not updated when the “this.name” is changed.

JavaScript copies by value the reference when it is an object type. Because of that, the “userBL.address” and the “this.address” is the same object, so when the ng-model connects the input value to the “this.address” it also updates the “userBL.address”.

Note: If you assign new object to the “userBL.address”, you lose the binding because the “this.address” points to an old object, meaning the binding stops updating the “userBL.address”.

userBL.address = {strees:’abc’, house: 1};
 
A JavaScript properties came to rescue, see option 2.
 
Option 2:
 
function UserCtrl(userBL){
    this._bl = userBL;
    Object.defineProperties(this,{
       name : {
           get : function(){
               return userBL.name;
           },
           set : function(value){
               userBL.name = value;
           }
       },
       address : {
           get : function(){
               return userBL.address;
           },
           set : function(value){
               userBL.address = value;
           }
       }
    });
}
 
With JavaScript Properties the binding works on “userBL”. Which means no more copy by value of reference. We can use the same approach between a BL and a storage service (userDTO). 
 
Option 3:
 
(function (angular) {
    ‘use strict’;
    //////////////// AngularJS //////////////
    var mi = angular.module(‘myApp’, [])
    .factory(‘userBL’,blFactory)
    .factory(‘userDTO’,dtoFactory)
    .controller(‘UserCtrl’,UserCtrl);
    //////////////// JavaScript //////////////
    function dtoFactory($log){
        return {
            name: ‘bl-name’,
            address: {
                street: ‘abc’,
                house: 47
            }
        };
    }

    function blFactory($log,userDTO){
        var blUser = Object.defineProperties({},{
            name : {
                get: function(){
                    return userDTO.name;
                },
                set : function(value){
                    userDTO.name = value;
                }
            },

            address : {
                get: function(){
                    return userDTO.address;
                },
                set : function(value){
                    userDTO.address = value;
                }
            }
        });

        return blUser;
    }



    function UserCtrl(userBL){
        this._bl = userBL;
        Object.defineProperties(this,{
           name : {
               get : function(){
                   return userBL.name;
               },
               set : function(value){
                   userBL.name = value;
               }
           },

           address : {
               get : function(){
                   return userBL.address;
               },
               set : function(value){
                   userBL.address = value;
               }
           }
        });

    }   

})(angular);
 
 
We can write a helper function for creating JavaScript property more easily.

function createProperty(source,wrapper,property){
    Object.defineProperty(wrapper,property,{
        get: function(){
            return source[property];
        },
        set : function(value){
            source[property] = value;
        }
    })
}
 
Now the controller look like this:
 
function UserCtrl(userBL){
    this._bl = userBL;
    createProperty(userBL,this,’name’);
    createProperty(userBL,this,’address’);
}
 

Note: I wrote the properties on a 'this' object. We can do the same on a $scope object or to write the properties on a “UserCtrl.prototype”. This way we don’t duplicate the get and set functions every time we create a UserCtrl instance.

function UserCtrl (userBL){
    this._bl = userBL;

    createProperty(userBL,UserCtrl.prototype,'name');
    createProperty(userBL,UserCtrl.prototype,'address');
}

Last note: ngModelOptions
AngularJS 1.3 has a new directive called ng-model-options. This directive can bind throw setter and getter.

<input type="text" ng-model="vm.name" ng-model-options="{getterSetter:true}">

See documentations. I believe that working with JavaScript Properties is a better way.


I would be happy to receive feedback J

1 comment: