JavaScript & AngularJS Modules – Implementation Techniques and Structure

angularjsdesign-patternsjavascriptmvcprogramming practices

So Im building an app and I'm trying to implement the structure of the app so that its robust and scalable in future.

My app is mostly divided into JavaScript Modules (revealing pattern):

// filter.js file
//My javascript module structure
var filter = (function(){

var _privateMethod = function(){
}

var publicMethod = function(){
    _privateMethod();
}

return{
   publicMethod: publicMethod
}
});

Now the main issue is that I want to use AngularJS within my app too and AngularJs has its own angular modules like so:

// app.js file
var myApp =  angular.module('myApp',[]);
myApp.controller('myController', function($scope){

});

The way I have planned my app developed is:

  1. All HTTP calls to the server will be done within my JavaScript module or to be more clear within my "filter.js" file (see above)
  2. Returned data from the HTTP calls to server will then be sent back to angularjs controller or in my case the "app.js" file (see above) and that controller/app.js file will be responsible for updating my view/html file.

The reason for doing this is so that all my functions that connect with server and handle data are within a private scope (the filter.js module) and no one should have access to those functions as they are being kept out of the global scope.

But to pass data from my filter.js file to my Angular app.js controller I had to use callbacks.

Below is the complete code of my above described scenario:

filter.js

var filter = (function(){

// public method
var testMethod = function(callback){
  loadDoc(function(dta){
    alert(dta);
    callback(dta);
 })
};

// private Method
function loadDoc(callback) {
  // Create the XHR object to do GET to /data resource  
  var xhr = new XMLHttpRequest();
  var dta;
  xhr.open("GET","url",true);

  // register the event handler
  xhr.addEventListener('load',function(){
  if(xhr.status === 200){
       //alert("We got data: " + xhr.response);
        dta = xhr.response;
        callback(dta);
   }
  },false) 

  // perform the work
  xhr.send();
  }

 return {
     testMethod: testMethod   
  };

})();

app.js:

var myApp = angular.module('myApp',[]);

myApp.controller('myController', function($scope){


$scope.myFunc = function(){

     filter.testMethod(function(dta){
        alert(dta);
        $scope.test = dta;
        console.log($scope.test);
      });
   };
});

You can see from my above code structure that I am using callbacks to send data back to my controllers.

My Question:

  1. My question is that the above descried scenario of how I see my code
    implementation and structure, is that right approach?

  2. Does the implementation adhere to good, clean and correct javascript
    code?

  3. Is there something else I can do to improve my app's code structure?

Best Answer

I would argue that, in the case of AngularJS, utilizing callbacks is a code smell. I say this for the pure simple fact that AngularJS includes the notion of promises that you can leverage for the same use-case, and in fact look extremely similar in practice to the code you have already developed. Consider your test method:

var testMethod = function() {
  return loadDoc();
};

function loadDoc(callback) {
    // Create the XHR object to do GET to /data resource  
    var xhr = new XMLHttpRequest();
    var dta, 
        // This is the deferred action
        defer = $q.defer(); 
    xhr.open("GET","url",true);

    // register the event handler
    xhr.addEventListener('load',function() {
        if(xhr.status === 200) {
            // We have a response, tell those who are waiting
            // about it
            defer.resolve(xhr.response);
        }

    }, false) ;

    // perform the work
    xhr.send();

    return defer.promise;
}

The calling code could look like this:

testMethod().then(function(data) {
    console.log('data', data);
});

Not only that, but the calling code could also look like this:

function callTheTestMethod() {
    return testMethod().then(function(data) {
        console.log('data', data);

        return data;
    });
}

And the code calling that code could look like this:

callTheTestMethod().then(function(data) {
    console.log('I have data here, too', data);
});

Another benefit to using promises is that they already have a built-in way to handle failures:

if(xhr.status === 200) {
    defer.resolve(xhr.response);
} else if(xhr.status > 400 && xhr.status < 600) {
    defer.reject();
}

Just glancing at your code, it looks like your callback is actually being called twice, and to me looks like a bug. Also, on a side note, I don't think you should be instantiating your own version of XMLHttpRequest here vs just using the Angular $http service, but perhaps you have your reasons.

I would suggest this approach: Build an AngularJS service that uses the built in $http service to abstract away your API Calls, something like this:

angular.module('my.module', [])
    .service('TestService', [
        '$http',
        function($http) {
            return {
                testMethod: testMethod
            };

            function testMethod() {
                // Just a simple example to illustrate private 
                // method usage..
                var data = privateMethod();
                return $http.get('url/' + data);
            }

            // Since this function isn't returned in the object above, only
            // code that exists in this scope can actually reference it
            function privateMethod() {
                return '3';
            }
        }
    ])
;

You can consume this service in a controller (or directive, or another service) in this fashion:

angular.module('app.module', ['my.module'])
    .controller('TestController', [
        'TestService',
        function(TestService) {

            activate(); // Prepare for Angular 2.0!

            function activate() {
                TestService.testMethod().then(
                    function(data) {
                        console.log('I still have data', data);
                    },
                    function() {
                        console.log('something went wrong...');
                    }
                );
            }
        }
   ])
;
Related Topic