Skip to content

Writing build enabled code

Cláudio Silva edited this page Feb 14, 2015 · 12 revisions

In order to obtain the best results with the build tool, some care should be taken with how you structure your code.

Wrapped and unwrapped code

The recommended practice is to always wrap the code in each source file like this:

(function () {

  var myPrivateVar = 1;

  angular.module ('moduleName', []).

    directive ('test', function () {
      return {
        restrict: 'E',
        link: function (scope, element, attrs) {
          return myPrivateFn ();
        }
      }
    });

  function myPrivateFn () {
  }

}) ();

Or like this:

(function (declare) {

  var myPrivateVar = 1;

  declare.

    directive ('test', function () {
      return {
        restrict: 'E',
        link: function (scope, element, attrs) {
          return myPrivateFn ();
        }
      }
    });

  function myPrivateFn () {
  }

}) (angular.module ('moduleName', []));

Code like the examples above will work just fine with the builder tool.

Unwrapped code

Nevertheless, if your code consists only of module definitions, with no private functions or variables, you don't need to wrap it.

For example:

// Valid code.

/* Comments are allowed. */

angular.module ('moduleName', []).

  service ('test', function () {
    // do something
  }).

  factory ('test2', function () {
    // do something
  }).

  directive ('test3', function () {
    return {
      restrict: 'E',
      link: function (scope, element, attrs) {
        // do something
      }
    }
  });

/** These are allowed too: */

window.name = "Hello";
console.log ("Hey!");

In this example source file, code is not wrapped, so the build tool will have to analyze it with more care. This will be a little slower to build.

The build tool will run the code in an isolated sandbox to analyze whether the code will run the same way on debug builds and on release builds.

On release builds, each module's code is wrapped in an isolated javascript scope.
On debug builds, code runs as it is.

Statements like those two lines at the end of the example above will be accepted, as they will perform the same way whether wrapped or not.

But the following code will be rejected, as it could have unintended side effects when transformed for a release build:

// Dangerous code.

// These variables may be used elsewhere, as they are global.
var x = 1;
e = 1;

angular.module('moduleName', []).

  service ('test2', function () {
    return myPrivateFn3 ();
  });

// This function may be called elsewhere, as it is global.
function myPrivateFn3 () {
}

Here, three identifiers are added to the javascript global scope: x, e and myPrivateFn3.
When running on a release build, those identifiers will not be added to the javascript global scope. This may, or may not, have unintended consequences.

You may force the build tool to accept this kind of source code by running the grunt command with the --force option.

Split your modules into several files

Don't create gigantic monolithic module files!

The main reason for using a build tool is to be able to split your code into as many files as you need, to make it more organized and simpler to understand.

One way to organize your code is to create a folder for each module.
Inside that folder, you may create additional folders to group related functionality.
You may also nest some modules inside others, if you need to.

Split your module's code into as many files as you want.
You can put many services and directives per file, or you may create a file for each service or directive, or you can mix both approaches.
Do what feels best for you.

One thing you shouldn't do, though, is to mix declarations for more than one module in the same source file!

Although you can do it, it is not recommended. See Limitations.

Example directory structure (not mandatory):

src
 |--module1
 |  |--services
 |  |    service1.js
 |  |    services2_and_3.js
 |  |--directives
 |  |    my_directives.js
 |  |    some_other_directive.js
 |  |--modules
 |  |  |--submodule A
 |  |  |  |--services
 |  |  |  |    ...
 |  |  |  |--directives
 |  |  |  |    ...
 |  |  |  |--other-stuff
 |  |  |  |    ...
 |  |  |--submodule B
 |  |  |    ...
 |--module2
 |  ...

You can, of course, organize your code in any way you want. The build tool should be able to find and assemble all the related code, no matter into how many files and folders deep it was split into, or in which order they are read.

Take care with module references

To avoid redundancy and generate shorter code, the build tool replaces multiple references to the same angular module with variables.

Suppose you have the following three files:

  • One that declares the module:
angular.module ('moduleName', []).

  service ('test', function () {
    // do something
  });
  
  • Another one that extends it with additional definitions:
angular.module ('moduleName').

  factory ('test2', function () {
    // do something
  });

angular.module ('moduleName').constant ('X', 123);
  • And another one that is wrapped in an isolated javascript scope:
(function (mod) {
  var private1;
  
  mod.service ('test4', function () {
    // do something
  });

  function private2 () {}
   
}) (angular.module('moduleName'));

These would be assembled like this:

(function (module) {

  module.service ('test', function () {
    // do something
  });

  module.factory ('test2', function () {
    // do something
  });

  module.constant ('X', 123);

  var private1;
  
  module.service ('test4', function () {
    // do something
  });

  function private2 () {}
   
}) (angular.module('moduleName', []));

As you can see, the build tool had to unwrap the content of file 3, and then rename the module reference from mod to the preconfigured name module.

You can set your preferred name for module references using the releaseBuild.moduleVar task configuration option.

The example above would build just fine, although you may need to enable releaseBuild.renameModuleRefs, otherwise the build will stop with a warning.

This is so because the build tool, by default, only allows safe operations.

The renaming operation is not bullet-proof and may, sometimes, fail to rename things properly. So, use this functionality with care.

I recommend that you always use the same variable name for module references. It's safer that way.

Assigning module references to variables

This is supported:

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

mod.service ('test', function () {
    // do something
  });
Warning

Do not assign the module reference to a variable that has the same name as the autogenerated module variable (defined by the releaseBuild.moduleVar config. property) or your code will fail at runtime.

Warning

Although you may store a module reference in a variable, that variable is confined to the file where it is declared.
You may not assign your module to a global variable (nor is it a good practice).
Also, you are not able to share such a variable between files related to the same module. It will not work! - Read the next section to know why.

To effectively use Angular Builder, you should always have, at least, one angular.module('name') reference to a module on each file related to it.

Avoid using global variables

Angular Builder has specific code to detect the declaration of global variables and forbid it (only on 'release' mode), as your application code would behave differently on release and debug builds, due to the way the builder wraps and optimizes code on release builds.

Note

Although you can still define global variables by specifically storing them on the window object, the builder will not use them for module resolution (see the section above).

For further explanation about this topic, read the Unwrapped code section above.

Specifying the third parameter in a module declaration

This is partially supported:

angular.module('moduleName', [], function ($provide) {
	// some code...
});
// You can't put code here.

It works as long as there is no more source code after the module declaration.

The use of this declaration syntax is discouraged.

This partial support was implemented only for supporting the standard ngLocale localization optional libraries bundled with the Angular.js framework.


Next: Including stylesheets in the build