Codementor Events

Angular/Node: Building a Command Line Tool to Generate Projects Part 1

Published Feb 27, 2015Last updated Mar 28, 2017

Using Angular Server-Side

So I know what you're thinking:

"What's this about? Angular is a front-end framework, why would you need it on the back-end?"

I have come to really appreciate the structure and organization imposed on projects with Angular's use of services, controllers and directives. I like Angular so much, I wanted to extend that structure onto my other projects, as well as my server code. I really feel that having such a firm base to start from is a huge help in getting projects moving. So, I decided to figure out how to get an Angular app bootstrapped and working from the command line, and since there is quite a bit of necessary code required for the different Angular pieces to work correctly, I figured a great place to start with this is a command line tool to generate the boilerplate required to bootstrap an angular app, from the command line or the browser.

What Does Angular Need to Start

When Angular gets running, it wants a window object and a document object available, so to provide these quickly and transparently, we will use the Node.js benv (browser environment) module. We'll start by creating a project directory and installing benv:

$ mkdir PROJECT_NAME
$ cd PROJECT_NAME
$ npm install benv

Then, create a file for the bootstrap code. I called mine: nodeNg.js.
Use any editor your comfortable with—I happen to like vim—but you can use anything you'd like

$ vim nodeNg.js

and add this to the file to get started:

"use strict";
var benv = require('benv');

This pulls in the module that will create our browser environment Angular will use.

Now, this next bit is a bit of personal preference, but I chose to wrap the entire bootstrap process in a function that will take another function as an argument. The first function will do the bootstrapping and will contain the Angular app we can use, and the function we pass will be a basic anonymous function we will wrap our app in. So, after Angular is ready, we will call our passed in function that is inside the first function, and that will execute the code for our app. I know what I wrote was a mouthful, so lets take a look at what I mean.

We start by writing the function that will do the bootstrapping and take our app function as its argument:

var use_angular = function(callback){
    benv.setup(function(){
        benv.expose({
            angular:benv.require('./angular.js','angular')            
      });
    return callback();    
  });
};

So this looks complex, but really we're doing almost nothing, let's go line by line:

  • Line 1 is just declaring the function as a variable
  • Line 2 - 4 is where the magic really is,
    • On 2 we call the setup function of the benv module, this creates the window and document objects that angular will wrap to create its $window and $document services. inside this function definition we can add other things to our environment by calling benv.expose, the first argument will be an object, the values will become available and we can access them by using the keys we provide as variables, ie: calling benv.expose({myVar:55}) would make myVar equal to 55.
    • Line 3 calls benv.expose with {angular:benv.require('./angular.js','angular')} as its argument. which as i stated before adds angular as a variable in our environment, and it gives it the value of the return of a call to another benv function require, this is basically equivalent to adding a script tag to our html document .
    • Finally after calling benv.expose we can use angular so we return our passed in function as we call it, efficiently returning the return value of our passed in anonymous function, which if you recall will be our angular app.

Now, we just need to call our new function with an anonymous function that will pull in our app to run as its argument.

use_angular(function(){
    var path = require('path');
    require(path.join(path.resolve('./'),process.argv[2]))();
});

This looks odd, I know, but all we're doing here is requiring a file at a path given on the command line, and joining it with our current directory to be sure we required the correct file. Then, we're going to export a function from that file, so we'll go ahead and call it as we required it, since thats why it exists: to be executed.

So We're Done Bootstrapping Angular?

Basically. In fact, using this setup we can go ahead and start writing our command line tool with Angular, but I've already made that mistake. We'll learn from that and take a longer look at our bootstrapping function.

Like I said, we could continue, things would work, but here are the issues we would come across:

  • When we load items from the Angular world (ie: our factories, services and controllers), we would need to create an injector object to load them
  • This object has an uncomfortable API, because its not really meant for general use

To abstract away at least the creation of the injector object, which requires us to pass in the modules it has at its disposal, we can write a function, something like:

var ng_load = function(name){
    return angular.injector(['ng']).get(name);
};

Now this would work, but wouldn't allow us to use our own services and controllers. It would only allow us to use Angular's because we're only passing in the angular 'ng' module. If we want the ability to pass in our own modules, or other 3rd party modules, we would need to change it a bit. Also while we do this we'll need to keep in mind that Angular will spit out an error if we happen to inject any module more than once:

var ng_load = function(name,extraModules){
    var mods = ['ng'];
    if(extraModules && extraModules.length){
        angular.forEach(extraModules,function(itm){
            mods.indexOf(itm) === -1 ? mods.push(itm):false;
    });
  }
    return angular.injector(mods).get(name);
};

Ok. Now we can pass in any module we like, where it will inject them and pull whatever we'd like from the environment for us to use. But theres another issue. If we wanted to grab 2 services from a module named service.mod, we would need to add service.mod to every call of ng_load, i.e.:

var serviceA = ng_load('serviceA',['service.mod']);
var serviceB = ng_load('serviceB',['service.mod']);

When all we should need to do is:

var serviceA = ng_load('serviceA',['service.mod']);
var serviceB = ng_load('serviceB');
var serviceC = ng_load('serviceC');

And I know it doesn't seem like much in this context, but trust me. Having to pass in the module every time you need it can really become a pain, and if you ever happen to forget to add it, it becomes an easy way for bugs to creep in.

So, I will once again refactor our function to cache the modules we inject so we'll only need to add them once, then we can just ask for things from it after. As long as we added it to the first service request, we can get other services just by name. Before now I've been writing this as a stand alone function, but really it needs to be defined in the benv.setup call as a property on the object we pass to benv.expose (our module cache is going to live there as well).

Let's just start over and integrate it into our original setup:

// first we get our environment and define a function to use it
"use strict";
var benv = require('benv');

var use_angular = function(callback){
    benv.setup(function(){
        benv.expose({
                         angular:benv.require('./angular.js','angular'),
                         ng_mod_cache:[],
                         cache_mods : function(mods){
                               angular.forEach(mods,function(itm){
                                      ng_mod_cache.push(itm);
                             });
                        },
                       ng_load : function(name,extraModules){
                                 var mods = (ng_mod_cache && ng_mod_cache.length) ? ng_mod_cache : ['ng'];
                                 if(extraModules && extraModules.length){
                                          angular.forEach(extraModules,function(itm){
                                               mods.indexOf(itm) === -1 ? mods.push(itm):false;
                                            });
                                      }
                                      cache_mods(mods);
                                      return angular.injector(mods).get(name);
                                 }
                          });
                  return callback();
              });
};

// then we pass an anonymous function that calls a function from a file
// the file we point this script at should contain our angular code
use_angular(function(){
    var path = require('path');
    require(path.join(path.resolve('./'),process.argv[2]))();
});

Bootstrapping the Angular framework

To actually make sure our app runs and instantiates our controllers and services, as well as to access the dependency injector, we need to bootstrap our app. we can add a simple little function to do this.

    ng_bootstrap:function(app){
         var _app = angular.isString(app) ? angular.module(app) : app;
        _app.config(function($locationProvider){
            $locationProvider.html5Mode(false);
        });
        return angular.bootstrap(angular.element(document.createElement('body')),[_app.name]);
    }

now using that we can write a little function to grab our dependency injector from angular:

    ng_injector:function(app){
        return ng_bootstrap(app).invoke;
    }

and the last issue we may find that we run into will be executing nodes async functions using angular, this is because angular uses a custom version of the Q module for promises, node uses a different method by default. So we will write a function that will take node async functions as its first argument, and will return a function that uses $q angulars wrapper around the Q library:

    promisify:function(asyncFunc,context){
        var $q = ng_load('$q');
        return function(){
            var defer = $q.defer(),
                   args = Array.prototype.slice.call(arguments);
            args.push(function(err,val){
                 if(err !== null){
                    defer.reject(err);
                }else{
                    defer.resolve(val);
               }
            });
            asyncFunc.apply(context || {},args);
            return defer.promise;
        };
    }

Now we can start writing our command line tool. Now we are ready.

To be continued.....

for a copy of the code in this blog and a small example see the github repo

Discover and read more posts from Kyle J. Roux
get started