mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-20 00:10:26 +00:00
feat(controller): support as instance syntax
Support ng-controller="MyController as my" syntax
which publishes the controller instance to the
current scope.
Also supports exporting a controller defined with route:
````javascript
angular.module('routes', [], function($routeProvider) {
$routeProvider.when('/home', {controller: 'Ctrl as home', templateUrl: '...'});
});
````
This commit is contained in:
parent
021bdf3922
commit
cd38cbf975
7 changed files with 181 additions and 34 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
return $injector.instantiate(constructor, locals);
|
||||
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 instance;
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,8 +30,75 @@
|
|||
* 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) {
|
||||
|
|
@ -93,6 +161,7 @@
|
|||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
*/
|
||||
var ngControllerDirective = [function() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue