Creating An Application With Sails.js, Angular.js and Require.js Part 2 – Adding Dependencies

by Tyson Cadenhead on August 5, 2014

The modern web is always changing, and this article is more than two years old.

Tyson Cadenhead continues an article series about building an application with Sails.js, Angular.js and Require.js. This installment dives into adding dependencies to your project. You can find Part 1 here.

By Tyson Cadenhead

As I promised at the beginning of this series, we are going to be using RequireJS to manage our client-side modules. If you haven’t used RequireJS or a similar AMD loader before, let me just say that you are really missing out. RequireJS lets you write your code in small modules that only expose the parts of the API that you want to show and as an added bonus, it uses JavaScript to load your JavaScript modules.

We Can Use Bower, Right?

Before we get started, I wanted to give a quick rant about why using bower in a Sails project hasn’t worked very well for me. Bower is a package manager for client-side JavaScript much like NPM is for server-side JavaScript. Typically, I would use bower to manage my dependencies for any project. The issue is that Sails doesn’t run your assets right out of the folder you create them in. Instead, it packages them up with Grunt and runs them out of .tmp/public. That approach is really helpful if you have a compilation step such as converting SASS or LESS stylesheets to CSS or minifying your JavaScript. The issue is that it if you have a ton of files in your assets directory, as is often the case when you have installed bower packages, you will inevitably either get errors complaining about the number of files being processed or it will be painfully slow. Either way, I’ve come to the painful conclusion that using Bower with Sails is more trouble than it is worth.

My compromise is typically to load my vendor files from a CDN whenever there is one instead of junking up my project with code that I didn’t write.

RequireJS

RequireJS

There is actually some debate about whether or not RequireJS has value in an Angular project. Angular has a dependency injection concept that makes running the code itself pretty modular. However, it doesn’t load specific files for you out of the box. There are a few ways to leverage the Angular library to load your files for you. To me, though, it seems simpler to follow a set standard like what RequireJS provides by closely following the AMD spec. That way, developers in the future will have a clearer idea of where the code is coming from and how it is being loaded.

So, let’s get RequireJS working.

Let’s open the layout.ejs file and add a line to instantiate our Angular application. We will replace the body tag with this:

<body ng-app="todoIt">

The name of the app can be whatever you want to call your application, but just be aware that you will be referencing it later in your JavaScript.

Next, we need to go to the bottom of the layout.ejs file and replace all of the JavaScript files with this:

<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.11/require.min.js" data-main="/js/main"></script>

That will load the require.min.js file from a CDN and then immediately require your /js/main.js file. Don’t have a main.js file yet? No problem. Let’s go ahead and build that.


window.name = 'NG_DEFER_BOOTSTRAP!';

require.config({
  'baseUrl': '/js',
  'paths': {
    'angular': '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular',
    'jquery': '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min',
    'bootstrap': '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min'
  },
  'shim': {
    'angular': {
      'exports': 'angular'
    },
    'bootstrap': {
      'deps': ['jquery']
    },
    'socket.io': {
      'exports': 'io'
    },
    'sails.io': {
      'deps': ['socket.io'],
      'exports': 'io'
    }
  }
});

require([
  'angular',
  'app',
  'bootstrap'
], function (angular, app) {

  angular.element(document.getElementsByTagName('html')[0]);

  angular.element().ready(function() {
    angular.resumeBootstrap([app.name]);
  });

});

Notice at the top where we set the window.name to “NG_DEFER_BOOTSTRAP!”. That will keep angular from initializing until we tell it to. Near the bottom after everything is loaded, we call angular.resumeBootstrap(). That is where angular picks back up and finishes initializing.

The socket.io and sails.io files that come with Sails are not wrapped as AMD modules, so we need to shim them both and export the io variable.

Otherwise, the main.js is pretty standard for a requireJS config file.

We will also need to update the app.js file to this:


define([
  'angular',
  'sails.io'
], function (angular, io) {

  var socket = io.connect(), app;

  socket.on('connect', function socketConnected() {
    console.log('Socket is now connected');
  });

  app = angular.module('todoIt', []);

  return app;

});

We are leaving the socket.io connection. We’ll be working with that later on. For now, we mostly just want to return our app as a new angular module. Notice that the name “todoIt” is the same as the ng-app that we added to our layout.ejs file.

Now the main.js file will take the app that is returned from app.js and create an angular application.

When you look at your console at this point, you should get a message that says:

Socket is now connected

If you see that and don’t get any errors, you have successfully added an angular application on top of Sails. Stay tuned next time when we make the application actually do something.

If you want to see the code in action, check out the example repository here.

This article was previously published on Tyson’s blog here.

Tyson is a (JavaScript Engineer)[http://appendto.com/team/tyson-cadenhead/] at (appendTo)[http://appendto.com].

6 comments"

  1. Sander says:

    hey, on your bower problem: have you tried letting bower write the files directly into the other folder? You can configure bower to use a different location (http://bower.io/docs/config/#directory)

    1. Tyson Cadenhead says:

      The entire folder gets overwritten, so that doesn’t work. However, I’ve discovered that if you write to the /assets/linker/ directory, it doesn’t get updated every time a file changes, so that will work. In a project I’ve been working on, I put it in /assets/linker/bower_components and it is working perfectly.

  2. Nick says:

    Hey there!

    I actually plan on starting a project using Sails, Angular and Require all in one, really excited to see this! As of right now, I only have Sails and I was hoping to include REQUIRE only. I have copied in the require CDN, however the main file seems to have a lot of stuff and I’m not sure what is needed for Require only. Any chance you can help me understand what is needed for just require?

    Thanks in advanced!!

  3. Johan says:

    Hello!

    I’m trying to run this on Sails 0.11.0 without success. The file socket.io.js has been integrated into sails.io.js and can therefore not be found, with the result that sails.io fails to load.

    So I removed the shim for socket.io and it’s dependency in sails.io. I also added ‘sails.io’: ‘./dependencies/sails.io’ to paths as this is the new location. This results in the console printing the error: sails.io.js requires a socket.io client, but io was not passed in.

    I’ve also tried to use a separate socket.io.js by adding ‘socket.io’: ‘//cdn.socket.io/socket.io-1.2.1’ to paths but I’ve had no more progress.

  4. Colm says:

    Hi there. I have done everything you said, but in the console after running sails lift, I get the
    ” Uncaught Error: Mismatched anonymous define() module: function (angular, io) {var socket = io.connect(), app;
    socket.on(‘connect’, function socketConnected() {
    console.log(‘Socket is now connected’);
    });
    app = angular.module(‘todoIt’, []);
    return app;
    }”
    And I’m being pointed to line 5 of my main.js file, which starts with
    “require.config({ . . .”
    Any help would be greatly appreciated.

Leave a Reply

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