Couch Potato: Application Module Setup

Stop, Breathe and Look at the Samples

Yes, you were called impatient, hasty and quick if you wanted to look at the samples. Well it was a joke (perhaps a weak one). Or maybe it was deemed important to read the conceptual overview. In any case, the rest of this guide assumes you've looked at the samples. Using Couch Potato is actually pretty easy, and without seeing it in action it may feel more complicated and confusing than it needs to be or is.

So have a look at the source and its readme and see them in action!

Since Couch Potato component definitions run outside of the Angular context, there are two things we need to "do to" the app in order to be able to reach back into it to register components. Let's take them in order of when they occur in the lifecycle of the application.

When the application module is declared, we want to define the registerXXX functions that Couch Potato components use.

The easiest and best way is probably to let Couch Potato do it for us. When you declare your module and its dependencies, ask Couch Potato to configure the app.

Two examples follow. The first assumes you've used <script> tags to load Angular and Couch Potato and the second uses the AMD-style. It's important to realize that the choice does not impact the way Couch Potato components are authored.

Using <script> tags:

  var myApp = angular.module('myApp', ['scs.couch-potato']);

  couchPotato.configureApp(myApp);

Using AMD:

define(['angular','couch-potato'], function(angular, couchPotato) {
  var myApp = angular.module('myApp', ['scs.couch-potato']);

  couchPotato.configureApp(myApp);
});

Pretty easy.

So what just happened? Couch Potato has assigned some functions as variables on the app. Specifically:

It is these functions that the Couch Potato components call to accomplish registration. More on their inner workings below.

The second thing we want to do occurs when the application enters run-time.

myApp.run(['$couchPotato', function($couchPotato) {
  myApp.lazy = $couchPotato; //note that you need to use the name 'lazy'
});

This little assignment gives those register functions that were assigned above the capability of asking Couch Potato to do its work. Remember that the RequireJS AMD modules do not run in an Angular context -- hence we wouldn't be able to use dependency injection to grab the $couchPotato service. Latching onto it in module.run works around this.

So back to those registerXXX functions. In fact, they contain branches. Each one looks something like this (we'll use registerController but they're all doing the same thing). Keep in mind you don't write these, Couch Potato assigns them to the app module for you when you call configureApp.

function registerController(name, controller) {
  if (app.lazy) {
    app.lazy.registerController([name, controller]);
  }
  else {
    app.controller(name, controller);
  }
}

Why the branch? It enables calls to registerController (or registerDirective, etc.) to function at at configuration time or at run time. This is really handy. In practical terms, you can author all of your components as Couch Potato components, whether you want to compile them into your eventual app.js using r.js optimization or you want to load/register them lazily.

Advanced Setup

What follows assumes you're using RequireJS to load your app and is relatively advanced. It is likely to be confusing to those who do not know RequireJS well. (If you're using <script> tags then you're probably compiling certain components directly into your application module anyway.)

You've been warned. If you want to skip this section you can move straight on to Lazy Loading Via Routing.

The distinction between whether r.js will compile Couch Potato components or whether they will be lazy-loaded/registered is effected by whether or not you require (as in use the RequireJS require function on) them within your RequireJS main.js file (or wherever it is that you have your RequireJS entry point). An example is probably necessary to flesh out this concept.

Let's say your (over)simplified main.js looks like this -- relatively normal RequireJS configuration for an Angular app:

require.config({
  paths: {
    'angular':            '//somecdn/angular.min.js',
    'angular-ui-router':  '//somecdn/angular-ui-router.min.js',
    'couch-potato':       '/js/angular-couch-potato.min.js'
  }
});

require(['app', 'run'], function(app) {
  angular.element(document).ready(function() {
    angular.bootstrap($('html'), [
      app['name'], function() {
        $('html').addClass('ng-app');
      }
    ]
  });
});

'app' is your AMD module that defines the Angular app module.

'run' is your AMD module that causes the Angular app to be run.

This main.js just initializes the app and runs it, then attaches the bootstrap function to the document ready event. As said, this is basic RequireJS/Angular stuff. Uses jQuery, but that's not important.

Everything in main.js will be compiled by r.js -- including the components that are required, and the ones they require, etc.

So anything that 'app' depends on, and anything that 'run' depends on -- those will be compiled into your output from r.js (if they are not prevented by being loaded from a CDN or otherwise exluded from the r.js compile process).

Let's look at app.js:

define(['angular', 'couch-potato', 'ui-router'],
  function(angular, couchPotato) {
    var app = angular.module('myApp', [
      'ui.state'
      'scs.couch-potato'
    ]);

    couchPotato.configureApp(app);

    // return app so you can access it using RequireJs
    return app;
  }
);

Aside from configuring the app for Couch Potato, this is all still normal RequireJS/Angular. Don't get bored -- we're getting toward the point here...

Let's look at run.js:

define(['app', 'config', 'someService'],
  function(app) {
    app.run(['$couchPotato', 'someService',
      function($couchPotato, someService) {
        app.lazy = $couchPotato;
      }
    ]);
  }
);

So this causes the app to go into run mode, and assigns $couchPotato to app.lazy. For the present exposition, what matters is that 'config' is required.

Here's config.js:

define(['app', 'couch-potato', 'compiled-components'], function(app) {
  app.config('$couchPotatoProvider', function($couchPotatoProvider) {
    // nothing really needed here except the rest of your app.config logic
  });
});

So, because it's necessary, $couchPotatoProvider is injected. But the point here is that 'compiled-components' is required. This is the module in which you specific which of your Couch Potato components will not be lazy loaded, and instead will be compiled into your app by r.js.

One approach here is to bail on lazy loading filters and decorators. You may also have some directives that are used all over the place and not worth tracking as individual dependencies or lazy loading them.

In which case, 'compiled-components.js' could look like:

define(
  [
    'filters',
    'decorators',
    'common-directives'
  ], function() {
});

Since all of the modules in this exposition are required down from main.js, r.js is going to build them into the compiled output.

Components that are not required by this tree of dependencies will be lazy loaded as disussed in Lazy Loading Via Routing.