|
| 1 | +@ngdoc overview |
| 2 | +@name Developer Guide: Modules |
| 3 | +@description |
| 4 | + |
| 5 | +# What is a Module? |
| 6 | + |
| 7 | +Most applications have a main method which instantiates, wires, and bootstraps the application. |
| 8 | +Angular apps don't have a main method, instead the modules serves the purpose of declaratively |
| 9 | +specifying how an application should be bootstrapped. There are several advantages to this |
| 10 | +approach: |
| 11 | + |
| 12 | + * The process is more declarative which is easier to understand |
| 13 | + * In unit-testing there is no need to load all modules, which may aid in writing unit-tests. |
| 14 | + * Additional modules can be loaded in scenario tests, which can override some of the |
| 15 | + configuration and help end-to-end test the application |
| 16 | + * Third party code can be packaged as reusable modules. |
| 17 | + * The modules can be loaded in any/parallel order (due to delayed nature of module execution). |
| 18 | + |
| 19 | + |
| 20 | +# The Basics |
| 21 | + |
| 22 | +Ok i am in a hurry how do i get a Hello World module working? |
| 23 | + |
| 24 | +Important things to notice: |
| 25 | + |
| 26 | + * {@link api/angular.Module Module} API |
| 27 | + * Notice the reference to the `myApp` module in the `<html ng:app="myApp">`, it is what |
| 28 | + bootstraps the app using your module. |
| 29 | + |
| 30 | +<doc:example module='simpleApp'> |
| 31 | + <doc:source> |
| 32 | + <script> |
| 33 | + // declare a module |
| 34 | + var simpleAppModule = angular.module('simpleApp', []); |
| 35 | + |
| 36 | + // configure the module. |
| 37 | + // in this example we will create a greeting filter |
| 38 | + simpleAppModule.filter('greet', function() { |
| 39 | + return function(name) { |
| 40 | + return 'Hello, ' + name + '!'; |
| 41 | + }; |
| 42 | + }); |
| 43 | + |
| 44 | + </script> |
| 45 | + <div> |
| 46 | + {{ 'World' | greet }} |
| 47 | + </div> |
| 48 | + </doc:source> |
| 49 | +</doc:example> |
| 50 | + |
| 51 | + |
| 52 | + |
| 53 | +# Recommended Setup |
| 54 | + |
| 55 | +While the example above is simple, it will not scale to large applications. Instead we recommend |
| 56 | +that you break your application to multiple modules like this: |
| 57 | + |
| 58 | + * A service module, for service declaration |
| 59 | + * A directive module, for directive declaration |
| 60 | + * A filter module, for filter declaration |
| 61 | + * And an application level module which depends on the above modules, and which has |
| 62 | + initialization code. |
| 63 | + |
| 64 | +The reason for this breakup is that in your tests, it is often necessary to ignore the |
| 65 | +initialization code, which tends to be difficult to test. By putting it into separate module it |
| 66 | +can be easily ignored in tests. The tests can also be more focused by only loading the modules |
| 67 | +which are relevant to tests. |
| 68 | + |
| 69 | +The above is only a suggestion, so feel free to tailor it to your needs. |
| 70 | + |
| 71 | +<doc:example module='xmpl'> |
| 72 | + <doc:source> |
| 73 | + <script> |
| 74 | + angular.module('xmpl.service', []). |
| 75 | + value('greeter', { |
| 76 | + salutation: 'Hello', |
| 77 | + localize: function(localization) { |
| 78 | + this.salutation = localization.salutation; |
| 79 | + }, |
| 80 | + greet: function(name) { |
| 81 | + return this.salutation + ' ' + name + '!'; |
| 82 | + } |
| 83 | + }). |
| 84 | + value('user', { |
| 85 | + load: function(name) { |
| 86 | + this.name = name; |
| 87 | + } |
| 88 | + }); |
| 89 | + |
| 90 | + angular.module('xmpl.directive', []); |
| 91 | + |
| 92 | + angular.module('xmpl.filter', []); |
| 93 | + |
| 94 | + angular.module('xmpl', ['xmpl.service', 'xmpl.directive', 'xmpl.filter']). |
| 95 | + run(function(greeter, user) { |
| 96 | + // This is effectively part of the main method initialization code |
| 97 | + greeter.localize({ |
| 98 | + salutation: 'Bonjour' |
| 99 | + }); |
| 100 | + user.load('World'); |
| 101 | + }) |
| 102 | + |
| 103 | + |
| 104 | + // A Controller for your app |
| 105 | + var XmplController = function($scope, greeter, user) { |
| 106 | + $scope.greeting = greeter.greet(user.name); |
| 107 | + } |
| 108 | + </script> |
| 109 | + <div ng-controller="XmplController"> |
| 110 | + {{ greeting }}! |
| 111 | + </div> |
| 112 | + </doc:source> |
| 113 | + </doc:example> |
| 114 | + |
| 115 | + |
| 116 | + |
| 117 | +# Module Loading & Dependencies |
| 118 | + |
| 119 | +A module is a collection of configuration and run block which get applied to the application |
| 120 | +during the bootstrap process. In its simplest form the module consist of collection of two kinds |
| 121 | +of blocks: |
| 122 | + |
| 123 | + 1. **Configuration blocks** - get executed during the provider registrations and configuration |
| 124 | + phase. Only providers and constants can be injected into configuration blocks. This is to |
| 125 | + prevent accidental instantiation of services before they have been fully configured. |
| 126 | + 2. **Run blocks** - get executed after the injector is created and are used to kickstart the |
| 127 | + application. Only instances and constants can be injected into run blocks. This is to prevent |
| 128 | + further system configuration during application run time. |
| 129 | + |
| 130 | +<pre> |
| 131 | +angular.module('myModule', []). |
| 132 | + config(function(injectables) { // provider-injector |
| 133 | + // This is an example of config block. |
| 134 | + // You can have as many of these as you want. |
| 135 | + // You can only inject Providers (not instances) |
| 136 | + // into the config blocks. |
| 137 | + }). |
| 138 | + run(function(injectables) { // instance-injector |
| 139 | + // This is an example of a run block. |
| 140 | + // You can have as many of these as you want. |
| 141 | + // You can only inject instances (not Providers) |
| 142 | + // int the run blocks |
| 143 | + }); |
| 144 | +</pre> |
| 145 | + |
| 146 | +## Configuration Blocks |
| 147 | + |
| 148 | +There are some convenience methods on the module which are equivalent to the config block. For |
| 149 | +example: |
| 150 | + |
| 151 | +<pre> |
| 152 | +angular.module('myModule', []). |
| 153 | + value('a', 123). |
| 154 | + factory('a', function() { return 123; }). |
| 155 | + directive('directiveName', ...). |
| 156 | + filter('filterName', ...); |
| 157 | + |
| 158 | +// is same as |
| 159 | + |
| 160 | +angular.module('myModule', []). |
| 161 | + config(function($provide, $compileProvider, $filterProvider) { |
| 162 | + $provide.value('a', 123) |
| 163 | + $provide.factory('a', function() { return 123; }) |
| 164 | + $compileProvider.directive('directiveName', ...). |
| 165 | + $filterProvider.register('filterName', ...); |
| 166 | + }); |
| 167 | +</pre> |
| 168 | + |
| 169 | +The configuration blocks get applied in the order in which they are registered. The only exception |
| 170 | +to it are constant definitions, which are placed at the beginning of all configuration blocks. |
| 171 | + |
| 172 | +## Run Blocks |
| 173 | + |
| 174 | +Run blocks are the closest thing in Angular to the main method. A run block is the code which |
| 175 | +needs to run to kickstart the application. It is executed after all of the service have been |
| 176 | +configured and the injector has been created. Run blocks typically contain code which is hard |
| 177 | +to unit-test, and for this reason should be declared in isolated modules, so that they can be |
| 178 | +ignored in the unit-tests. |
| 179 | + |
| 180 | +## Dependencies |
| 181 | + |
| 182 | +Modules can list other modules as their dependencies. Depending on a module implies that required |
| 183 | +module needs to be loaded before the requiring module is loaded. In other words the configuration |
| 184 | +blocks of the required modules execute before the configuration blocks or the requiring module. |
| 185 | +The same is true for the run blocks. Each module can only be loaded once, even if multiple other |
| 186 | +modules require it. |
| 187 | + |
| 188 | +## Asynchronous Loading |
| 189 | + |
| 190 | +Modules are a way of managing $injector configuration, and have nothing to do with loading of |
| 191 | +scripts into a VM. There are existing projects which deal with script loading, which may be used |
| 192 | +with Angular. Because modules do nothing at load time they can be loaded into the VM in any order |
| 193 | +and thus script loaders can take advantage of this property and paralyze the loading process. |
| 194 | + |
| 195 | + |
| 196 | +# Unit Testing |
| 197 | + |
| 198 | +In its simplest form a unit-test is a way of instantiating a subset of the application in test and |
| 199 | +then applying a stimulus to it. It is important to realize that each module can only be loaded |
| 200 | +once per injector. Typically an app has only one injector. But in tests, each test has its own |
| 201 | +injector, which means that the modules are loaded multiple times per VM. Properly structured |
| 202 | +modules can help with unit testing, as in this example: |
| 203 | + |
| 204 | +In all of these examples we are going to assume this module definition: |
| 205 | +<pre> |
| 206 | + angular.module('greetMod', []). |
| 207 | + |
| 208 | + factory('alert', function($window) { |
| 209 | + return function(text) { |
| 210 | + $window.alert(text); |
| 211 | + } |
| 212 | + }). |
| 213 | + |
| 214 | + value('salutation', 'Hello'). |
| 215 | + |
| 216 | + factory('greet', function(alert, salutation) { |
| 217 | + return function(name) { |
| 218 | + alert(salutation + ' ' + name + '!'); |
| 219 | + } |
| 220 | + }); |
| 221 | +</pre> |
| 222 | + |
| 223 | +Let's write some tests: |
| 224 | +<pre> |
| 225 | +describe('myApp', function() { |
| 226 | + // load the application relevant modules then load a special |
| 227 | + // test module which overrides the $window with mock version, |
| 228 | + // so that calling window.alert() will not block the test |
| 229 | + // runner with a real alert box. This is an example of overriding |
| 230 | + // configuration information in tests. |
| 231 | + beforeEach(module('greetMod', function($provide) { |
| 232 | + $provide.value('$window', { |
| 233 | + alert: jasmine.createSpy('alert') |
| 234 | + }); |
| 235 | + }); |
| 236 | + |
| 237 | + // The inject() will create the injector and inject the greet and |
| 238 | + // $window into the tests. The test need not concern itself with |
| 239 | + // wiring of the application, only with testing it. |
| 240 | + it('should alert on $window', inject(function(greet, $window) { |
| 241 | + greet('World'); |
| 242 | + expect($window.alert).toHaveBeenCalledWith('Hello World!'); |
| 243 | + })); |
| 244 | + |
| 245 | + // this is another way of overriding configuration in the |
| 246 | + // tests using an inline module and inject methods. |
| 247 | + it('should alert using the alert service', function() { |
| 248 | + var alertSpy = jasmine.createSpy('alert'); |
| 249 | + module(function($provide) { |
| 250 | + $provide.value('alert', alertSpy); |
| 251 | + }); |
| 252 | + inject(function(greet) { |
| 253 | + greet('World'); |
| 254 | + expect(alertSpy).toHaveBeenCalledWith('World'); |
| 255 | + }); |
| 256 | + }); |
| 257 | +}); |
| 258 | +</pre> |
0 commit comments