aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPete Bacon Darwin2013-10-11 12:59:39 +0100
committerPete Bacon Darwin2013-10-11 12:59:39 +0100
commit116fac05628807acec6baf331a31b58f26f774a7 (patch)
tree5959f3b2c3953faa110675064f598af97889927b
parentc3024254b6535486097648503e2ddae5562b6ddc (diff)
downloadangular.js-116fac05628807acec6baf331a31b58f26f774a7.tar.bz2
docs(guide/controller): improve guidance and examples
Remove mention of global controller functions Convert larger examples to runnable demos Remove mention of pre-1.0 controllers, in particular discussion of controller inheritance. TODO: Probably could do with updating to explain the "controller as" syntax at some point. Closes: #4373
-rw-r--r--docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc357
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() {