diff --git a/src/ng/compile.js b/src/ng/compile.js index 98c233206f65..7a6121f6e98f 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -928,6 +928,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return this; }; + this.$$componentControllers = createMap(); /** * @ngdoc method * @name $compileProvider#component @@ -1052,6 +1053,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. */ this.component = function registerComponent(name, options) { + var controller = options.controller || function() {}; + var ident = identifierForController(options.controller) || options.controllerAs || '$ctrl'; + this.$$componentControllers[name] = { controller: controller, ident: ident}; + function factory($injector) { function makeInjectable(fn) { if (isFunction(fn) || isArray(fn)) { @@ -1065,8 +1070,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var template = (!options.template && !options.templateUrl ? '' : options.template); return { - controller: options.controller || function() {}, - controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', + controller: controller, + controllerAs: ident, template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), transclude: options.transclude, diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 6997d83a5dd0..3910cca15baa 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2161,6 +2161,34 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { }; }]; +/** + * @ngdoc service + * @name $componentController + * @description + * A service that can be used to create instances of component controllers. + *
+ * Be aware that the controller will be instantiated and attached to the scope as specified in + * the component definition object. That means that you must always provide a `$scope` object + * in the `locals` param. + *
+ * @param {string} componentName the name of the component whose controller we want to instantiate + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @param {string=} ident Override the property name to use when attaching the controller to the scope. + * @return {Object} Instance of requested controller. + */ +angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { + return { + $get: ['$controller', function($controller) { + return function $componentController(componentName, locals, bindings, ident) { + var controllerInfo = $compileProvider.$$componentControllers[componentName]; + return $controller(controllerInfo.controller, locals, bindings, ident || controllerInfo.ident); + }; + }] + }; +}]; + /** * @ngdoc module @@ -2184,7 +2212,8 @@ angular.module('ngMock', ['ng']).provider({ $log: angular.mock.$LogProvider, $interval: angular.mock.$IntervalProvider, $httpBackend: angular.mock.$HttpBackendProvider, - $rootElement: angular.mock.$RootElementProvider + $rootElement: angular.mock.$RootElementProvider, + $componentController: angular.mock.$ComponentControllerProvider }).config(['$provide', function($provide) { $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); $provide.decorator('$$rAF', angular.mock.$RAFDecorator); diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 14fb543eb374..434394143e24 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1857,6 +1857,88 @@ describe('ngMock', function() { }); }); }); + + + describe('$componentController', function() { + it('should instantiate a simple controller defined inline in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($compileProvider) { + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); + }); + }); + + it('should instantiate a controller with $$inject annotation defined inline in a component', function() { + function TestController(x, y, z) { + this.$scope = x; + this.a = y; + this.b = z; + } + TestController.$inject = ['$scope', 'a', 'b']; + module(function($compileProvider) { + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); + }); + }); + + it('should instantiate a named controller defined in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($controllerProvider, $compileProvider) { + $controllerProvider.register('TestController', TestController); + $compileProvider.component('test', { + controller: 'TestController' + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); + }); + }); + + it('should instantiate a named controller with `controller as` syntax defined in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($controllerProvider, $compileProvider) { + $controllerProvider.register('TestController', TestController); + $compileProvider.component('test', { + controller: 'TestController as testCtrl' + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.testCtrl).toBe(ctrl); + }); + }); + }); });