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 | 323 | 
1 files changed, 323 insertions, 0 deletions
| diff --git a/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc new file mode 100644 index 00000000..18e74edc --- /dev/null +++ b/docs/content/guide/dev_guide.mvc.understanding_controller.ngdoc @@ -0,0 +1,323 @@ +@workInProgress +@ngdoc overview +@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 dev_guide.scopes Scope}, excluding the root scope. When you or angular create a new +child scope object via the {@link api/angular.scope.$new scope.$new} API        , there is an +option to pass in a controller as a method argument. This will tell angular to associate the +controller with the new scope and to augment its behavior. + + +Use controllers to: + + +- Set up the initial state of a 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 an initial state for an angular scope. + + +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. + + +You set up the initial state of a scope by creating model properties. For example: + + +function GreetingCtrl() { + this.greeting = 'Hola!'; +} + + +The `GreetingCtrl` controller creates a `greeting` model which can be referred to in a template. + + +When a controller function is applied to an angular scope object, the `this` of the controller +function becomes the scope of the angular scope object, so any assignment to `this` within the +controller function happens on the angular scope object. + + +# 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. + + +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 +the scope, along with any prototype methods of the controller type, become functions available in +the template/view, and can be invoked via angular expressions and `ng:` event handlers (e.g. {@link +api/angular.directive.ng:click ng:click}). These controller methods are always evaluated within the +context of the angular scope object that the controller function was applied to (which means that +the `this` keyword of any controller method is always bound to the scope that the controller +augments). This is how the second task of adding behavior to the scope is accomplished. + + + + +# 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 dev_guide.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 dev_guide.templates.databinding} for automatic DOM manipulation. If +you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link +dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}. +- Input formatting — Use {@link dev_guide.templates.formatters angular formatters} instead. +- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead. +- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular +services} instead. +- Instantiate or manage 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 explicitly via the {@link api/angular.scope.$new +scope.$new} api or implicitly via the {@link api/angular.directive.@ng:controller ng:controller +directive} or {@link api/angular.service.$route $route service}. + + + + +## Controller Constructor and Methods Example + + +To illustrate how the controller component works 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` + + +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:controller="SpicyCtrl"> + <button ng:click="chiliSpicy()">Chili</button> + <button ng:click="jalapenoSpicy()">Jalapeño</button> + <p>The food is {{spice}} spicy!</p> +</body> + + +function SpicyCtrl() { + this.spice = 'very'; + this.chiliSpicy = function() { +   this.spice = 'chili'; + } +} + + +SpicyCtrl.prototype.jalapenoSpicy = function() { + this.spice = 'jalapeño'; +} +</pre> + + +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". +- The JavaScript keyword `this` in the `SpicyCtrl` function is bound to the scope that the +controller augments. +- Assigning a property to `this` creates or updates the model. +- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or +as prototype methods of the controller constructor function (the `jalapenoSpicy` method) +- Both controller methods are available in the template (for the `body` element and and its +children). + + +Controller methods can also take arguments, as demonstrated in the following variation of the +previous example. + + +## Controller Method Arguments Example + + +<pre> +<body ng:controller="SpicyCtrl"> + <input name="customSpice" value="wasabi"> + <button ng:click="spicy('chili')">Chili</button> + <button ng:click="spicy(customSpice)">Custom spice</button> + <p>The food is {{spice}} spicy!</p> +</body> + + +function SpicyCtrl() { + this.spice = 'very'; + this.spicy = function(spice) { +   this.spice = spice; + } +} +</pre> + + +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/angular.scope Scope} inheritance. Let's +have a look at an example: + + +<pre> +<body 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> +</body> + + +function MainCtrl() { + this.timeOfDay = 'morning'; + this.name = 'Nikki'; +} + + +function ChildCtrl() { + this.name = 'Mattie'; +} + + +function BabyCtrl() { + this.timeOfDay = 'evening'; + this.name = 'Gingerbreak Baby'; +} +</pre> + + +Notice how we nested three `ng:controller` directives in our template. This template construct will +result in 4 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. + + +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. + + + + +## Testing Controllers + + +The way to test a controller depends upon how complicated the controller is. + + +- If your controller doesn't use DI or scope methods — create the controller with the `new` +operator and test away. For example: + + +Controller Function: +<pre> +function myController() { +   this.spices = [{"name":"pasilla", "spiciness":"mild"}, +                  {"name":"jalapeno", "spiceiness":"hot hot hot!"}, +                  {"name":"habanero", "spiceness":"LAVA HOT!!"}]; + + +   this.spice = "habanero"; +} +</pre> + + +Controller Test: +<pre> +describe('myController function', function() { + + +  describe('myController', function(){ +    var ctrl; + + +    beforeEach(function() { +      ctrl = new myController(); +    }); + + +    it('should create "spices" model with 3 spices', function() { +      expect(ctrl.spices.length).toBe(3); +    }); + + +    it('should set the default value of spice', function() { +      expect(ctrl.spice).toBe('habanero'); +    }); +  }); +}); +</pre> + + +- If your controller does use DI or scope methods — create a root scope, then create the controller +in the root scope with `scope.$new(MyController)`. Test the controller using `$eval`, if necessary. +- If you need to test a nested controller that depends on its parent's state — create a root scope, +create a parent scope, create a child scope, and test the controller using $eval if necessary. + + + + +## Related Topics + + +* {@link dev_guide.mvc About MVC in Angular} +* {@link dev_guide.mvc.understanding_model Understanding the Model Component} +* {@link dev_guide.mvc.understanding_view Understanding the View Component} | 
