diff --git a/docs/content/error/$componentController/multicomp.ngdoc b/docs/content/error/$componentController/multicomp.ngdoc new file mode 100644 index 000000000000..ad69e528c333 --- /dev/null +++ b/docs/content/error/$componentController/multicomp.ngdoc @@ -0,0 +1,17 @@ +@ngdoc error +@name $componentController:multicomp +@fullName Multiple components defined +@description + +This error occurs when multiple components or component-like directives +are defined with the same name, and processing them would result in a +collision or an unsupported configuration. + + +To resolve this issue remove one of the directives or controllers which is causing the collision. + +Component-like directives are those that: + +* Have a controller +* Have defined controllerAs +* Have restrict to 'E' diff --git a/docs/content/error/$componentController/nocomp.ngdoc b/docs/content/error/$componentController/nocomp.ngdoc new file mode 100644 index 000000000000..46ac5dd81274 --- /dev/null +++ b/docs/content/error/$componentController/nocomp.ngdoc @@ -0,0 +1,11 @@ +@ngdoc error +@name $componentController:nocomp +@fullName No component available +@description + +This error occurs when it is requested a component that it is not defined. + + +To resolve this issue create the component which the requested name or change +the name to match an existing component name. + diff --git a/src/ng/compile.js b/src/ng/compile.js index 7a6121f6e98f..1049aa6c8c53 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -928,7 +928,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return this; }; - this.$$componentControllers = createMap(); /** * @ngdoc method * @name $compileProvider#component @@ -1054,8 +1053,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { */ 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) { @@ -1071,7 +1068,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var template = (!options.template && !options.templateUrl ? '' : options.template); return { controller: controller, - controllerAs: ident, + controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 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 6a391832c638..b26a79bab6a4 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2182,11 +2182,27 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { * @return {Object} Instance of requested controller. */ angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { + var minErr = angular.$$minErr('$componentController'); return { - $get: ['$controller', function($controller) { + $get: ['$controller','$injector', function($controller,$injector) { return function $componentController(componentName, locals, bindings, ident) { - var controllerInfo = $compileProvider.$$componentControllers[componentName]; - return $controller(controllerInfo.controller, locals, bindings, ident || controllerInfo.ident); + // get all directives associated to the component name + var directives = $injector.get(componentName + 'Directive'); + // look for those directives that are components + var candidateDirectives = directives.filter(function(directiveInfo) { + // components have controller, controllerAs and restrict:'E' + return directiveInfo.controller && directiveInfo.controllerAs && directiveInfo.restrict === 'E'; + }); + // check if valid directives found + if (candidateDirectives.length === 0) { + throw minErr('nocomp','No \'{0}\' component found', componentName); + } + if (candidateDirectives.length > 1) { + throw minErr('multicomp','Found {0} directive candidates for component \'{1}\'', candidateDirectives.length, componentName); + } + // get the info of the component + var directiveInfo = candidateDirectives[0]; + return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs); }; }] }; diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index b7d5fa9441b9..60c6651715ef 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1938,6 +1938,125 @@ describe('ngMock', function() { expect($scope.testCtrl).toBe(ctrl); }); }); + + it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name but not restricted to \'E\'', function() { + function TestController() { + this.r = 6779; + } + module(function($compileProvider) { + $compileProvider.directive('test', function() { + return { restrict: 'A' }; + }); + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var ctrl = $componentController('test', { $scope: {} }); + expect(ctrl).toEqual({ r: 6779 }); + }); + }); + + it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name and restricted to \'E\' but no controller', function() { + function TestController() { + this.r = 22926; + } + module(function($compileProvider) { + $compileProvider.directive('test', function() { + return { restrict: 'E' }; + }); + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var ctrl = $componentController('test', { $scope: {} }); + expect(ctrl).toEqual({ r: 22926 }); + }); + }); + + it('should instantiate the controller of the directive with controller, controllerAs and restrict:\'E\' if there are more directives', function() { + function TestController() { + this.r = 18842; + } + module(function($compileProvider) { + $compileProvider.directive('test', function() { + return { }; + }); + $compileProvider.directive('test', function() { + return { + restrict: 'E', + controller: TestController, + controllerAs: '$ctrl' + }; + }); + }); + inject(function($componentController, $rootScope) { + var ctrl = $componentController('test', { $scope: {} }); + expect(ctrl).toEqual({ r: 18842 }); + }); + }); + + it('should fail if there is no directive with restrict:\'E\' and controller', function() { + function TestController() { + this.r = 31145; + } + module(function($compileProvider) { + $compileProvider.directive('test', function() { + return { + restrict: 'AC', + controller: TestController + }; + }); + $compileProvider.directive('test', function() { + return { + restrict: 'E', + controller: TestController + }; + }); + $compileProvider.directive('test', function() { + return { + restrict: 'EA', + controller: TestController, + controllerAs: '$ctrl' + }; + }); + $compileProvider.directive('test', function() { + return { restrict: 'E' }; + }); + }); + inject(function($componentController, $rootScope) { + expect(function() { + $componentController('test', { $scope: {} }); + }).toThrowMinErr('$componentController','nocomp', "No 'test' component found"); + }); + }); + + it('should fail if there more than two components with same name', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($compileProvider) { + $compileProvider.directive('test', function() { + return { + restrict: 'E', + controller: TestController, + controllerAs: '$ctrl' + }; + }); + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + expect(function() { + var $scope = {}; + $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + }).toThrowMinErr('$componentController','multicomp',"Found 2 directive candidates for component 'test'"); + }); + }); }); });