mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
384 lines
18 KiB
Text
384 lines
18 KiB
Text
@ngdoc overview
|
|
@name Conceptual Overview
|
|
@description
|
|
|
|
There are some concepts within Angular that you should understand before creating your first application.
|
|
This section touches all important parts of Angular really quickly using a simple example.
|
|
However, it won't explain all details.
|
|
For a more in-depth explanation, have a look at the {@link tutorial/ tutorial}.
|
|
|
|
| Concept | Description |
|
|
|------------------|------------------------------------------|
|
|
|{@link concepts#template Template} | HTML with additional markup |
|
|
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
|
|{@link concepts#model Model} | the data that is shown to the user and with which the user interacts |
|
|
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
|
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
|
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
|
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
|
|{@link concepts#view View} | what the user sees (the DOM) |
|
|
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
|
|{@link concepts#controller Controller} | the business logic behind views |
|
|
|{@link concepts#di Dependency Injection} | Creates and wires objects / functions |
|
|
|{@link concepts#injector Injector} | dependency injection container |
|
|
|{@link concepts#module Module} | configures the Injector |
|
|
|{@link concepts#service Service} | reusable business logic independent of views |
|
|
|
|
|
|
# A first example: Data binding
|
|
|
|
In the following we will build a form to calculate the costs of an invoice in different currencies.
|
|
|
|
Let's start with input fields for quantity and cost whose values are multiplied to produce the total of the invoice:
|
|
|
|
|
|
<example>
|
|
<file name="index.html">
|
|
<div ng-init="qty=1;cost=2">
|
|
<b>Invoice:</b>
|
|
<div>
|
|
Quantity: <input type="number" ng-model="qty" required >
|
|
</div>
|
|
<div>
|
|
Costs: <input type="number" ng-model="cost" required >
|
|
</div>
|
|
<div>
|
|
<b>Total:</b> {{qty * cost | currency}}
|
|
</div>
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
Try out the Live Preview above, and then let's walk through the example and describe what's going on.
|
|
|
|
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding1.png">
|
|
|
|
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
|
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
|
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
|
|
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
|
|
|
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
|
|
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
|
{@link api/ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
|
initializes our application. Angular also defines a directive for the {@link api/ng.directive:input `input`}
|
|
element that adds extra behavior to the element. E.g. it is able to automatically validate that the entered
|
|
text is non empty by evaluating the `required` attribute.
|
|
The {@link api/ng.directive:ngModel `ng-model`} directive stores/updates
|
|
the value of the input field into/from a variable and shows the validation state of the input field by
|
|
adding css classes. In the example we use these css classes to mark an empty input field with a red border.
|
|
|
|
<div class="alert alert-info">
|
|
**Custom directives to access the DOM**: In Angular, the only place where an application touches the DOM is
|
|
within directives. This is good as artifacts that access the DOM are hard to test.
|
|
If you need to access the DOM directly you should write a custom directive for this. The
|
|
{@link directive directives guide} explains how to do this.
|
|
</div>
|
|
|
|
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
|
|
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
|
|
An <a name="expression">"{@link expression expression}"</a> in a template is a JavaScript-like code snippet that allows
|
|
to read and write variables. Note that those variables are not global variables.
|
|
Just like variables in a JavaScript function live in a scope,
|
|
Angular provides a <a name="scope">"{@link scope scope}"</a> for the variables accessible to expressions.
|
|
The values that are stored in variables on the scope are referred to as the <a name="model">"model"</a>
|
|
in the rest of the documentation.
|
|
Applied to the example above, the markup directs Angular to "take the data we got from the input widgets
|
|
and multiply them together".
|
|
|
|
The example above also contains a <a name="filter">"{@link filter filter}"</a>.
|
|
A filter formats the value of an expression for display to the user.
|
|
In the example above, the filter {@link api/ng.filter:currency `currency`} formats a number
|
|
into an output that looks like money.
|
|
|
|
The important thing in the example is that angular provides _live_ bindings:
|
|
Whenever the input values change, the value of the expressions are automatically
|
|
recalculated and the DOM is updated with their values.
|
|
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
|
|
|
|
|
# Adding UI logic: Controllers
|
|
|
|
Let's add some more logic to the example that allows us to enter and calculate the costs in
|
|
different currencies and also pay the invoice.
|
|
|
|
<example module="invoice1">
|
|
<file name="invoice1.js">
|
|
angular.module('invoice1', [])
|
|
.controller('InvoiceController', function() {
|
|
this.qty = 1;
|
|
this.cost = 2;
|
|
this.inCurr = 'EUR';
|
|
this.currencies = ['USD', 'EUR', 'CNY'];
|
|
this.usdToForeignRates = {
|
|
USD: 1,
|
|
EUR: 0.74,
|
|
CNY: 6.09
|
|
};
|
|
|
|
this.total = function total(outCurr) {
|
|
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
|
|
};
|
|
this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) {
|
|
return amount * this.usdToForeignRates[outCurr] * 1 / this.usdToForeignRates[inCurr];
|
|
};
|
|
this.pay = function pay() {
|
|
window.alert("Thanks!");
|
|
};
|
|
});
|
|
</file>
|
|
<file name="index.html">
|
|
<div ng-controller="InvoiceController as invoice">
|
|
<b>Invoice:</b>
|
|
<div>
|
|
Quantity: <input type="number" ng-model="invoice.qty" required >
|
|
</div>
|
|
<div>
|
|
Costs: <input type="number" ng-model="invoice.cost" required >
|
|
<select ng-model="invoice.inCurr">
|
|
<option ng-repeat="c in invoice.currencies">{{c}}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<b>Total:</b>
|
|
<span ng-repeat="c in invoice.currencies">
|
|
{{invoice.total(c) | currency:c}}
|
|
</span>
|
|
<button class="btn" ng-click="invoice.pay()">Pay</button>
|
|
</div>
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
What changed?
|
|
|
|
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
|
|
More exactly, the file contains a constructor function that creates the actual controller instance.
|
|
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
|
|
|
Besides the new file that contains the controller code we also added a
|
|
{@link api/ng.directive:ngController `ng-controller`} directive to the HTML.
|
|
This directive tells angular that the new `InvoiceController` is responsible for the element with the directive
|
|
and all of the element's children.
|
|
The syntax `InvoiceController as invoice` tells Angular to instantiate the controller
|
|
and save it in the variable `invoice` in the current scope.
|
|
|
|
We also changed all expressions in the page to read and write variables within that
|
|
controller instance by prefixing them with `invoice.` . The possible currencies are defined in the controller
|
|
and added to the template using {@link api/ng.directive:ngRepeat `ng-repeat`}.
|
|
As the controller contains a `total` function
|
|
we are also able to bind the result of that function to the DOM using `{{ invoice.total(...) }}`.
|
|
|
|
Again, this binding is live, i.e. the DOM will be automatically updated
|
|
whenever the result of the function changes.
|
|
The button to pay the invoice uses the directive {@link api/ng.directive:ngClick `ngClick`}. This will evaluate the
|
|
corresponding expression whenever the button is clicked.
|
|
|
|
In the new JavaScript file we are also creating a {@link concepts#module module}
|
|
at which we register the controller. We will talk about modules in the next section.
|
|
|
|
The following graphic shows how everything works together after we introduced the controller:
|
|
|
|
<img style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding2.png">
|
|
|
|
# View independent business logic: Services
|
|
|
|
Right now, the `InvoiceController` contains all logic of our example. When the application grows it
|
|
is a good practice to move view independent logic from the controller into a so called
|
|
<a name="service">"{@link dev_guide.services service}"</a>, so it can be reused by other parts
|
|
of the application as well. Later on, we could also change that service to load the exchange rates
|
|
from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
|
|
|
|
Let's refactor our example and move the currency conversion into a service in another file:
|
|
|
|
<example module="invoice2">
|
|
<file name="finance2.js">
|
|
angular.module('finance2', [])
|
|
.factory('currencyConverter', function() {
|
|
var currencies = ['USD', 'EUR', 'CNY'],
|
|
usdToForeignRates = {
|
|
USD: 1,
|
|
EUR: 0.74,
|
|
CNY: 6.09
|
|
};
|
|
return {
|
|
currencies: currencies,
|
|
convert: convert
|
|
};
|
|
|
|
function convert(amount, inCurr, outCurr) {
|
|
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
|
|
}
|
|
});
|
|
</file>
|
|
<file name="invoice2.js">
|
|
angular.module('invoice2', ['finance2'])
|
|
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
|
|
this.qty = 1;
|
|
this.cost = 2;
|
|
this.inCurr = 'EUR';
|
|
this.currencies = currencyConverter.currencies;
|
|
|
|
this.total = function total(outCurr) {
|
|
return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
|
|
};
|
|
this.pay = function pay() {
|
|
window.alert("Thanks!");
|
|
};
|
|
}]);
|
|
</file>
|
|
<file name="index.html">
|
|
<div ng-controller="InvoiceController as invoice">
|
|
<b>Invoice:</b>
|
|
<div>
|
|
Quantity: <input type="number" ng-model="invoice.qty" required >
|
|
</div>
|
|
<div>
|
|
Costs: <input type="number" ng-model="invoice.cost" required >
|
|
<select ng-model="invoice.inCurr">
|
|
<option ng-repeat="c in invoice.currencies">{{c}}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<b>Total:</b>
|
|
<span ng-repeat="c in invoice.currencies">
|
|
{{invoice.total(c) | currency:c}}
|
|
</span>
|
|
<button class="btn" ng-click="invoice.pay()">Pay</button>
|
|
</div>
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-module-service.png">
|
|
|
|
What changed?
|
|
We moved the `convertCurrency` function and the definition of the existing currencies
|
|
into the new file `finance.js`. But how does the controller
|
|
get a hold of the now separated function?
|
|
|
|
This is where <a name="di">"{@link di Dependency Injection}"</a> comes into play.
|
|
Dependency Injection (DI) is a software design pattern that
|
|
deals with how objects and functions get created and how they get a hold of their dependencies.
|
|
Everything within Angular (directives, filters, controllers,
|
|
services, ...) is created and wired using dependency injection. Within Angular,
|
|
the DI container is called the <a name="injector">"{@link di injector}"</a>.
|
|
|
|
To use DI, there needs to be a place where all the things that should work together are registered.
|
|
In Angular, this is the purpose of the so called <a name="module">"{@link module modules}"</a>.
|
|
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
|
|
including the configuration of all modules that this module depends on.
|
|
|
|
In the example above:
|
|
The template contains the directive `ng-app="invoice2"`. This tells Angular
|
|
to use the `invoice` module as the main module for the application.
|
|
The code snippet `angular.module('invoice', ['finance'])` specifies that the `invoice` module depends on the
|
|
`finance` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service.
|
|
|
|
Now that Angular knows of all the parts of the application, it needs to create them.
|
|
In the previous section we saw that controllers are created using a factory function.
|
|
For services there are multiple ways to define their factory
|
|
(see the {@link dev_guide.services service guide}).
|
|
In the example above, we are using a function that returns the `currencyConverter` function as the factory
|
|
for the service.
|
|
|
|
Back to the initial question: How does the `InvoiceController` get a reference to the `currencyConverter` function?
|
|
In Angular, this is done by simply defining arguments on the constructor function. With this, the injector
|
|
is able to create the objects in the right order and pass the previously created objects into the
|
|
factories of the objects that depend on them.
|
|
In our example, the `InvoiceController` has an argument named `currencyConverter`. By this, Angular knows about the
|
|
dependency between the controller and the service and calls the controller with the service instance as argument.
|
|
|
|
The last thing that changed in the example between the previous section and this section is that we
|
|
now pass an array to the `module.controller` function, instead of a plain function. The array first
|
|
contains the names of the service dependencies that the controller needs. The last entry
|
|
in the array is the controller constructor function.
|
|
Angular uses this array syntax to define the dependencies so that the DI also works after minifying
|
|
the code, which will most probably rename the argument name of the controller constructor function
|
|
to something shorter like `a`.
|
|
|
|
# Accessing the backend
|
|
|
|
Let's finish our example by fetching the exchange rates from the Yahoo Finance API.
|
|
The following example shows how this is done with Angular:
|
|
|
|
<example module="invoice3">
|
|
<file name="invoice3.js">
|
|
angular.module('invoice3', ['finance3'])
|
|
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
|
|
this.qty = 1;
|
|
this.cost = 2;
|
|
this.inCurr = 'EUR';
|
|
this.currencies = currencyConverter.currencies;
|
|
|
|
this.total = function total(outCurr) {
|
|
return currencyConverter.convert(this.qty * this.cost, this.inCurr, outCurr);
|
|
};
|
|
this.pay = function pay() {
|
|
window.alert("Thanks!");
|
|
};
|
|
}]);
|
|
</file>
|
|
<file name="finance3.js">
|
|
angular.module('finance3', [])
|
|
.factory('currencyConverter', ['$http', function($http) {
|
|
var YAHOO_FINANCE_URL_PATTERN =
|
|
'http://query.yahooapis.com/v1/public/yql?q=select * from '+
|
|
'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
|
|
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK',
|
|
currencies = ['USD', 'EUR', 'CNY'],
|
|
usdToForeignRates = {};
|
|
refresh();
|
|
return {
|
|
currencies: currencies,
|
|
convert: convert,
|
|
refresh: refresh
|
|
};
|
|
|
|
function convert(amount, inCurr, outCurr) {
|
|
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
|
|
}
|
|
|
|
function refresh() {
|
|
var url = YAHOO_FINANCE_URL_PATTERN.
|
|
replace('PAIRS', 'USD' + currencies.join('","USD'));
|
|
return $http.jsonp(url).success(function(data) {
|
|
var newUsdToForeignRates = {};
|
|
angular.forEach(data.query.results.rate, function(rate) {
|
|
var currency = rate.id.substring(3,6);
|
|
newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
|
|
});
|
|
usdToForeignRates = newUsdToForeignRates;
|
|
});
|
|
}
|
|
}]);
|
|
</file>
|
|
<file name="index.html">
|
|
<div ng-controller="InvoiceController as invoice">
|
|
<b>Invoice:</b>
|
|
<div>
|
|
Quantity: <input type="number" ng-model="invoice.qty" required >
|
|
</div>
|
|
<div>
|
|
Costs: <input type="number" ng-model="invoice.cost" required >
|
|
<select ng-model="invoice.inCurr">
|
|
<option ng-repeat="c in invoice.currencies">{{c}}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<b>Total:</b>
|
|
<span ng-repeat="c in invoice.currencies">
|
|
{{invoice.total(c) | currency:c}}
|
|
</span>
|
|
<button class="btn" ng-click="invoice.pay()">Pay</button>
|
|
</div>
|
|
</div>
|
|
</file>
|
|
</example>
|
|
|
|
What changed?
|
|
Our `currencyConverter` service of the `finance` module now uses the
|
|
{@link api/ng.$http $http} service, a builtin service provided by Angular
|
|
for accessing the backend. It is a wrapper around [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
|
and [JSONP](http://en.wikipedia.org/wiki/JSONP) transports. Details can be found in the api docs of that service.
|
|
|