mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 15:40:22 +00:00
259 lines
10 KiB
Text
259 lines
10 KiB
Text
@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.module.ng.$rootScope.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.forms angular form widgets} 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.module.ng.$rootScope.Scope#$new
|
|
scope.$new} api or implicitly via the {@link api/angular.directive.ng:controller ng:controller
|
|
directive} or {@link api/angular.module.ng.$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 ng:model="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.module.ng.$rootScope.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}
|
|
|
|
|