From d54dfecb00fba41455536c5ddd55310592fdaf84 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 26 Mar 2012 13:01:24 -0700 Subject: [PATCH] feat($controller): support controller registration via $controllerProvider It's now possible to register controllers as: .register('MyCtrl', function($scope) { ... }); // or .register('MyCtrl', ['$scope', function($scope) { ... }); Additionally a module loader shortcut api was added as well: myModule.controller('MyCtr', function($scope) { ... }); --- src/loader.js | 13 ++++++++- src/service/controller.js | 51 ++++++++++++++++++++++++++++------ test/directive/ngViewSpec.js | 19 +++++++++++++ test/loaderSpec.js | 2 ++ test/service/controllerSpec.js | 41 +++++++++++++++++++++++++-- 5 files changed, 114 insertions(+), 12 deletions(-) diff --git a/src/loader.js b/src/loader.js index fdfdaebdd097..b25fc40e4086 100644 --- a/src/loader.js +++ b/src/loader.js @@ -167,13 +167,24 @@ function setupModuleLoader(window) { * @ngdoc method * @name angular.Module#filter * @methodOf angular.Module - * @param {string} name filter name + * @param {string} name Filter name. * @param {Function} filterFactory Factory function for creating new instance of filter. * @description * See {@link angular.module.ng.$filterProvider#register $filterProvider.register()}. */ filter: invokeLater('$filterProvider', 'register'), + /** + * @ngdoc method + * @name angular.Module#controller + * @methodOf angular.Module + * @param {string} name Controller name. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link angular.module.ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + /** * @ngdoc method * @name angular.Module#directive diff --git a/src/service/controller.js b/src/service/controller.js index 229ce14aa480..fa90f8cde828 100644 --- a/src/service/controller.js +++ b/src/service/controller.js @@ -1,6 +1,32 @@ 'use strict'; +/** + * @ngdoc object + * @name angular.module.ng.$controllerProvider + * @description + * The {@link angular.module.ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link angular.module.ng.$controllerProvider#register register} method. + */ function $ControllerProvider() { + var controllers = {}; + + + /** + * @ngdoc function + * @name angular.module.ng.$controllerProvider#register + * @methodOf angular.module.ng.$controllerProvider + * @param {string} name Controller name + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + controllers[name] = constructor; + }; + + this.$get = ['$injector', '$window', function($injector, $window) { /** @@ -8,8 +34,14 @@ function $ControllerProvider() { * @name angular.module.ng.$controller * @requires $injector * - * @param {Function|string} Class Constructor function of a controller to instantiate, or - * expression to read from current scope or window. + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * check `window[constructor]` on the global `window` object + * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * @@ -20,14 +52,17 @@ function $ControllerProvider() { * a service, so that one can override this service with {@link https://gist.github.com/1649788 * BC version}. */ - return function(Class, locals) { - if(isString(Class)) { - var expression = Class; - Class = getter(locals.$scope, expression, true) || getter($window, expression, true); - assertArgFn(Class, expression); + return function(constructor, locals) { + if(isString(constructor)) { + var name = constructor; + constructor = controllers.hasOwnProperty(name) + ? controllers[name] + : getter(locals.$scope, name, true) || getter($window, name, true); + + assertArgFn(constructor, name, true); } - return $injector.instantiate(Class, locals); + return $injector.instantiate(constructor, locals); }; }]; } diff --git a/test/directive/ngViewSpec.js b/test/directive/ngViewSpec.js index 52aefa3a5160..636e15a8f0a4 100644 --- a/test/directive/ngViewSpec.js +++ b/test/directive/ngViewSpec.js @@ -54,6 +54,25 @@ describe('ng-view', function() { }); + it('should support string controller declaration', function() { + var MyCtrl = jasmine.createSpy('MyCtrl'); + + module(function($controllerProvider, $routeProvider) { + $controllerProvider.register('MyCtrl', ['$scope', MyCtrl]); + $routeProvider.when('/foo', {controller: 'MyCtrl', template: '/tpl.html'}); + }); + + inject(function($route, $location, $rootScope, $templateCache) { + $templateCache.put('/tpl.html', [200, '
', {}]); + $location.path('/foo'); + $rootScope.$digest(); + + expect($route.current.controller).toBe('MyCtrl'); + expect(MyCtrl).toHaveBeenCalledWith(element.contents().scope()); + }); + }); + + it('should load content via xhr when route changes', function() { module(function($routeProvider) { $routeProvider.when('/foo', {template: 'myUrl1'}); diff --git a/test/loaderSpec.js b/test/loaderSpec.js index dff3787d503c..b2341a71ae38 100644 --- a/test/loaderSpec.js +++ b/test/loaderSpec.js @@ -38,6 +38,7 @@ describe('module loader', function() { value('k', 'v'). filter('f', 'ff'). directive('d', 'dd'). + controller('ctrl', 'ccc'). config('init2'). constant('abc', 123). run('runBlock')).toBe(myModule); @@ -52,6 +53,7 @@ describe('module loader', function() { ['$provide', 'value', ['k', 'v'] ], ['$filterProvider', 'register', ['f', 'ff'] ], ['$compileProvider', 'directive', ['d', 'dd'] ], + ['$controllerProvider', 'register', ['ctrl', 'ccc']], ['$injector', 'invoke', ['init2'] ] ]); expect(myModule._runBlocks).toEqual(['runBlock']); diff --git a/test/service/controllerSpec.js b/test/service/controllerSpec.js index 2c0f8c62df7d..91389013cc6a 100644 --- a/test/service/controllerSpec.js +++ b/test/service/controllerSpec.js @@ -1,12 +1,47 @@ 'use strict'; describe('$controller', function() { - var $controller; + var $controllerProvider, $controller; - beforeEach(inject(function($injector) { - $controller = $injector.get('$controller'); + beforeEach(module(function(_$controllerProvider_) { + $controllerProvider = _$controllerProvider_; })); + + beforeEach(inject(function(_$controller_) { + $controller = _$controller_; + })); + + + describe('provider', function() { + + it('should allow registration of controllers', function() { + var FooCtrl = function($scope) { $scope.foo = 'bar' }, + scope = {}, + ctrl; + + $controllerProvider.register('FooCtrl', FooCtrl); + ctrl = $controller('FooCtrl', {$scope: scope}); + + expect(scope.foo).toBe('bar'); + expect(ctrl instanceof FooCtrl).toBe(true); + }); + + + it('should allow registration of controllers annotated with arrays', function() { + var FooCtrl = function($scope) { $scope.foo = 'bar' }, + scope = {}, + ctrl; + + $controllerProvider.register('FooCtrl', ['$scope', FooCtrl]); + ctrl = $controller('FooCtrl', {$scope: scope}); + + expect(scope.foo).toBe('bar'); + expect(ctrl instanceof FooCtrl).toBe(true); + }); + }); + + it('should return instance of given controller class', function() { var MyClass = function() {}, ctrl = $controller(MyClass);