AngularJS概念概述和第一个使用例子
点击查看AngularJS系列目录
转载请注明出处:http://www.cnblogs.com/leosx/
概念概述
本节使用一个简单的例子简要介绍了AngularJS的重要组成部分。
概念 | 描述 |
模板(Template) | HTML的附加标记 |
指令(Directives) | 通过元素或者客户属性去扩展HTML |
模型(Model) | 用户和界面交互的数据的模型。 |
上下文(Scope) | 语境上下文,这样控制器,指令和表达式可以访问它里面的数据。 |
表达式(Expressions) | 可以从Scope(上下文)中访问变量和函数。 |
编译器(Compiler) | 解析模板和实例化指令和表达式 |
过滤器(Filter) | 格式表达式的值以显示给用户 |
视图(View) | 用户所看到的(DOM) |
数据绑定(Data Binding) | 模型和视图间同步数据的动作 |
控制器(Controller) | 试图(view)背后的业务逻辑 |
依赖注入(Dependency Injection) | 创建对象和函数的工厂 |
注入器(Injector) | 依赖注入的容器 |
模块(Module) | 对应用程序的不同部分,包括控制器,服务器,过滤器,指令进行配置注射器的容器 |
服务(Service) | 对于试图可重用的业务逻辑独立的功能部分 |
第一个例子:数据绑定
在下面的例子中,我们将建立一个表格来计算总价格。
<div ng-app ng-init="qty=1;cost=2"> <b>Invoice:</b> <div> Quantity: <input type="number" min="0" ng-model="qty"> </div> <div> Costs: <input type="number" min="0" ng-model="cost"> </div> <div> <b>Total:</b> {{qty * cost | currency}} </div> </div>
结果如下图:
这看起来像普通的HTML和一些新的标记的组合。在AngularJS中,这样的文件称为模板。当AngularJS启动应用程序时,它会使用编译器去分析和处理来自新的标记标识的模板。然后在加载,改造,呈现一个新的DOM到用户视图。
第一个新的标记是指令(directives)。它是HTML元素或者属性的一个特殊的行为。在上面的例子当中,我们使用了 ng-app 属性,这是一个指令,用于自动初始化我们的应用程序。AngularJS还为input元素定义了一些额外的指令,用于增加一些行为来扩展元素。例如:ng-model 指令,用来存储或者更新输入字段的值。
自定义指令访问DOM:在一个AngularJS应用程序中,唯一的一个允许去访问DOM的地方就是指令的范围内(Scope)。这点很重要,因为访问DOM元素是一件很难预测,也很难测试的事情。如果您需要直接访问DOM,你应该写一个自定义的指令去做这件事。
在指令章节,我们会去介绍如何自定义。
第二种新的标记是双花括号 {{表达|过滤器}}
,当编译器遇到这个标记,将其与标记的评估值替换。在模板中的表达式是一个类似于JavaScript的代码段,允许读取和写入变量。请注意,这些变量不是全局变量。就像AngularJS函数和这些变量在一个function函数里面一样,Angular可以去访问。在上面的例子当中,我们的花括号表达式的功能是计算数量和单价,以获得总价格。后面的过滤器使得我们的输出格式为金钱格式(这里加了一个$符号表示是美元为单位的)。在这个例子中,我们可以看到,AngularJS提供了一个绑定:当输入值发生变化,表达式的值会自动重新计算并且去更新DOM与之对应的值。这使用到的概念是双向数据绑定(学过WPF或者Silverlight的同学一定很清楚明白的)。
添加UI逻辑:控制器(controller)
让我们添加更多的逻辑,使我们能够进行不同货币的总价格计算。
第一个文件: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] / this.usdToForeignRates[inCurr]; }; this.pay = function pay() { window.alert("Thanks!"); }; });
第二个文件:index.html
<div ng-app="invoice1" ng-controller="InvoiceController as invoice"> <b>Invoice:</b> <div> Quantity: <input type="number" min="0" ng-model="invoice.qty" required > </div> <div> Costs: <input type="number" min="0" 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>
效果图如下:
和上一个例子相比,有什么改变呢?
首先,增加了一个包含控制器的新的JavaScript文件。更确切地说,该文件包含一个构造函数用于去创建一个真实的Controller控制器的实例。控制器的目的就是为我们的表达式和指令去提供一些变量和方法。
除了增加了新的文件之外,我们还在HTML代码中增加了一个ng-controller
指令,这个指令就是在告诉AngularJS,去创建一个InvoiceController
去负责这个元素,以及这个元素下的所有子元素。语法:ng-controller="InvoiceController as invoice"
是告诉AngularJS去创建一个控制器,并重命名此控制器为invoce,这样下面访问控制器中的变量的时候,就可以使用invoce这个昵称去访问控制器所在的Scope中的变量了,HTML 中,还使用了ng-repeat
指令去循环指定的元素。也定义了一个方法total (function)方法去计算总价格。
同样,这种结合是很灵活的,即当数据变化时,DOM会自动更新显示变化后的结果。这就省却了我们去修改DOM元素。我们使用了ng-click
指令去绑定到了按钮点击事件上。这样,当点击按钮的时候,就会触发与controller
的scope
所对应的方法进行执行。
在新的JavaScript文件,我们还在Module中注册了我们的控制器模块。我们将在下一节讨论模块(module),下图显示了所有的东西是如何在Controller里面对应显示出来的。
独立的业务逻辑:服务
眼下,InvoiceController包含我们的例子中的所有逻辑。当我们的逻辑变得复杂之后,我们应该把一些可以独立出来的业务逻辑提出来,放到服务里面去,这样这些独立的业务逻辑块儿就可以重复使用了。之后,我们就可以从网络上加载一些独立的业务逻辑,如装载并调用雅虎的财经API来实现汇率的转换,而我们的控制器不用变。
服务文件:finance2.js
angular.module('finance2', []) .factory('currencyConverter', function() { var currencies = ['USD', 'EUR', 'CNY']; var usdToForeignRates = { USD: 1, EUR: 0.74, CNY: 6.09 }; var convert = function (amount, inCurr, outCurr) { return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr]; }; return { currencies: currencies, convert: convert }; });
控制器文件: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!"); }; }]);
HTML文件:index.html
<div ng-app="invoice2" ng-controller="InvoiceController as invoice"> <b>Invoice:</b> <div> Quantity: <input type="number" min="0" ng-model="invoice.qty" required > </div> <div> Costs: <input type="number" min="0" 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>
效果图:
这次又有什么变化呢?
我们新建了一个文件:finance2.js 并且把 convertCurrency
功能移动到了文件当中去。但是我们又如何使得控制器依旧保持着这个功能呢?
这就是依赖注入的用武之地。依赖注入(DI)是软件设计的一部分,它实现了如何创建对象(object)和功能函数(function),以及如何正确找到它们的依赖关系。在Angular中,所有的东西(指令,过滤器,控制器,服务,...)的创建都有使用依赖注入。在Angular中,DI的容器是注入器(injector)
要使用DI,我们首先是要在需要使用的地方进行注册。在Angular中,我们是在模块(modules)中进行注册的。当Angular启动时,它会执行模块的配置(config)然后根据ng-app
指令开始执行,在执行前,模块会根据配置的名称去加载所有依赖的模块。
在上面的例子当中,包含了一个指令ng-app="invoice2"
。这是在告诉Angular,应用程序的主模块适用名为:invoice2
的模块作为主模块。代码段:angular.module('invoice2', ['finance2'])
指定了invoice2
的模块依赖于finance2
模块。也就是说,InvoiceController
控制器使用了currencyConverter
服务中的功能。执行到这里Angular知道该应用程序的所有部分了,接下来就是去创建它们了。在上一节我们的控制器使用工厂函数来创建的。在服务方面有多种方式来定义自己的工厂(见服务指南)。在上面的例子中,我们在服务的工厂(factory)中使用了一个函数去返回了一个currencyConverter
函数功能。
回到最初的问题:如何在InvoiceController控制器中得到一个服务中的currencyConverter功能?在Angular中,这是通过在构造函数中简单地定义参数进行实现的。有了这个,injector能够根据顺序去创建正确的对象,并且在创建前,会先调用创建依赖对象并调用它们的工厂函数。在InvoiceController
中,有一个currencyConverter
的参数,有了它,Angular就知道控制器和服务之间的依赖性,并调用与服务实例作为控制器的参数。
无论是上一个例子还是这一个例子,我们都可以看到,我们在module.controller
函数中,有一个数组,数组首先包含了控制器需要服务相关的名称,它就是在注册所依赖的模块。数组中的最后一项是控制器构造函数。Angular使用此数组语法定义的依赖关系,使得减少了代码污染DI。
访问后台服务
让我们来实现一下我们上面说到的,使用雅虎汇率服务API,去通过网络获得实时的汇率信息。
第一个文件: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!"); }; }]);
第二个文件:finance3.js
angular.module('finance3', []) .factory('currencyConverter', ['$http', function($http) { var YAHOO_FINANCE_URL_PATTERN = '//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'; var currencies = ['USD', 'EUR', 'CNY']; var usdToForeignRates = {}; var convert = function (amount, inCurr, outCurr) { return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr]; }; var refresh = function() { 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; }); }; refresh(); return { currencies: currencies, convert: convert, refresh: refresh }; }]);
第三个文件:index.html
<div ng-app="invoice3" ng-controller="InvoiceController as invoice"> <b>Invoice:</b> <div> Quantity: <input type="number" min="0" ng-model="invoice.qty" required > </div> <div> Costs: <input type="number" min="0" 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>
效果图:
这个示例又有什么变化呢?我们的finance
模块的currencyConverter
服务,使用了$http
,$http
是一个AngularJS内置的用于访问服务器的后端数据的服务。$http
使用的是XMLHttpRequest
技术和 JSONP 进行传输.