diff options
| -rw-r--r-- | src/ng/controller.js | 34 | ||||
| -rw-r--r-- | src/ng/directive/ngController.js | 73 | ||||
| -rw-r--r-- | src/ng/directive/ngView.js | 51 | ||||
| -rw-r--r-- | src/ng/route.js | 2 | ||||
| -rw-r--r-- | test/ng/controllerSpec.js | 11 | ||||
| -rw-r--r-- | test/ng/directive/ngControllerSpec.js | 23 | ||||
| -rw-r--r-- | test/ng/directive/ngViewSpec.js | 21 |
7 files changed, 181 insertions, 34 deletions
diff --git a/src/ng/controller.js b/src/ng/controller.js index 0474e766..cfd730e4 100644 --- a/src/ng/controller.js +++ b/src/ng/controller.js @@ -11,7 +11,8 @@ * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { - var controllers = {}; + var controllers = {}, + CNTRL_REG = /^(\w+)(\s+as\s+(\w+))?$/; /** @@ -56,17 +57,32 @@ function $ControllerProvider() { * a service, so that one can override this service with {@link https://gist.github.com/1649788 * BC version}. */ - 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); + return function(expression, locals) { + var instance, match, constructor, identifier; - assertArgFn(constructor, name, true); + if(isString(expression)) { + match = expression.match(CNTRL_REG), + constructor = match[1], + identifier = match[3]; + expression = controllers.hasOwnProperty(constructor) + ? controllers[constructor] + : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + + assertArgFn(expression, constructor, true); + } + + instance = $injector.instantiate(expression, locals); + + if (identifier) { + if (typeof locals.$scope !== 'object') { + throw new Error('Can not export controller as "' + identifier + '". ' + + 'No scope object provided!'); + } + + locals.$scope[identifier] = instance; } - return $injector.instantiate(constructor, locals); + return instance; }; }]; } diff --git a/src/ng/directive/ngController.js b/src/ng/directive/ngController.js index 438a0d87..be2f149f 100644 --- a/src/ng/directive/ngController.js +++ b/src/ng/directive/ngController.js @@ -21,7 +21,8 @@ * @scope * @param {expression} ngController Name of a globally accessible constructor function or an * {@link guide/expression expression} that on the current scope evaluates to a - * constructor function. + * constructor function. The controller instance can further be published into the scope + * by adding `as localName` the controller name attribute. * * @example * Here is a simple form for editing user contact information. Adding, removing, clearing, and @@ -29,10 +30,77 @@ * easily be called from the angular markup. Notice that the scope becomes the `this` for the * controller's instance. This allows for easy access to the view data from the controller. Also * notice that any changes to the data are automatically reflected in the View without the need - * for a manual update. + * for a manual update. The example is included in two different declaration styles based on + * your style preferences. <doc:example> <doc:source> <script> + function SettingsController() { + this.name = "John Smith"; + this.contacts = [ + {type: 'phone', value: '408 555 1212'}, + {type: 'email', value: 'john.smith@example.org'} ]; + }; + + SettingsController.prototype.greet = function() { + alert(this.name); + }; + + SettingsController.prototype.addContact = function() { + this.contacts.push({type: 'email', value: 'yourname@example.org'}); + }; + + SettingsController.prototype.removeContact = function(contactToRemove) { + var index = this.contacts.indexOf(contactToRemove); + this.contacts.splice(index, 1); + }; + + SettingsController.prototype.clearContact = function(contact) { + contact.type = 'phone'; + contact.value = ''; + }; + </script> + <div ng-controller="SettingsController as settings"> + Name: <input type="text" ng-model="settings.name"/> + [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> + Contact: + <ul> + <li ng-repeat="contact in settings.contacts"> + <select ng-model="contact.type"> + <option>phone</option> + <option>email</option> + </select> + <input type="text" ng-model="contact.value"/> + [ <a href="" ng-click="settings.clearContact(contact)">clear</a> + | <a href="" ng-click="settings.removeContact(contact)">X</a> ] + </li> + <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> + </ul> + </div> + </doc:source> + <doc:scenario> + it('should check controller', function() { + expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); + expect(element('.doc-example-live li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('.doc-example-live li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('.doc-example-live li:first a:contains("clear")').click(); + expect(element('.doc-example-live li:first input').val()).toBe(''); + + element('.doc-example-live li:last a:contains("add")').click(); + expect(element('.doc-example-live li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + </doc:scenario> + </doc:example> + + + + <doc:example> + <doc:source> + <script> function SettingsController($scope) { $scope.name = "John Smith"; $scope.contacts = [ @@ -93,6 +161,7 @@ }); </doc:scenario> </doc:example> + */ var ngControllerDirective = [function() { return { diff --git a/src/ng/directive/ngView.js b/src/ng/directive/ngView.js index 5b6d938b..8d7c87c5 100644 --- a/src/ng/directive/ngView.js +++ b/src/ng/directive/ngView.js @@ -23,7 +23,7 @@ * @example <example module="ngView" animations="true"> <file name="index.html"> - <div ng-controller="MainCntl"> + <div ng-controller="MainCntl as main"> Choose: <a href="Book/Moby">Moby</a> | <a href="Book/Moby/ch/1">Moby: Ch1</a> | @@ -37,26 +37,26 @@ ng-animate="{enter: 'example-enter', leave: 'example-leave'}"></div> <hr /> - <pre>$location.path() = {{$location.path()}}</pre> - <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> - <pre>$route.current.params = {{$route.current.params}}</pre> - <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> - <pre>$routeParams = {{$routeParams}}</pre> + <pre>$location.path() = {{main.$location.path()}}</pre> + <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> + <pre>$route.current.params = {{main.$route.current.params}}</pre> + <pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre> + <pre>$routeParams = {{main.$routeParams}}</pre> </div> </file> <file name="book.html"> <div> - controller: {{name}}<br /> - Book Id: {{params.bookId}}<br /> + controller: {{book.name}}<br /> + Book Id: {{book.params.bookId}}<br /> </div> </file> <file name="chapter.html"> <div> - controller: {{name}}<br /> - Book Id: {{params.bookId}}<br /> - Chapter Id: {{params.chapterId}} + controller: {{chapter.name}}<br /> + Book Id: {{chapter.params.bookId}}<br /> + Chapter Id: {{chapter.params.chapterId}} </div> </file> @@ -104,31 +104,33 @@ angular.module('ngView', [], function($routeProvider, $locationProvider) { $routeProvider.when('/Book/:bookId', { templateUrl: 'book.html', - controller: BookCntl + controller: BookCntl, + controllerAlias: 'book' }); $routeProvider.when('/Book/:bookId/ch/:chapterId', { templateUrl: 'chapter.html', - controller: ChapterCntl + controller: ChapterCntl, + controllerAlias: 'chapter' }); // configure html5 to get links working on jsfiddle $locationProvider.html5Mode(true); }); - function MainCntl($scope, $route, $routeParams, $location) { - $scope.$route = $route; - $scope.$location = $location; - $scope.$routeParams = $routeParams; + function MainCntl($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; } - function BookCntl($scope, $routeParams) { - $scope.name = "BookCntl"; - $scope.params = $routeParams; + function BookCntl($routeParams) { + this.name = "BookCntl"; + this.params = $routeParams; } - function ChapterCntl($scope, $routeParams) { - $scope.name = "ChapterCntl"; - $scope.params = $routeParams; + function ChapterCntl($routeParams) { + this.name = "ChapterCntl"; + this.params = $routeParams; } </file> @@ -202,6 +204,9 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c if (current.controller) { locals.$scope = lastScope; controller = $controller(current.controller, locals); + if (current.controllerAlias) { + lastScope[current.controllerAlias] = controller; + } element.children().data('$ngControllerController', controller); } diff --git a/src/ng/route.js b/src/ng/route.js index b52f4351..7651a806 100644 --- a/src/ng/route.js +++ b/src/ng/route.js @@ -44,6 +44,8 @@ function $RouteProvider(){ * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly * created scope or the name of a {@link angular.Module#controller registered controller} * if passed as a string. + * - `controllerAlias` – `{sttring=}` – A controller alias name. If present the controller will be + * published to scope under the `controllerAlias` name. * - `template` – `{string=|function()=}` – html template as a string or function that returns * an html template as a string which should be used by {@link ng.directive:ngView ngView} or * {@link ng.directive:ngInclude ngInclude} directives. diff --git a/test/ng/controllerSpec.js b/test/ng/controllerSpec.js index 2abcace9..e34463b0 100644 --- a/test/ng/controllerSpec.js +++ b/test/ng/controllerSpec.js @@ -88,4 +88,15 @@ describe('$controller', function() { expect(ctrl.$scope).toBe(scope); }); + + + it('should publish controller instance into scope', function() { + var scope = {}; + + $controllerProvider.register('FooCtrl', function() { this.mark = 'foo'; }); + + var foo = $controller('FooCtrl as foo', {$scope: scope}); + expect(scope.foo).toBe(foo); + expect(scope.foo.mark).toBe('foo'); + }); }); diff --git a/test/ng/directive/ngControllerSpec.js b/test/ng/directive/ngControllerSpec.js index ab85c569..402ddf09 100644 --- a/test/ng/directive/ngControllerSpec.js +++ b/test/ng/directive/ngControllerSpec.js @@ -3,6 +3,11 @@ describe('ngController', function() { var element; + beforeEach(module(function($controllerProvider) { + $controllerProvider.register('PublicModule', function() { + this.mark = 'works'; + }); + })); beforeEach(inject(function($window) { $window.Greeter = function($scope) { // private stuff (not exported to scope) @@ -27,6 +32,10 @@ describe('ngController', function() { $window.Child = function($scope) { $scope.name = 'Adam'; }; + + $window.Public = function() { + this.mark = 'works'; + } })); afterEach(function() { @@ -41,6 +50,20 @@ describe('ngController', function() { })); + it('should publish controller into scope', inject(function($compile, $rootScope) { + element = $compile('<div ng-controller="Public as p">{{p.mark}}</div>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('works'); + })); + + + it('should publish controller into scope from module', inject(function($compile, $rootScope) { + element = $compile('<div ng-controller="PublicModule as p">{{p.mark}}</div>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('works'); + })); + + it('should allow nested controllers', inject(function($compile, $rootScope) { element = $compile('<div ng-controller="Greeter"><div ng-controller="Child">{{greet(name)}}</div></div>')($rootScope); $rootScope.$digest(); diff --git a/test/ng/directive/ngViewSpec.js b/test/ng/directive/ngViewSpec.js index e9d53110..3150c040 100644 --- a/test/ng/directive/ngViewSpec.js +++ b/test/ng/directive/ngViewSpec.js @@ -55,6 +55,27 @@ describe('ngView', function() { }); + it('should instantiate controller with an alias', function() { + var log = [], controllerScope, + Ctrl = function($scope) { + this.name = 'alias'; + controllerScope = $scope; + }; + + module(function($compileProvider, $routeProvider) { + $routeProvider.when('/some', {templateUrl: '/tpl.html', controller: Ctrl, controllerAlias: 'ctrl'}); + }); + + inject(function($route, $rootScope, $templateCache, $location) { + $templateCache.put('/tpl.html', [200, '<div></div>', {}]); + $location.path('/some'); + $rootScope.$digest(); + + expect(controllerScope.ctrl.name).toBe('alias'); + }); + }); + + it('should support string controller declaration', function() { var MyCtrl = jasmine.createSpy('MyCtrl'); |
