Creating a modular angularJS application using browserify, gulp and less

Complete source on github.

Exploration

I’m a big fan of not using the latest and greatest right out of the gate. I prefer to let other people work through the initial bugs and get a few examples documented before I jump in. It’s worked quite well for me thus far.

That said, I recently came across the need to set up a new angular project (client need). And I do like to explore new frameworks/methodologies/techniques/etc. once they’ve had some time to mature. Having not yet used browserify, this seemed like a good enough reason as any to give it a go. I have previously used requireJs in my angular projects, so I thought this would be a good opportunity for comparison.

After looking around for examples I quickly realized that, as to be expected, there are quite a few different ways to make this work… And, I didn’t really like any of them. I had some concerns that browserify would abstract the angular code too much. I wanted things to stay as traditionally angularish as possible. The eventual handoff will be to angular developers who may or may not have experience with browserify – so I want to keep things as simple as possible. Since none of the examples I found fit the bill, here’s what I came up with.

My Solution

I really like to group my files by module; not by file type. ie, I don’t have a single monolithic services.js file and a directives.js file, etc. That works fine on a small project, but I find that those files quickly become overburdened with too many things. Working on a team (as is usually the case with me) is much easier with more smaller files. Managing in source control is easier with more smaller files. And I think semantically, it makes more sense to group files by component/module than by type. Consider the following folder structure for a navbar component.

folder structure Additional components would live as siblings to the navbar component in their own
component/component-name
folder.

Let’s take a look at how we instantiate our angular app, and load in the navbar component. Again, I am placing a great deal of priority on keeping angular┬ábehaving like angular. I will utilize dependency injection(DI) for loading in all dependencies. And my file structure would work almost exactly the same without browserify.

main.js

var angular = require('angular');
var config = require('./config');
var navbar = require('./components/navbar/navbar.js');

angular.module('app', [
  'templates', // container for templates from ngHtml2Js gulp task
  'app.navbar'
])
.constant('configUrls', config.urls); // make urls accessible in all modules
As you can see, the only difference between this file and one without browserify is that I’m making my JS files accessible through require statements, intsead of <script> tags in my html file. My navbar component is defined as
app.navbar
in navbar.js. We just use DI to make it accessible to the app. Let’s continue a bit deeper.

navbar.js

var navbarDirective = require('./navbar-directive');
var navbarTabDirective = require('./navbar-directive.tab');
var navbarService = require('./navbar-service');

angular.module('app.navbar', [
  'app.navbar.service',
  'app.navbar.directive',
  'app.navbar.directive.tab'
]);

Again, you’ll notice that this file also looks just like any regular angular module definition. We define our component here as ‘app.navbar’ and load in all the necessary dependencies for this component using DI. Let’s keep going.

navbar-service.js

angular.module('app.navbar.service', [])
.factory('NavbarService', function($http, configUrls) {
  var serviceMethods = {
    getTabsData: function(success, error) {
      $http.get(configUrls.tabs)
      .success(function(data, status, headers, config) {
        success(data);
      })
      .error(function(data, status, headers, config) {
        error(status);
      });
    }
  };

  return serviceMethods;
});
Undoubtably, you’re noticing a trend here. This service definition would work in exactly the same way, even without browserify. We have no special
module.exports
or any strange return values anywhere. Everything looks just like it would without browserify. Let’s take a look at a directive now.

navbar-directive.js

require('./navbar-template.ngt');

angular.module('app.navbar.directive', ['app.navbar.service'])
.directive('rexNavbar', function(NavbarService) {

  return {
    restrict: 'E',
    templateUrl: 'navbar-template.ngt',
    controller: function($scope, $element) {
      var activeId = 0;
      NavbarService.getTabsData(
        // success
        function(data) {
          $scope.tabs = data;
        },

        // error
        function(status) {
          console.error('error loading tabs data', status);
        });

      $scope.isActive = function(tab) {
        return tab.id === activeId;
      };

      this.setActive = function(tab) {
        activeId = tab.id;
      };
    }
  }
});

Here I’m using the browserify-ngHtml2Js transform to add my templates to my browserify bundle and pull them into angular’s $templateCache. Although completely optional, this allows me to reference my template files via url as if they were separate requests, but without the need to make the request. Perhaps I’ll do an additional post on that if there’s interest. Let me know. Let’s look at the sub-directive and the template, for the sake of completeness.

navbar-directive.tab.js

angular.module('app.navbar.directive.tab', [])
.directive('rexNavbarTab', function() {

  return {
    restrict: 'A',
    scope: {
      tab: '=rexNavbarTab'
    },
    require: '^rexNavbar',
    link: function(scope, element, attrs, parentController) {
      element.on('click', function(event) {
        scope.$apply(function() {
          parentController.setActive(scope.tab);
        });
      });
    }
  }
});

Nothing special about this file. We’re requiring the parent tab directive to get access to the parent scope, but we’re not doing anything special to account for browserify.

navbar-template.ngt


<ul class="nav nav-tabs">

<li role="presentation" ng-class="{active: isActive(tab)}" ng-repeat="tab in tabs" rex-navbar-tab="tab"><a href="#">{{tab.label}}</a></li>

</ul>

Finally, nothing special here either.

That’s all there really is to it.

Check out the full source-code to the project, including my gulp setup and build steps on github.

Also, as I haven’t used browserify before, let me know if you can think of any reasons not to do things this way!

Leave a Reply

Your email address will not be published. Required fields are marked *