angularjs应用骨架(3)
好,继续上一章节我们继续聊聊angularjs骨架。开发任何一款优秀的应用都会面临一项非常困难的工作,那就是找到一种合适的方式方法把代码组织在合适的功能范围内。我们已经看过控制器的处理方式,它会提供一块空间,把正确的数据模型和函数暴露给视图模版。但是用来支持我们应用的代码该怎么办呢?有一块最明显的可以放在这些代码的地方,那就是控制器的函数。
这种做法对于小型的应用和例子来说可以工作的很好,我们已经看到过很多这样的例子,但是在真实的应用中,这种做法很快就会使得代码无法维护。控制器将会变成一个垃圾场,我们做的所有东西都会倒在里面。它们会变得非常难以理解,并且很难修改。
我们来看模块
我们来看模块,它们提供了一种方法,可以用来组织应用中一块功能区域的依赖关系;同时还提供了一种机制,可以自动解析依赖关系(又叫做依赖注入)。一般来说,我们把这些东西叫做依赖服务。因为它们负责为应用提供特殊服务。
例如,我们要在购物站点中的一个控制器中需要从服务器上获取一个商品列表,那么我们就需要这些对象——不妨把它叫做Items——用来处理服务器端获取商品列表的工作,进而,Items对象就需要以某种方式与服务器上的数据库进行交互,可以通过XHR或者WebSocket。
如果没有模块的情况下,我们的控制器看起来可能就是这样:
1 function ItemsViewController(){ 2 //向服务器发起请求 3 4 //解析响应并放入Items对象 5 6 //把Items数组放到$scope上,这样视图才能显示它 7 }
虽然这样可以运行,但是却存在大量潜在的问题:
1、如果其他控制器也要从服务器上获取Items,那么只能把这段代码再写一遍,这会给维护工作带来很大的负担。
2、加上一些其他因素,例如服务端认证、解析数据的复杂性,会使得控制器对象很难划分出一个合理的功能边界,并且代码阅读起来非常困难。
3、使得单元测试变得复杂。
利用模块和模块内置的依赖注入的功能,就可以把控制器变得简单,示例如下:
1 app.controller('myController', function ($scope,Items) { 2 $scope.items =Items.query(); 3 …… 4 }
你可能会问,“不错,看起来还不错,但是Items是什么东西,从哪里来?”以上代码,假如我们已经把Items变成了一个服务。服务都是单例的对象,它们用来执行必要的任务支撑应用的功能。
Angular内置了很多服务,例如$location服务,用来和浏览器的地址栏进行交互;$route服务,用来根据URL地址的变化切换视图;还有$http服务,用来和服务器进行交互。你也可已创建自己的服务,用它们来实现你的应用所特有的功能,如果需要,服务可以在任何控制器中共享,当你需要多个控制器之间进行交互和共享状态时这些服务就是一种很好的机制。Angular的内置服务一$开头,你当然可以为你的服务随意起名字,但是最好不要以$开头,以免引起命名冲突。你可以使用模型对象的API来定义服务。一下三种函数可以用来创建一般的服务,它们的复杂度和功能度不同。
函数1:provider(name,Object or constructor()),定义:一个可配置的服务,创建的逻辑比较复杂。
函数2:factory(name,$getFunction() ),定义:一个不可配置的服务,创建逻辑比较复杂。可以把它看成provider(name,{$get:$getFunction()})。
函数3:servive(name,conttuctor()),定义:一个不可配置的服务,创建逻辑比较简单。
我们先来看一个使用factory创建服务的例子。我们可以像下面这样来编写服务:
1 var app = angular.module('MyApp', []); 2 app.factory('Items', function () { 3 var items={}; 4 items.query= function () { 5 return [ 6 {title: "羽毛球", quantity: 8, price: 3.99}, 7 {title: "篮球", quantity: 17, price: 1.99}, 8 {title: "足球", quantity: 5, price: 3.99} 9 ]; 10 }; 11 return items; 12 });
为了能让这一机制能够和模版配合起来,我们把模版名称告诉ng-app指令。
完整代码如下:
1 <!DOCTYPE html> 2 <html ng-app="MyApp"> 3 <head> 4 <meta charset="UTF-8"> 5 <title></title> 6 <script src="static/js/angular.js" type="text/javascript"></script> 7 <script src="static/app/controller/shopingcartcontroller.js" type="text/javascript"></script> 8 </head> 9 <body ng-controller="myController"> 10 <div> 11 <h1>Your Shoping Cart</h1> 12 <div ng-repeat="item in items"> 13 <span>{{item.title}}</span> 14 <input type="text" ng-model="item.quantity"/> 15 <span>{{item.quantity | currency}}</span> 16 <span>{{item.quantity * item.price | currency}}</span> 17 <button ng-click="remove($index)">remove</button> 18 </div> 19 20 <div>total:{{bill.totalCart| currency}}</div> 21 <div>Discount:{{bill.discount | currency}}</div> 22 <div>Subtotal:{{ bill.subtotal | currency}}</div> 23 </div> 24 </body> 25 </html>
1 /** 2 * Created by Administrator on 2015/6/12. 3 */ 4 var app = angular.module('MyApp', []); 5 app.factory('Items', function () { 6 var items={}; 7 items.query= function () { 8 return [ 9 {title: "羽毛球", quantity: 8, price: 3.99}, 10 {title: "篮球", quantity: 17, price: 1.99}, 11 {title: "足球", quantity: 5, price: 3.99} 12 ]; 13 }; 14 return items; 15 }); 16 app.controller('myController', function ($scope,Items) { 17 $scope.bill = {}; 18 $scope.items =Items.query(); 19 20 $scope.remove = function (index) { 21 $scope.items.splice(index, 1); 22 } 23 24 //var calulateTotal = function () { 25 // var total = 0; 26 // for (var i = 0, len = $scope.items.length; i < len; i++) { 27 // total = total + $scope.items[i].price * $scope.items[i].quantity; 28 // } 29 // $scope.bill.totalCart = total; 30 // $scope.bill.discount = total > 100 ? 10 : 0; 31 // $scope.bill.subtotal=total - $scope.bill.discount; 32 //}; 33 34 $scope.$watch(function () { 35 var total = 0; 36 for (var i = 0, len = $scope.items.length; i < len; i++) { 37 total = total + $scope.items[i].price * $scope.items[i].quantity; 38 } 39 $scope.bill.totalCart = total; 40 $scope.bill.discount = total > 100 ? 10 : 0; 41 $scope.bill.subtotal=total - $scope.bill.discount; 42 }); 43 44 45 46 47 function ItemsViewController(){ 48 //向服务器发起请求 49 50 //解析响应并放入Items对象 51 52 //把Items数组放到$scope上,这样视图才能显示它 53 } 54 });
当然你如果你需要使用第三个方包中的所提供的服务或指令,它们一般会有自己的模块,所以需要在应用中定义依赖关系才能应用它们。假如你引入了模块A和B,那么应用中 的模块声明看起来可能会像下面这样:
var appMod=agular.module('myapp',['A','B']);
使用过滤器格式化数据
你可以使用过滤器来声明如何变换数据显示的格式,然后在展示给用户,你只要在模版中使用一个插值变量即可。使用过滤器的语法是:
{{expression | filterName : parameter1 : parameter2}}
过滤器中的多个参数使用冒号隔开。
Angular中有很多过滤器,如currency
{{12.9 | currency}}
以上代码得到结果就是$12.90
在绑定过程中使用管道符号把过滤器连接起来。例如:
{{12.9 | currency | number}}
得到的结果是$13
当然,你也不必受限与内置过滤器,你也可以自定义过滤器:
1 <p> {{ 'my name is amber.xu' | titleCase }}</p>
1 app.filter('titleCase', function () { 2 var titleCaseFilter= function (input) { 3 var words=input.split(' '); 4 for(var i=0;i<words.length;i++){ 5 words[i]=words[i].charAt(0).toUpperCase() + words[i].slice(1); 6 } 7 return words.join(' '); 8 }; 9 return titleCaseFilter; 10 });
效果如下:
My Name Is Amber.xu
使用路由和$location切换视图
虽然从技术上来说AJAX应用确实是单页应用,但是在很多时候,出于各种原因,我们需要为用户展示或隐藏一些子页面视图。
我们可以利用angular的$route服务来管理这种场景,可以利用路由服务来定义这样一种东西:对于浏览器的一种url,angular将会加载一中视图模版,并实例化一个控制器为模版提供内容。在应用中你可以使用$routeProvider服务上的函数来创建路由,把需要创建的路由当成一个配置块来传给函数即可。创建过程类似一下的伪代码:
1 app.config(function ($routeProvider) { 2 $routeProvider.when('url',{controller:aController,templateUrl:'/path/to/template'}). 3 when(...other mapping for your app...). 4 ... 5 otherwise(...waht to do if nothing else matches...) 6 });
以上代码是说,当浏览器中的URL发生变成指定的取值时,angular将会加载/path/to/template路径下的模版,然后把模版中的跟元素关联到aController上(因为我们在模版中写了ng-controller=aController)最后一行的otherwise相当于else或者default。
既然,理论上的东西都有了。我们来实战一下吧。我写这个demo的时候也遇到了一些bug,不妨爆出来给大家看看:
错误1:
这个错出现的原因是说$routeProvider根本没有被找到,为什么会没有被找到了,你可能会说,我已经导入了angular.js文件啦?其实不然,路由已经被单独分离出来了,所以要导入angular-route.js文件的引用,之前有讲过,如果使用了第三方包中提供的指令就要在模块中引用它:var app=angular.module('AMail',['ngRoute']);如果值导入了文件,而在代码中没有对ngRoute进行引用会出现此错误:
现在需要一个首页index.html
1 <!DOCTYPE html> 2 <html ng-app="AMail"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>AMail</title> 6 <script src="../static/js/angular.js" type="text/javascript"></script> 7 <script src="../static/js/angular-route.min.js"></script> 8 <script src="../static/app/controller/controller.js" type="text/javascript"></script> 9 </head> 10 <body> 11 <h1>A-Mail</h1> 12 <div ng-view> 13 14 </div> 15 </body> 16 </html>
index.html上有一个之前没有见过的指令ng-view。ng-view会把模版中的内容替换到这里(相当于一个占位符,说的比较口语化)。
有两个模版
1、list.html显示所有的邮件列表。
2、detail.html单个邮件的详细页。
1 <table> 2 <tr> 3 <td><strong>Sender</strong></td> 4 <td><strong>Subject</strong></td> 5 <td><stroong>Date</stroong></td> 6 </tr> 7 <tr ng-repeat="message in messages"> 8 <td>{{message.sender}}</td> 9 <td><a href="#/view/{{message.id}}">{{message.subject}}</a></td> 10 <td>{{message.date}}</td> 11 </tr> 12 </table>
1 <div><strong>Subject:</strong>{{message.subject}}</div> 2 <div><strong>Sender:</strong>{{message.sender}}</div> 3 <div><strong>Date:</strong>{{message.date}}</div> 4 <div><strong>To:</strong> 5 <span ng-repeat="re in message.recipients"> {{re}}</span> 6 </div> 7 <div>{{message.message}}</div> 8 <a href="#/">Back to Message</a>
1 !(function () { 2 var app = angular.module('AMail', ['ngRoute']); 3 4 function emailRouteCoinfig($routeProvider) { 5 $routeProvider.when('/', {controller: ListController, templateUrl: 'list.html'}). 6 when('/view/:id', {controller: DetailController, templateUrl: 'detail.html'}). 7 otherwise({redirectTo: '/'}); 8 }; 9 //配置我们的路由,以便AMail能够找到他 10 app.config(emailRouteCoinfig); 11 12 //一些虚拟的哟意见 13 var messages = [ 14 { 15 id: 0, 16 sender: 'jean@yesno.com.cn', 17 subject: '今天不用加班啦,明天周末', 18 date: '2015年7月14日 21:39:59', 19 message: '今天不用加班啦,明天周末!明天是周末,可以好好休息两天了。', 20 recipients: ['woshixuleijava@163.com'] 21 }, 22 { 23 id: 1, 24 sender: 'jean2@yesno.com.cn', 25 subject: '明天不用加班啦,明天周末', 26 date: '2015年7月15日 21:39:59', 27 message: '明天不用加班啦,明天周末!明天是周末,可以好好休息两天了。', 28 recipients: ['woshixuleijava2@163.com'] 29 }, 30 { 31 id: 2, 32 sender: 'jean3@yesno.com.cn', 33 subject: '后天不用加班啦,明天周末', 34 date: '2015年7月16日 21:39:59', 35 message: '后天不用加班啦,明天周末!明天是周末,可以好好休息两天了。', 36 recipients: ['woshixuleijava3@163.com'] 37 } 38 ]; 39 40 function ListController($scope) { 41 $scope.messages = messages; 42 }; 43 function DetailController($scope, $routeParams) { 44 $scope.message = messages[$routeParams.id]; 45 }; 46 47 48 }());
效果图:
我们已经为应用搭好了基本框架,这款应用带有很多视图,可以通过修改url的方式来切换视图。这就意味着对用户来说,前进后退按钮能真正运行起来了。而这里实际上只有一个真实的HTML页面。
如果觉得对你有所帮助就打点一下吧