AngularJS是Google开源出来的一款 Javascript MVC 框架。利用AngularJS,你能够构建结构清晰、便于测试和维护的前端应用。javascript
使用AngularJS,你能够经过directive去定义不少本身的HTML元素属性。AngularJS无缝衔接了HTML(view)和Javascript(model),这样你就不须要去过多地关注Dom如何变化,你只需专一的处理你的数据。html
AngularJS和服务器通讯很是方便。和大多数Javascript的MVC框架同样, 只要你的应用提供一个RESTful API,AngularJS就能够和你的服务器相配合。同时,AngularJS提供了基于XHR的服务,这将大大简化你的代码,也方便将可复用的服务抽象成API调用。前端
Raoni Boaventura提供了一个教程,一步一步地教你写一个简单的AngularJS应用,让咱们一块儿来看一下吧。java
为了简化问题,咱们要作的是一个直接从网络上拉取信息的应用,好比,一个查看方程式赛车比赛信息的应用。直接用Ergast的api获取信息。git
能够看一下这个demo,对咱们要作一个什么样的应用有个大体的概念。angularjs
推荐使用augular-seed,提供了一个很好的项目骨架。github
咱们的应用的骨架大概是这样的:web
如今要开始写代码了。从最重要的页面开始吧:锦标赛的表格。chrome
HTML大概是这个样子的(为了提升可读性,先忽略CSS):json
<body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>
我想你会注意到模板里面有一些{{
和 }}
之类的表达式。咱们能够在里面作一些计算。举一些例子吧:
{{ 1 + 1 }} {{ 946757880 | date }} {{ user.name }}
是否是和javascript很像?不过,虽然它们很强大,咱们不该该用它们去实现一些高层级的逻辑——这该交给directive。
上面的模板里还有一些相似ng-attributes
的语句,这些正是directive。
directive让AngularJS把特定的行为附加到DOM元素中。让咱们看一下上面的模板中的例子:
ng-app
初始化你的应用,定义其做用域。在 AngularJS 中,同一页面能够有多个应用,ng-app
指令代表应用的首尾位置。ng-controller
定义视图由哪一个控制器负责。在咱们的例子中,driversController
提供了车手的列表(driversList
)。ng-repeat
这个最经常使用。当使用循环的时候,ng-repeat
定义模板的范围。在咱们的例子中,就driversList
中的每一个车手,ng-repeat
会生成重复的行。固然,没有控制器,咱们的视图什么也干不了。让咱们在controllers.js
中添加一个driversController
:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });
你可能注意到了咱们将$scope
传递给了控制器。$scope
变量将控制器和视图相链接。事实上,它储存了模板中会用到的全部数据。任何你加入的内容(好比咱们的例子中的driversList
)能够直接在视图中访问。如今咱们先用一个静态的数据数组,稍后咱们会把它换成API服务。
在app.js
中加入:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
这行代码让咱们初始化了咱们的应用,同时也登记了须要的依赖。稍后咱们会回到这个文件。
好了,如今让咱们把这一切在index.html
中整合起来:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> <script src="lib/angular/angular.js"></script> <script src="lib/angular/angular-route.js"></script> <script type="text/javascript" src="js/app.js"></script> <script type="text/javascript" src="js/controllers.js"></script> <script type="text/javascript" src="js/services.js"></script> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body> </html>
如今你能够尝试运行下这个应用了。
若是你但愿调试应用,建议看下Chrome的Batarang 插件。
既然咱们已经知道如何展现这些数据了,如今该是咱们从RESTful服务器获取信息的时候了。
AngularJS提供的$http
和$resource
帮助咱们和服务器通信。
$http
是以XMLHttpRequest和[JSONP]为基础的抽象层,$resource
则提供更高层的抽象。在这里咱们使用$http
。
为了将API调用从控制器中抽象出来,咱们建立一个本身定制的服务,该服务将抓取咱们须要的信息,将$http
封装起来。在services.js
中加入:
angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
开头两行,咱们建立了一个名为F1FeederApp.services
的新模块,并在ergastAPIservice
模块内注册了服务。注意,咱们将$http
传递给了该服务。这就告诉了Angular的依赖注入引擎咱们的新服务依赖于$http
服务。
相似地,咱们须要让Angular将咱们的新模块包含到应用中。在app.js
注册下便可:
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
如今咱们只需调整一下controller.js
,将ergastAPIservice
做为依赖:
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });
好了,从新启动一下应用,看看结果如何。注意咱们彻底没有改动模板,只是增长了一个nameFilter
。让咱们把这个变量用起来。
好极了!咱们的控制器已经能够工做了。可是它只能显示一个车手的列表。让咱们加一些功能吧。咱们来实现一个简单的文本搜索框,能够过滤列表。将如下这行加入到index.html
,加在<body>
标签下面:
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
如今咱们要用上 ng-model
指令了。这个指令将文本框绑定到$scope.nameFilter
变量,而且确保该变量的值会及时更新。如今让咱们稍微调整下index.html
,加上一行ng-repeat
指令。
<tr ng-repeat="driver in driversList | filter: nameFilter">
这一行告诉ng-repeat
,在输出数据以前,车手数组先要通过nameFilter
的过滤。
此刻就是双向数据绑定发挥威力的时候了:每次你在搜索框里键入一些值的时候,Angular会及时更新$scope.nameFilter
的内容。因为绑定是双向的,因此nameFilter
更新的时候,相应的指令(ng-repeat
)也会得到新的数值,而后视图会马上更新。
重启你的应用,看看搜索框。
注意,过滤器会搜索全部属性中的关键词,包括你不想包括的内容。假设你只想经过Driver.givenName
和Driver.familyName
过滤:首先,在driversController
文件的$scope.driversList = [];
下加入这样一行:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.filterName, 'i'); return !$scope.filterName || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
如今回到index.html
,更新包括ng-repeat
的那行:
<tr ng-repeat="driver in driversList | filter: searchFilter">
从新启动应用,如今你能够经过姓名搜索了。
接下来咱们要建立一个车手详情页面,当咱们点击车手的时候,咱们就能够看到关于他的一些详细信息。
首先,咱们在app.js
里加入$routeProvider
服务,这个服务将帮助咱们处理应用路由。而后咱们加入两个路由:一个转向锦标赛表格,另外一个转向车手详情。
angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);
修改以后,访问http://domain/#/drivers
,这将加载driversController
,而后在partials/drivers.html
寻找须要渲染的部分视图。等等!咱们好像尚未部分视图?咱们须要建立他们。
AngularJS容许你将路由绑定到特定的控制器和视图。不过咱们首先须要告诉Angular在哪里渲染这些部分视图。这须要使用ng-view
指令。修改一下你的index.html
:
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> <script src="lib/angular/angular.js"></script> <script src="lib/angular/angular-route.js"></script> <script type="text/javascript" src="js/app.js"></script> <script type="text/javascript" src="js/controllers.js"></script> <script type="text/javascript" src="js/services.js"></script> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> </body> </html>
如今,只要是经过应用路由浏览,Angular 就会加载相应的视图,而且
在<ng-view>
标签处渲染。你所须要作的只是建立一个名为partials/drivers.html
的文件,而后将锦标赛表格放在那里。同时咱们也将将车手的姓名和详情页面链接起来。
<input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>
最后,让咱们肯定下详情页面要展现什么。一个总结了车手相关信息(好比生日、国籍)的页面,同时包括一个最近成绩的表格。在services.js
里加入这些:
angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
此次咱们把车手的ID提供给服务,这样咱们就能够获取特定车手的信息了。修改一下controllers.js
:
angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.filterName, 'i'); return !$scope.filterName || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });
值得注意的是咱们将$routeParams
服务插入了车手控制器。这个服务容许咱们使用$routeParams.id
访问URL参数(好比:id
)。
如今咱们已经有数据了,咱们只须要处理一下局部视图了。建立一个partials/driver.html
:
<section id="main"> <nav id="secondary" class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}}<br/>{{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>
注意此次咱们用上了ng-show
。只有当你提供的表达式是true
的时候,它才会显示HTML元素。在咱们的例子中,只有当控制器加载了车手对象后才会显示头像。
加上一些CSS以后,大体是这样的效果:
如今你的应用已经能够上线了。确认下路由能工做。你能够在index.html
加入一个静态的菜单以增强导航。一切皆有可能。
咱们已经介绍了开发一个简单应用所需的一切。最后别忘了,Angular是一个很是强大的框架。咱们只是试了试水而已。之后有机会将向你们展现Angular区别于其余前端MVC框架的特性:可测试性。咱们将评测使用Karma编写和运行测试的过程,介绍持续集成的工具Yeomen、Grunt和Bower,以及Angular的其余优点。敬请期待。
原文 A Step-by-Step Guide to Your First AngularJS App
编译 SegmentFault