diff options
Diffstat (limited to 'docs/content/guide/controller.ngdoc')
| -rw-r--r-- | docs/content/guide/controller.ngdoc | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/docs/content/guide/controller.ngdoc b/docs/content/guide/controller.ngdoc new file mode 100644 index 00000000..bc5c9504 --- /dev/null +++ b/docs/content/guide/controller.ngdoc @@ -0,0 +1,340 @@ +@ngdoc overview +@name Controllers +@description + +# Understanding Controllers + +In Angular, a Controller is a JavaScript **constructor function** that is used to augment the +{@link scope Angular Scope}. + +When a Controller is attached to the DOM via the {@link api/ng.directive:ngController ng-controller} +directive, Angular will instantiate a new Controller object, using the specified Controller's +**constructor function**. A new **child scope** will be available as an injectable parameter to the +Controller's constructor function as `$scope`. + +Use Controllers to: + +- Set up the initial state of the `$scope` object. +- Add behavior to the `$scope` object. + +# Setting up the initial state of a `$scope` object + +Typically, when you create an application you need to set up the initial state for the Angular +`$scope`. You set up the initial state of a scope by attaching properties to the `$scope` object. +The properties contain the **view model** (the model that will be presented by the view). All the +`$scope` properties will be available to the template at the point in the DOM where the Controller +is registered. + +The following example shows a very simple constructor function for a Controller, `GreetingCtrl`, +which attaches a `greeting` property containing the string `'Hola!'` to the `$scope`: + +<pre> + function GreetingCtrl($scope) { + $scope.greeting = 'Hola!'; + } +</pre> + +Once the Controller has been attached to the DOM, the `greeting` property can be data-bound to the +template: + +<pre> + <div ng-controller="GreetingCtrl"> + {{ greeting }} + </div> +</pre> + +**NOTE**: Although Angular allows you to create Controller functions in the global scope, this is +not recommended. In a real application you should use the `.controller` method of your +{@link module Angular Module} for your application as follows: + +<pre> + var myApp = angular.module('myApp',[]); + + myApp.controller('GreetingCtrl', ['$scope', function($scope) { + $scope.greeting = 'Hola!'; + }]); +</pre> + +We have used an **inline injection annotation** to explicitly specify the dependency +of the Controller on the `$scope` service provided by Angular. See the guide on +{@link http://docs.angularjs.org/guide/di Dependency Injection} for more information. + + +# Adding Behavior to a Scope Object + +In order to react to events or execute computation in the view we must provide behavior to the +scope. We add behavior to the scope by attaching methods to the `$scope` object. These methods are +then available to be called from the template/view. + +The following example uses a Controller to add a method to the scope, which doubles a number: + +<pre> + var myApp = angular.module('myApp',[]); + + myApp.controller('DoubleCtrl', ['$scope', function($scope) { + $scope.double = function(value) { return value * 2; }; + }]); +</pre> + +Once the Controller has been attached to the DOM, the `double` method can be invoked in an Angular +expression in the template: + +<pre> + <div ng-controller="DoubleCtrl"> + Two times <input ng-model="num"> equals {{ double(num) }} + </div> +</pre> + +As discussed in the {@link concepts Concepts} section of this guide, any +objects (or primitives) assigned to the scope become model properties. Any methods assigned to +the scope are available in the template/view, and can be invoked via angular expressions +and `ng` event handler directives (e.g. {@link api/ng.directive:ngClick ngClick}). + +# Using Controllers Correctly + +In general, a Controller shouldn't try to do too much. It should contain only the business logic +needed for a single view. + +The most common way to keep Controllers slim is by encapsulating work that doesn't belong to +controllers into services and then using these services in Controllers via dependency injection. +This is discussed in the {@link di Dependency Injection} {@link dev_guide.services +Services} sections of this guide. + +Do not use Controllers for: + +- Any kind of DOM manipulation — Controllers should contain only business logic. DOM +manipulation (the presentation logic of an application) is well known for being hard to test. +Putting any presentation logic into Controllers significantly affects testability of the business +logic. Angular offers {@link databinding databinding} for automatic DOM manipulation. If +you have to perform your own manual DOM manipulation, encapsulate the presentation logic in +{@link guide/directive directives}. +- Input formatting — Use {@link forms angular form controls} instead. +- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead. +- Sharing stateless or stateful code across Controllers — Use {@link dev_guide.services angular +services} instead. +- Managing the life-cycle of other components (for example, to create service instances). + + +# Associating Controllers with Angular Scope Objects + +You can associate Controllers with scope objects implicitly via the {@link api/ng.directive:ngController ngController +directive} or {@link api/ngRoute.$route $route service}. + + +## Simple Spicy Controller Example + +To illustrate further how Controller components work in Angular, let's create a little app with the +following components: + +- A {@link templates template} with two buttons and a simple message +- A model consisting of a string named `spice` +- A Controller with two functions that set the value of `spice` + +The message in our template contains a binding to the `spice` model, which by default is set to the +string "very". Depending on which button is clicked, the `spice` model is set to `chili` or +`jalapeño`, and the message is automatically updated by data-binding. + +<doc:example module="spicyApp1"> + <doc:source> + <div ng-app="spicyApp1" ng-controller="SpicyCtrl"> + <button ng-click="chiliSpicy()">Chili</button> + <button ng-click="jalapenoSpicy()">Jalapeño</button> + <p>The food is {{spice}} spicy!</p> + </div> + <script> + var myApp = angular.module('spicyApp1', []); + + myApp.controller('SpicyCtrl', ['$scope', function($scope){ + $scope.spice = 'very'; + + $scope.chiliSpicy = function() { + $scope.spice = 'chili'; + }; + + $scope.jalapenoSpicy = function() { + $scope.spice = 'jalapeño'; + }; + }]); + </script> + </doc:source> +</doc:example> + +Things to notice in the example above: + +- The `ng-controller` directive is used to (implicitly) create a scope for our template, and the +scope is augmented (managed) by the `SpicyCtrl` Controller. +- `SpicyCtrl` is just a plain JavaScript function. As an (optional) naming convention the name +starts with capital letter and ends with "Ctrl" or "Controller". +- Assigning a property to `$scope` creates or updates the model. +- Controller methods can be created through direct assignment to scope (see the `chiliSpicy` method) +- The Controller methods and properties are available in the template (for the `<div>` element and +and its children). + +## Spicy Arguments Example + +Controller methods can also take arguments, as demonstrated in the following variation of the +previous example. + +<doc:example module="spicyApp2"> + <doc:source> + <div ng-app="spicyApp2" ng-controller="SpicyCtrl"> + <input ng-model="customSpice"> + <button ng-click="spicy('chili')">Chili</button> + <button ng-click="spicy(customSpice)">Custom spice</button> + <p>The food is {{spice}} spicy!</p> + </div> + <script> + var myApp = angular.module('spicyApp2', []); + + myApp.controller('SpicyCtrl', ['$scope', function($scope){ + $scope.customSpice = "wasabi"; + $scope.spice = 'very'; + + $scope.spicy = function(spice){ + $scope.spice = spice; + }; + }]); + </script> +</doc:source> +</doc:example> + +Notice that the `SpicyCtrl` Controller now defines just one method called `spicy`, which takes one +argument called `spice`. The template then refers to this Controller method and passes in a string +constant `'chili'` in the binding for the first button and a model property `spice` (bound to an +input box) in the second button. + +## Scope Inheritance Example + +It is common to attach Controllers at different levels of the DOM hierarchy. Since the +{@link api/ng.directive:ngController ng-controller} directive creates a new child scope, we get a +hierarchy of scopes that inherit from each other. The `$scope` that each Controller receives will +have access to properties and methods defined by Controllers higher up the hierarchy. +See {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes Understanding Scopes} for +more information about scope inheritance. + +<doc:example module="scopeInheritance"> + <doc:source> + <div ng-app="scopeInheritance" class="spicy"> + <div ng-controller="MainCtrl"> + <p>Good {{timeOfDay}}, {{name}}!</p> + + <div ng-controller="ChildCtrl"> + <p>Good {{timeOfDay}}, {{name}}!</p> + + <div ng-controller="GrandChildCtrl"> + <p>Good {{timeOfDay}}, {{name}}!</p> + </div> + </div> + </div> + </div> + <style> + div.spicy div { + padding: 10px; + border: solid 2px blue; + } + </style> + <script> + var myApp = angular.module('scopeInheritance', []); + myApp.controller('MainCtrl', ['$scope', function($scope){ + $scope.timeOfDay = 'morning'; + $scope.name = 'Nikki'; + }]); + myApp.controller('ChildCtrl', ['$scope', function($scope){ + $scope.name = 'Mattie'; + }]); + myApp.controller('GrandChildCtrl', ['$scope', function($scope){ + $scope.timeOfDay = 'evening'; + $scope.name = 'Gingerbreak Baby'; + }]); + </script> + </doc:source> +</doc:example> + +Notice how we nested three `ng-controller` directives in our template. This will result in four +scopes being created for our view: + +- The root scope +- The `MainCtrl` scope, which contains `timeOfDay` and `name` properties +- The `ChildCtrl` scope, which inherits the `timeOfDay` property but overrides (hides) the `name` +property from the previous +- The `GrandChildCtrl` scope, which overrides (hides) both the `timeOfDay` property defined in `MainCtrl` +and the `name` property defined in `ChildCtrl` + +Inheritance works with methods in the same way as it does with properties. So in our previous +examples, all of the properties could be replaced with methods that return string values. + + +## Testing Controllers + +Although there are many ways to test a Controller, one of the best conventions, shown below, +involves injecting the {@link api/ng.$rootScope $rootScope} and {@link api/ng.$controller $controller}: + +**Controller Definition:** +<pre> + var myApp = angular.module('myApp',[]); + + myApp.controller('MyController', function($scope) { + $scope.spices = [{"name":"pasilla", "spiciness":"mild"}, + {"name":"jalapeno", "spiceiness":"hot hot hot!"}, + {"name":"habanero", "spiceness":"LAVA HOT!!"}]; + $scope.spice = "habanero"; + }); +</pre> + +**Controller Test:** +<pre> +describe('myController function', function() { + + describe('myController', function() { + var $scope; + + beforeEach(module('myApp')); + + beforeEach(inject(function($rootScope, $controller) { + $scope = $rootScope.$new(); + $controller('MyController', {$scope: $scope}); + })); + + it('should create "spices" model with 3 spices', function() { + expect($scope.spices.length).toBe(3); + }); + + it('should set the default value of spice', function() { + expect($scope.spice).toBe('habanero'); + }); + }); +}); +</pre> + + +If you need to test a nested Controller you need to create the same scope hierarchy +in your test that exists in the DOM: + +<pre> +describe('state', function() { + var mainScope, childScope, grandChildScope; + + beforeEach(module('myApp')); + + beforeEach(inject(function($rootScope, $controller) { + mainScope = $rootScope.$new(); + $controller('MainCtrl', {$scope: mainScope}); + childScope = mainScope.$new(); + $controller('ChildCtrl', {$scope: childScope}); + grandChildScope = childScope.$new(); + $controller('GrandChildCtrl', {$scope: grandChildScope}); + })); + + it('should have over and selected', function() { + expect(mainScope.timeOfDay).toBe('morning'); + expect(mainScope.name).toBe('Nikki'); + expect(childScope.timeOfDay).toBe('morning'); + expect(childScope.name).toBe('Mattie'); + expect(grandChildScope.timeOfDay).toBe('evening'); + expect(grandChildScope.name).toBe('Gingerbreak Baby'); + }); +}); +</pre> + + + |
