diff --git a/docs/content/error/ng/aobj.ngdoc b/docs/content/error/ng/aobj.ngdoc new file mode 100644 index 000000000000..101fb172393b --- /dev/null +++ b/docs/content/error/ng/aobj.ngdoc @@ -0,0 +1,7 @@ +@ngdoc error +@name ng:aobj +@fullName Invalid Argument +@description + +The argument passed should be an object. Check the value that was passed to the function where +this error was thrown. diff --git a/src/AngularPublic.js b/src/AngularPublic.js index e90814e8d1a9..827e97399ab0 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -265,5 +265,6 @@ function publishExternalAPI(angular) { $$cookieReader: $$CookieReaderProvider }); } - ]); + ]) + .info({ angularVersion: '"NG_VERSION_FULL"' }); } diff --git a/src/auto/injector.js b/src/auto/injector.js index f7022ec3023e..70e3c6eb4592 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -180,6 +180,28 @@ function annotate(fn, strictDi, name) { * As an array of injection names, where the last item in the array is the function to call. */ +/** + * @ngdoc property + * @name $injector#modules + * @type {Object} + * @description + * A hash containing all the modules that have been loaded into the + * $injector. + * + * You can use this property to find out information about a module via the + * {@link angular.Module#info `myModule.info(...)`} method. + * + * For example: + * + * ``` + * var info = $injector.modules['ngAnimate'].info(); + * ``` + * + * **Do not use this property to attempt to modify the modules after the application + * has been bootstrapped.** + */ + + /** * @ngdoc method * @name $injector#get @@ -673,6 +695,7 @@ function createInjector(modulesToLoad, strictDi) { instanceInjector = protoInstanceInjector; providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; + instanceInjector.modules = providerInjector.modules = createMap(); var runBlocks = loadModules(modulesToLoad); instanceInjector = protoInstanceInjector.get('$injector'); instanceInjector.strictDi = strictDi; @@ -768,6 +791,7 @@ function createInjector(modulesToLoad, strictDi) { try { if (isString(module)) { moduleFn = angularModule(module); + instanceInjector.modules[module] = moduleFn; runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); runInvokeQueue(moduleFn._invokeQueue); runInvokeQueue(moduleFn._configBlocks); diff --git a/src/loader.js b/src/loader.js index 36da047fe20b..05c24ce3e28f 100644 --- a/src/loader.js +++ b/src/loader.js @@ -79,6 +79,9 @@ function setupModuleLoader(window) { * @returns {angular.Module} new module with the {@link angular.Module} api. */ return function module(name, requires, configFn) { + + var info = {}; + var assertNotHasOwnProperty = function(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); @@ -114,6 +117,45 @@ function setupModuleLoader(window) { _configBlocks: configBlocks, _runBlocks: runBlocks, + /** + * @ngdoc method + * @name angular.Module#info + * @module ng + * + * @param {Object=} info Information about the module + * @returns {Object|Module} The current info object for this module if called as a getter, + * or `this` if called as a setter. + * + * @description + * Read and write custom information about this module. + * For example you could put the version of the module in here. + * + * ```js + * angular.module('myModule', []).info({ version: '1.0.0' }); + * ``` + * + * The version could then be read back out by accessing the module elsewhere: + * + * ``` + * var version = angular.module('myModule').info().version; + * ``` + * + * You can also retrieve this information during runtime via the + * {@link $injector#modules `$injector.modules`} property: + * + * ```js + * var version = $injector.modules['myModule'].info().version; + * ``` + */ + info: function(value) { + if (isDefined(value)) { + if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value'); + info = value; + return this; + } + return info; + }, + /** * @ngdoc property * @name angular.Module#requires diff --git a/src/loader.prefix b/src/loader.prefix index 2783f7834268..b5188555461e 100644 --- a/src/loader.prefix +++ b/src/loader.prefix @@ -5,4 +5,7 @@ */ 'use strict'; (function() { - function isFunction(value) {return typeof value === 'function';}; + function isFunction(value) {return typeof value === 'function';} + function isDefined(value) {return typeof value !== 'undefined';} + function isObject(value) {return value !== null && typeof value === 'object';} + diff --git a/src/ngAnimate/module.js b/src/ngAnimate/module.js index ee83e27c9772..704021450add 100644 --- a/src/ngAnimate/module.js +++ b/src/ngAnimate/module.js @@ -756,6 +756,7 @@ angular.module('ngAnimate', [], function initAngularHelpers() { isFunction = angular.isFunction; isElement = angular.isElement; }) + .info({ angularVersion: '"NG_VERSION_FULL"' }) .directive('ngAnimateSwap', ngAnimateSwapDirective) .directive('ngAnimateChildren', $$AnimateChildrenDirective) diff --git a/src/ngAria/aria.js b/src/ngAria/aria.js index d46822d57a25..639b370a6f75 100644 --- a/src/ngAria/aria.js +++ b/src/ngAria/aria.js @@ -54,6 +54,7 @@ * {@link guide/accessibility Developer Guide}. */ var ngAriaModule = angular.module('ngAria', ['ng']). + info({ angularVersion: '"NG_VERSION_FULL"' }). provider('$aria', $AriaProvider); /** diff --git a/src/ngCookies/cookies.js b/src/ngCookies/cookies.js index fb6047d09c23..6901da62aaf8 100644 --- a/src/ngCookies/cookies.js +++ b/src/ngCookies/cookies.js @@ -17,6 +17,7 @@ angular.module('ngCookies', ['ng']). + info({ angularVersion: '"NG_VERSION_FULL"' }). /** * @ngdoc provider * @name $cookiesProvider diff --git a/src/ngMessageFormat/messageFormatService.js b/src/ngMessageFormat/messageFormatService.js index 2e83e6674982..9f93950073a5 100644 --- a/src/ngMessageFormat/messageFormatService.js +++ b/src/ngMessageFormat/messageFormatService.js @@ -216,6 +216,7 @@ var toJson; var $$stringify; var module = window['angular']['module']('ngMessageFormat', ['ng']); +module['info']({ 'angularVersion': '"NG_VERSION_FULL"' }); module['factory']('$$messageFormat', $$MessageFormatFactory); module['config'](['$provide', function($provide) { $interpolateMinErr = window['angular']['$interpolateMinErr']; diff --git a/src/ngMessages/messages.js b/src/ngMessages/messages.js index b02ccd3db478..9de67aefc666 100644 --- a/src/ngMessages/messages.js +++ b/src/ngMessages/messages.js @@ -267,6 +267,7 @@ angular.module('ngMessages', [], function initAngularHelpers() { isString = angular.isString; jqLite = angular.element; }) + .info({ angularVersion: '"NG_VERSION_FULL"' }) /** * @ngdoc directive diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 978802bf5b9c..3cc6c7bf8ad1 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -790,6 +790,7 @@ angular.mock.TzDate.prototype = Date.prototype; * You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))` */ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) + .info({ angularVersion: '"NG_VERSION_FULL"' }) .config(['$provide', function($provide) { @@ -2404,7 +2405,7 @@ angular.module('ngMock', ['ng']).provider({ $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); $provide.decorator('$controller', createControllerDecorator($compileProvider)); $provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator); -}]); +}]).info({ angularVersion: '"NG_VERSION_FULL"' }); /** * @ngdoc module @@ -2419,7 +2420,7 @@ angular.module('ngMock', ['ng']).provider({ */ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); -}]); +}]).info({ angularVersion: '"NG_VERSION_FULL"' }); /** * @ngdoc service diff --git a/src/ngParseExt/module.js b/src/ngParseExt/module.js index eeebf8812e4b..eccdb29bd155 100644 --- a/src/ngParseExt/module.js +++ b/src/ngParseExt/module.js @@ -44,4 +44,5 @@ function isValidIdentifierContinue(ch, cp) { angular.module('ngParseExt', []) .config(['$parseProvider', function($parseProvider) { $parseProvider.setIdentifierFns(isValidIdentifierStart, isValidIdentifierContinue); - }]); + }]) + .info({ angularVersion: '"NG_VERSION_FULL"' }); diff --git a/src/ngResource/resource.js b/src/ngResource/resource.js index bb879b884903..060d66b7f120 100644 --- a/src/ngResource/resource.js +++ b/src/ngResource/resource.js @@ -429,6 +429,7 @@ function shallowClearAndCopy(src, dst) { * */ angular.module('ngResource', ['ng']). + info({ angularVersion: '"NG_VERSION_FULL"' }). provider('$resource', function ResourceProvider() { var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/; diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 12e1492a3426..b368f9c4b25b 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -27,6 +27,7 @@ var noop; /* global -ngRouteModule */ var ngRouteModule = angular. module('ngRoute', []). + info({ angularVersion: '"NG_VERSION_FULL"' }). provider('$route', $RouteProvider). // Ensure `$route` will be instantiated in time to capture the initial `$locationChangeSuccess` // event (unless explicitly disabled). This is necessary in case `ngView` is included in an diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js index 73168716a5d2..ae78082f18dc 100644 --- a/src/ngSanitize/sanitize.js +++ b/src/ngSanitize/sanitize.js @@ -547,4 +547,6 @@ function sanitizeText(chars) { // define ngSanitize module and register $sanitize service -angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); +angular.module('ngSanitize', []) + .provider('$sanitize', $SanitizeProvider) + .info({ angularVersion: '"NG_VERSION_FULL"' }); diff --git a/src/ngTouch/touch.js b/src/ngTouch/touch.js index 5ae19d6b4a87..d52d1d89ddac 100644 --- a/src/ngTouch/touch.js +++ b/src/ngTouch/touch.js @@ -24,6 +24,8 @@ /* global -ngTouch */ var ngTouch = angular.module('ngTouch', []); +ngTouch.info({ angularVersion: '"NG_VERSION_FULL"' }); + ngTouch.provider('$touch', $TouchProvider); function nodeName_(element) { diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 397d95fda04d..2cdb34ed1675 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -2,6 +2,37 @@ /* globals support: false */ +describe('injector.modules', function() { + it('should expose the loaded module info on the instance injector', function() { + var test1 = angular.module('test1', ['test2']).info({ version: '1.1' }); + var test2 = angular.module('test2', []).info({ version: '1.2' }); + module('test1'); + inject(['$injector', function($injector) { + expect(Object.keys($injector.modules)).toEqual(['ng', 'ngLocale', 'ngMock', 'test1', 'test2']); + expect($injector.modules['test1'].info()).toEqual({ version: '1.1' }); + expect($injector.modules['test2'].info()).toEqual({ version: '1.2' }); + }]); + }); + + it('should expose the loaded module info on the provider injector', function() { + var providerInjector; + var test1 = angular.module('test1', ['test2']).info({ version: '1.1' }); + var test2 = angular.module('test2', []) + .info({ version: '1.2' }) + .provider('test', ['$injector', function($injector) { + providerInjector = $injector; + return { $get: function() {} }; + }]); + module('test1'); + // needed to ensure that the provider blocks are executed + inject(); + + expect(Object.keys(providerInjector.modules)).toEqual(['ng', 'ngLocale', 'ngMock', 'test1', 'test2']); + expect(providerInjector.modules['test1'].info()).toEqual({ version: '1.1' }); + expect(providerInjector.modules['test2'].info()).toEqual({ version: '1.2' }); + }); +}); + describe('injector', function() { var providers; var injector; diff --git a/test/loaderSpec.js b/test/loaderSpec.js index c4f7a33989bd..7166f218c3e8 100644 --- a/test/loaderSpec.js +++ b/test/loaderSpec.js @@ -156,4 +156,35 @@ describe('module loader', function() { it('should expose `$$minErr` on the `angular` object', function() { expect(window.angular.$$minErr).toEqual(jasmine.any(Function)); }); + + describe('Module', function() { + describe('info()', function() { + var theModule; + + beforeEach(function() { + theModule = angular.module('theModule', []); + }); + + it('should default to an empty object', function() { + expect(theModule.info()).toEqual({}); + }); + + it('should store the object passed as a param', function() { + theModule.info({ version: '1.2' }); + expect(theModule.info()).toEqual({ version: '1.2' }); + }); + + it('should throw if the parameter is not an object', function() { + expect(function() { + theModule.info('some text'); + }).toThrowMinErr('ng', 'aobj'); + }); + + it('should completely replace the previous info object', function() { + theModule.info({ value: 'X' }); + theModule.info({ newValue: 'Y' }); + expect(theModule.info()).toEqual({ newValue: 'Y' }); + }); + }); + }); });