diff options
Diffstat (limited to 'docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc')
| -rw-r--r-- | docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc | 357 | 
1 files changed, 202 insertions, 155 deletions
| diff --git a/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc index 880601fa..847f7f20 100644 --- a/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc +++ b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc @@ -2,24 +2,31 @@  @name Developer Guide: About MVC in Angular: Understanding the Controller Component  @description -In Angular, a controller is a JavaScript function(type/class) that is used to augment instances of -angular {@link scope Scope}, excluding the root scope. +# Understanding Controllers -Use controllers to: +In Angular, a Controller is a JavaScript **constructor function** that is used to augment the  +{@link scope Angular Scope}. -- Set up the initial state of a scope object. -- Add behavior to the scope object. +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`. -# Setting up the initial state of a scope object +Use Controllers to: -Typically, when you create an application you need to set up an initial state for an Angular scope. +- Set up the initial state of the `$scope` object. +- Add behavior to the `$scope` object. -Angular applies (in the sense of JavaScript's `Function#apply`) the controller constructor function -to a new Angular scope object, which sets up an initial scope state. This means that Angular never -creates instances of the controller type (by invoking the `new` operator on the controller -constructor). Constructors are always applied to an existing scope object. +# Setting up the initial state of a `$scope` object -You set up the initial state of a scope by creating model properties. For example: +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) { @@ -27,12 +34,18 @@ You set up the initial state of a scope by creating model properties. For exampl      }  </pre> -The `GreetingCtrl` controller creates a `greeting` model which can be referred to in a template. +Once the Controller has been attached to the DOM, the `greeting` property can be data-bound to the +template: -**NOTE**: Many of the examples in the documentation show the creation of functions -in the global scope. This is only for demonstration purposes - in a real -application you should use the `.controller` method of your Angular module for -your application as follows: +<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',[]); @@ -42,40 +55,62 @@ your application as follows:      }]);  </pre> -Note also that we use the array notation to explicitly specify the dependency -of the controller on the `$scope` service provided by Angular. +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 -Behavior on an Angular scope object is in the form of scope method properties available to the -template/view. This behavior interacts with and modifies the application model. +In order to react to events or execute computation in the view we must provide behavior to the +scope. We add behavior 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 dev_guide.mvc.understanding_model Model} section of this guide, any -objects (or primitives) assigned to the scope become model properties. Any functions assigned to +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 +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. +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: +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 +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 dev_guide.templates.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 +- 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). @@ -86,196 +121,208 @@ You can associate controllers with scope objects implicitly via the {@link api/n  directive} or {@link api/ng.$route $route service}. -## Controller Constructor and Methods Example +## Simple Spicy Controller Example -To illustrate how the controller component works in angular, let's create a little app with the +To illustrate further how Controller components work in Angular, let's create a little app with the  following components:  - A {@link dev_guide.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` +- 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. - -## A Spicy Controller Example - -<pre> -<body ng-app="SpicyApp" ng-controller="SpicyCtrl"> - <button ng-click="chiliSpicy()">Chili</button> - <button ng-click="jalapenoSpicy()">Jalapeño</button> - <p>The food is {{spice}} spicy!</p> -</body> - -var myApp = angular.module('SpicyApp', []); - -myApp.controller('SpicyCtrl', ['$scope', function($scope){ -    $scope.spicy = 'very'; -     -    $scope.chiliSpicy = function() { -        $scope.spice = 'chili'; -    }; -     -    $scope.jalapenoSpicy = function() { -        $scope.spice = 'jalapeño'; -    }; -}]); -</pre> +<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.spicy = '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 `ngController` directive is used to (implicitly) create a scope for our template, and the -scope is augmented (managed) by the `SpicyCtrl` controller. +- 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 (the `chiliSpicy` method) -- Both controller methods are available in the template (for the `body` element and and its -children). -- NB:  Previous versions of Angular (pre 1.0 RC) allowed you to use `this` interchangeably with -the $scope method, but this is no longer the case.  Inside of methods defined on the scope -`this` and $scope are interchangeable (angular sets `this` to $scope), but not otherwise -inside your controller constructor. -- NB:  Previous versions of Angular (pre 1.0 RC) added prototype methods into the scope -automatically, but this is no longer the case; all methods need to be added manually to -the scope. +- 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. -## Controller Method Arguments Example - -<pre> -<body ng-app="SpicyApp" 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> -</body> - -var myApp = angular.module('SpicyApp', []); - -myApp.controller('SpicyCtrl', ['$scope', function($scope){ -    $scope.customSpice = "wasabi"; -    $scope.spice = 'very'; -     -    $scope.spicy = function(spice){ -        $scope.spice = spice; -    }; -}]); - -</pre> +<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 +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. - -## Controller Inheritance Example - -Controller inheritance in Angular is based on {@link api/ng.$rootScope.Scope Scope} inheritance. Let's -have a look at an example: - -<pre> -<body ng-app="MyApp" ng-controller="MainCtrl"> - <p>Good {{timeOfDay}}, {{name}}!</p> - <div ng-controller="ChildCtrl"> -  <p>Good {{timeOfDay}}, {{name}}!</p> -  <p ng-controller="BabyCtrl">Good {{timeOfDay}}, {{name}}!</p> - </div> -</body> - -var myApp = angular.module('MyApp', []) - -.controller('MainCtrl', ['$scope', function($scope){ -  $scope.timeOfDay = 'morning'; -  $scope.name = 'Nikki'; -}]) -.controller('ChildCtrl', ['$scope', function($scope){ -  $scope.name = 'Mattie'; -}]) -.controller('BabyCtrl', ['$scope', function($scope){ -  $scope.timeOfDay = 'evening'; -  $scope.name = 'Gingerbreak Baby'; -}]); -</pre> - -Notice how we nested three `ngController` directives in our template. This template construct will -result in 4 scopes being created for our view: +## 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="BabyCtrl"> +            <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('BabyCtrl', ['$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` models -- The `ChildCtrl` scope, which shadows the `name` model from the previous scope and inherits the -`timeOfDay` model -- The `BabyCtrl` scope, which shadows both the `timeOfDay` model defined in `MainCtrl` and `name` -model defined in the ChildCtrl - -Inheritance works between controllers in the same way as it does with models. So in our previous -examples, all of the models could be replaced with controller methods that return string values. +- 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 `BabyCtrl` scope, which overrides (hides) both the `timeOfDay` property defined in `MainCtrl` +and the `name` property defined in `ChildCtrl` -Note: Standard prototypical inheritance between two controllers doesn't work as one might expect, -because as we mentioned earlier, controllers are not instantiated directly by Angular, but rather -are applied to the scope object. +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 `$rootScope` and `$controller` +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 Function: +**Controller Definition:**  <pre> -function myController($scope) { -   $scope.spices = [{"name":"pasilla", "spiciness":"mild"}, -                  {"name":"jalapeno", "spiceiness":"hot hot hot!"}, -                  {"name":"habanero", "spiceness":"LAVA HOT!!"}]; +    var myApp = angular.module('myApp',[]); -   $scope.spice = "habanero"; -} +    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: +**Controller Test:**  <pre>  describe('myController function', function() {    describe('myController', function() { -    var scope; +    var $scope; + +    beforeEach(module('myApp'));      beforeEach(inject(function($rootScope, $controller) { -      scope = $rootScope.$new(); -      var ctrl = $controller(myController, {$scope: scope}); +      $scope = $rootScope.$new(); +      $controller('MyController', {$scope: $scope});      }));      it('should create "spices" model with 3 spices', function() { -      expect(scope.spices.length).toBe(3); +      expect($scope.spices.length).toBe(3);      });      it('should set the default value of spice', function() { -      expect(scope.spice).toBe('habanero'); +      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. +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, babyScope; +    beforeEach(module('myApp')); +      beforeEach(inject(function($rootScope, $controller) {          mainScope = $rootScope.$new(); -        var mainCtrl = $controller(MainCtrl, {$scope: mainScope}); +        $controller('MainCtrl', {$scope: mainScope});          childScope = mainScope.$new(); -        var childCtrl = $controller(ChildCtrl, {$scope: childScope}); +        $controller('ChildCtrl', {$scope: childScope});          babyScope = childScope.$new(); -        var babyCtrl = $controller(BabyCtrl, {$scope: babyScope}); +        $controller('BabyCtrl', {$scope: babyScope});      }));      it('should have over and selected', function() { | 
