AngularJS: Getting around ngApp limitations with ngModule

Getting-around-ngApp-limitations-with-ngModule

As you AngularJS application gets more complex you will likely come to discover that the ngApp directive has two fairly big limitations:

  1. You can only have one ng-app per page.
  2. You can only associate a single module with a single HTML element

Combine modules into a single module

One way to work around this is to “combine” multiple modules into a single module by referencing them in another module. Here is an example of what I mean:

<!DOCTYPE html>
<html>
    <head>
        <script src="angular.js"></script>
        <script>
          var moduleA = angular.module("MyModuleA", []);
          moduleA.controller("MyControllerA", function($scope) {
              $scope.name = "Bob A";
          });

          var moduleB = angular.module("MyModuleB", []);
          moduleB.controller("MyControllerB", function($scope) {
              $scope.name = "Steve B";
          });

          angular.module("CombineModule", ["MyModuleA", "MyModuleB"]);
        </script>
    </head>
    <body ng-app="CombineModule">
        <div>
            <h1>myDiv1</h1>
            <div ng-controller="MyControllerA">
                {{name}}
            </div>
        </div>

        <div>
            <h1>myDiv2</h1>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>
    </body>
</html>

That works, but it has it’s problems. One it’s less clear the second div myDiv2 only needs the controller from MyModuleB why combine everything. You can imagine it would become less clear as your app and therefore the number of modules grows.

Also this example works because the controllers have different names, what if they had the same names. Being able to associate different modules with different HTML elements would give us better control of our namespace.

Do it programmatically using angular.bootstrap()

You might be surprised to find out that the limitations of the ngApp are not limitations of Angular itself. Angular allows you to associate more than one module per HTML element. Angular also allows you to have multiple HTML elements on a page associate with modules. You just have to do it grammatically.

Below is an example of how to do this. In this example we have two modules each with one controller. myDiv1 is associated with both modules. While myDiv2 is associated with just with a single module.

<!DOCTYPE html>
<html>
    <head>
        <script src="angular.js"></script>
        <script>
          var moduleA = angular.module("MyModuleA", []);
          moduleA.controller("MyControllerA", function($scope) {
              $scope.name = "Bob A";
          });

          var moduleB = angular.module("MyModuleB", []);
          moduleB.controller("MyControllerB", function($scope) {
              $scope.name = "Steve B";
          });

          angular.element(document).ready(function() {
              var myDiv1 = document.getElementById("myDiv1");
              angular.bootstrap(myDiv1, ["MyModuleA", "MyModuleB"]);

              var myDiv2 = document.getElementById("myDiv2");
              angular.bootstrap(myDiv2, ["MyModuleB"]);
          });
        </script>
    </head>
    <body>
        <div id="myDiv1">
            <h1>myDiv1</h1>
            <div ng-controller="MyControllerA">
                {{name}}
            </div>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>

        <div id="myDiv2">
            <h1>myDiv2</h1>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>
    </body>
</html>

The output of that code is:

    myDiv1

    Bob A
    Steve B

    myDiv2

    Steve B

You can see for yourself and play around with that code at:

http://jsfiddle.net/luisperezphd/QX3fQ/

This gives us a lot of flexibility, the problem is it’s pretty ugly. You have to reference the element itself in your code which means you are coupling your code with the HTML. That goes against one of the main goals of Angular.

Ideal solution: A more robust ngApp (enter ngModule)

The ideal solution would be for ngApp to allow you to do everything angular.bootstrap() allows you to do. Allow you to use it on multiple HTML elements. Allow you to specify more than one module.

Normally the solution to this would be to create your own Angular directive. The problem is, how would you define that directive? You would need a module to define it in which would defeat the purpose.

To implement a directive like ngApp you would need to implement it the way ngApp is implemented. I’ll spare you the trouble, I’ve already implemented it.

You can find it on GitHub at:
https://github.com/luisperezphd/ngModule

This JavaScript file introduces the ngModule directive. Here is an example of how you would rewrite the code above to use ngModule:

<!DOCTYPE html>
<html>
    <head>
        <script src="angular.js"></script>
        <script src="angular.ng-modules.js"></script>
        <script>
          var moduleA = angular.module("MyModuleA", []);
          moduleA.controller("MyControllerA", function($scope) {
              $scope.name = "Bob A";
          });

          var moduleB = angular.module("MyModuleB", []);
          moduleB.controller("MyControllerB", function($scope) {
              $scope.name = "Steve B";
          });
        </script>
    </head>
    <body>
        <div ng-modules="MyModuleA, MyModuleB">
            <h1>Module A, B</h1>
            <div ng-controller="MyControllerA">
                {{name}}
            </div>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>

        <div ng-module="MyModuleB">
            <h1>Just Module B</h1>
            <div ng-controller="MyControllerB">
                {{name}}
            </div>
        </div>
    </body>
</html>

You see in action here: http://jsfiddle.net/luisperezphd/j5jzsppv/

Notes about ngModule

You might have noticed that in one case I use the plural version of the directive ng-modules with the “s” and in another case I use the singular version without the “s” ng-module.

This was just a preference of mine. I allow for both spellings interchangeably regardless of whether you reference a single or multiple modules. I just liked how it read better.

In keeping with Angular’s convention I allow you to use any variation of the directive name. For example, the following are all valid: ng:module, x-ng-modules, data-ng-modules, etc.

Finally you should know that while you can now associate multiple HTML elements on a single page with modules by using this directive. Those HTML elements cannot be nested. If they are nested they will not behave properly. Said another way if you associate an HTML element with a module, you can’t associate a child of that element with another module.

Why the ngApp limitations?

One questions that I’ve seen asked in several forums is why did the Angular team place those limitations on ngApp to begin with. After all as I mentioned above the Angular framework itself does not have those limitations. I haven’t seen an answer from the Angular team but I can speculate.

Earlier versions of angular have the “view” and “route” functionality built in. In fact, that seemed the be the expected or recommended way to use Angular. Since you only have one URL, you can’t have multiple multiple views and routes on the same page. Therefore there might have been little need to be able to associate multiple modules.

They might have felt that having a single module associated with a page made it easier to understand and use. After all you often hear Angular often being mentioned as a technology for single page applications.

They might have simply not gotten around to it since two approaches already exists for doing it. I covered them above.

They might have felt that that kind of information belongs in your code, not your markup. While all these reasons are debatable, I think this one would be the most debatable since it’s possible for a module to only contain directives for front end controls.

I mentioned above that AngularJS does not allow nesting. Meaning you can’t have a element and a child element associated with different modules. The Angular team might have felt that because of that limitation it was better to avoid the situation by only allowing one module in your HTML.

Learning AngularJS?

I created a free 5 day mini email course to walk you through creating an app for scratch. Sign up here

Comments

  1. says

    For Firefox had to use this altered code instead, otherwise a module that was still just being declared via ng-app would fail to load:

    for(var i = 0; i < moduleElements.length; i++) {
        var moduleElement = moduleElements[i];
        if (typeof(modules[i]) != 'undefined') {
            var module = modules[i].replace(/ /g,'').split(",");
            angular.bootstrap(moduleElement, module);
        }
    }
    
      • Luis Perez says

        Hey Andy, I haven’t gotten around to make this update to the actual codebase on GitHub. For now you would have to pull the code and make the changes Bryan mentioned.

        • Luis Perez says

          I fixed the issue in the GitHub source code that was causing issues in Firefox. While Bryan’s solution will work the core of the issue was a bug in my code. An error I made while adapting Angular’s ngApp code.

  2. Saiteja says

    I think, in “Notes about ngModule” section, while talking about singular and plural versions of ng-module, it was entered “ng-model” for singular version instead of “ng-module”.

  3. Joe says

    This looks really nice, but I’m having a fair bit of trouble getting it to work. I can use the ng-module directive with as many “modules” as I like (Ex. ng-module=”mod1, mod2, mod3″) without error the first time it appears in the HTML, but after that it doesn’t seem to work. Any ideas what I might be doing wrong? Probably just a stupid mistake I’m missing?

    Javascript: (pastebin)
    HTML: (pastebin also)

  4. Iftikhar Ali says

    Hi Luis. Very information article. I have one question though. In a large where each group/or development is building there own modules with potentially the same name. For this example, say Developer 1 has a module named “ModuleA” in Module1.js and Developer 2 has also developed a module with the same name “ModuleA” in Module2.js.

    When I reference Module1.js and Module2.js, both modules will end up registering with angular.module(…) with the same name. Any suggestions to control that?

    • Luis Perez says

      Hey Iftikhar. Unfortunately modules need unique names.

      The key is how do you come up with a process so that developers always create unique names.

      One approach is to look at how other languages do it. For example in Java the “full name” of the class is based on the name of the file and the folder it’s in. For example if you had a Java file called Bitmap.java in the folder MyArtStuff, the full name of the class would be MyArtStuff.Bitmap

      Turns out AngularJS allows you to have dots (.) as part of your module name so you could essentially use the name convention.

      For example if a developer create a module called “ModuleA” in the script “MainPage\Module1.js” they should name their module “MainPage.Module1.ModuleA”. Because each path and filename is unique in your app then your module name will be unique.

      You would just have to get your developers to follow this convention.

      Here’s a JSFiddle to illustrate what I mean:

      http://jsfiddle.net/luisperezphd/bn2apxc0/1/

      Hope that helps.

      • Iftikhar Ali says

        Thanks. I was also thinking about some sort of namespace convention for module names. The problem is Angular doesn’t raise any explicit errors for duplicate module names – and combined with dynamic nature of JS, it just makes it harder to debug these kind of issues.

    • Luis Perez says

      Hey Pankaj, I created a JSFiddle with two modules. Included on in the other and referenced a service with no problem. See: http://jsfiddle.net/luisperezphd/zg9oLnx3/ – are you sure you included the necessary ngRoute JavaScript files? If it’s still a problem can you create a JSFiddle so I can take a look at the exact code.

  5. Fred Lima says

    How can i use this ngModule with $sce to bind views on my html? Without the $sce my code is working fine, but if I use this, the script not work.

    • Luis Perez says

      Hey Fred, it’s hard to determine the issue without more information. The best way is to create a JSFiddle with the minimal amount of code necessary to recreate the problem. If you do that I can take a further look. Thanks, Luis.

  6. Tony says

    Hi Luis, I’m a bit of a newbie to AngularJS so maybe this is a problem that only applies if you don’t code the way angularjs is supposed to be coded, but have you already noticed that ng-module will only work if the tag has no other strings, like i.e. id=’idname’ , class=’classname’ etc., in it?

    Thanks a lot for ng-module anyways. It’s one of these small things the make such a big matter.

    Greetings

  7. Suzie Doe says

    If it helps, the Uncaught Error: [$injector:modulerr] error you are getting might just be the Web Developer Tools in Google Chrome. If you uncheck the Enable JavaScript source maps in the Web Developer Settings or put the .map file in the same directory as your angularJS file it should stop howling and just work. Ran into this a couple of times before I found the answer googling.

  8. Amr Hussien says

    Hello Luis,
    I tried to nest two ng-modules but the inner module is working fine and the outer’s functions is not defined, any idea ?

  9. says

    Genial tu solución, no sabía de la limitación que tenía angujar con ng-app hasta que me encontré el error Error: [ng:areq] from angular controller. Lo curioso es que teniendo ese error la aplicación se ejecuta correctamente, digo visualmente no se ve afectada solo que en la consola del navegador se puede ver el error. Salvo eso lo demás funciona bien. Pero con tu solución creo que se resuelve el dichoso error.

  10. Kiran says

    Hi Luis,

    one quick question.. can i use a child ng-module inside a parent ng-module? because i’m planning to use ng-view inside the parent ng-module so that i can load templates based on route. those templates may have their own ng-module. so will this template’s ng-module conflict with the parent one?

    • Luis Perez says

      Unfortunately you cannot, you get some strange behaviour if you try. This is currently a limitation of AngularJS. You can though have nested controllers that can communicate with each other, I don’t know though if that accomplishes what you want.

    • Luis Perez says

      ng-module is a directive that serves as a convenient shortcut for calling angular.bootstrap() so you wouldn’t call the directive programmatically instead you would angular.bootstrap(). As long as you get can the element in some way you can “bootstrap” it. You could jQuery to get the element. There is an example in this article in the section titled “Do it programmatically using angular.bootstrap()”.

      • Saqib Nisar says

        I have been trying to include multiple ng-Apps in a page but it only activates the first it gets in html page and ignores the rest. So yes “Only one AngularJS application can be auto-bootstrapped per HTML document”.

        • Luis Perez says

          Correct AngularJS only allows one ngApp directive, to get around that I created ngModule, it works just like ngApp except that it allows you to have multiple ngModule per page.

  11. Saqib Nisar says

    Hi Luis,
    i have found this tutorial so useful, you not only solved my problem also showed how we could use multiple Modules under a single html page.

    Thanks a lot.

  12. adam says

    Hello, when i load in the file it says that angular is not defined on line 62 in your file. What am I doing wrong?

    • Luis Perez says

      Adam, more than likely either the AngularJS script wasn’t included or it was included after ngModule was included. It’s hard to say without more detail. Can you recreate the scenario in Plunkr or JS Fiddle? I can help you further from there.

  13. Shruthy says

    You have stated finally that nesting is not applicable to parent – child – each referring to different modules, but the statement it sounds otherway.I hope it is to be corrected as “can’t” instead of “can”.

    Finally you should know that while you can now associate multiple HTML elements on a single page with modules by using this directive. Those HTML elements cannot be nested. If they are nested they will not behave properly. Said another way if you associate an HTML element with a module, you “can”(‘can’t’) associate a child of that element with another module.

Leave a Reply

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