近日经过图灵社区的教程开始学习AngularJS,在此记录下要点。javascript
教程中有一些不完善的地方,包括php
所须要的工具css
node配置html
教程中的方法已经失效,应使用以下方法:前端
在git中先转到angular-phonecat目录,输入:java
npm install //这一步要一段时间,要耐心等完。强制退出将致使失败,失败后须要删除目录下的node_modules文件夹重来
npm start
每一个教程之间都须要用到checkout命令,建议另开个git bash,这样就不须要重复开启node服务器了。node
一些说明git
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="css/app.css">
<!-- angular.js要放在第一位 -->
<script src="bower_components/angular/angular.js"></script>
<!-- 下面的引入顺序没有影响 -->
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
<script src="js/services.js"></script>
</head>
ng-app标记了AngularJS的做用范围:angularjs
<html lang="en" ng-app>
双花括号绑定表达式,相似于JavaScript表达式,能够用于计算,动态输出,例如:github
<p>1 + 2 = {{ 1 + 2 }}</p>
也能够是从其余地方的变量读取过来,例如:
<html ng-app> <head> ... <script src="lib/angular/angular.js"></script> <script src="js/controllers.js"></script> </head> <body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul> </body> </html>
PhoneListCtrl是controllers.js定义的控制器的名字,controllers.js的内容,大概长这样:
function PhoneListCtrl($scope) {
$scope.phones = [
{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S.",
"age": 0},
{"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1},
{"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet.",
"age": 2}
];
$scope.orderProp = 'age';
}
phones是controllers.js中定义的数据,其做用域$scope是定义ng-controller元素之内的区域(即body)
ng-repeat是AngularJS的迭代器,会遍历phones数组
AngularJS使用了MVC模式,即模型Model-视图View-控制器Controller。
根据个人初步了解,能够做以下对应:
模型:HTML标签,例如:
<ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul>
视图:咱们在页面上看到的实时数据
控制器:在上个例子中,controllers.js就是控制器,他负责模型和视图的同步。
抽象地说,双向绑定即数据从视图到模型的绑定,以及从模型到视图的绑定。
在实际的例子中,能够这样理解:假如视图中一个input元素(视图)绑定了query变量(模型),那么输入框内容(视图)的修改,会实时更新到这个变量(模型)中;反之亦然。
能够看下面迭代器过滤这个例子
迭代器中使用filter能够实时过滤输出
下面这个输入框绑定了query,当输入框(视图)修改内容时,变量query(模型)的值会实时更新,这个更新的直接表现为filter:query改变,致使列表结果的更新。
<input ng-model="query">
在迭代器中添加filter:query,能够实时显示匹配搜索框的结果
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
相似地,若是为每一个phone实例添加一个能够比较大小的属性,如age,那么咱们就能够有name和age两种排序方式能够选择。
Search: <input ng-model="query"> Sort by: <select ng-model="orderProp"> <option value="name">Alphabetical</option> <option value="age">Newest</option> </select> <ul class="phones"> <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul>
select选中项的value值(视图)会实时更新到orderProp(模型)中,迭代器会根据实际值来进行排序输出。
XHR即XMLHttpRequest。以前咱们的数据都是定义在js文件内的,如今咱们也能够用XHR异步请求一个JSON文件,并在请求成功后的回调函数里,将解析后的JSON赋值给模型。
而所谓依赖注入,即AngularJS会根据你的须要,加载须要的服务(这里咱们须要的是$http),以及该模块依赖的服务。
function PhoneListCtrl($scope, $http) {//将须要的模块名做为参数传入
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
PhoneListCtrl.$inject = ['$scope', '$http'];
最后一行代码是为了防止JS压缩时,参数名字被修改致使没法识别正确的模块名而致使的错误。
也可使用传入数组的方式:
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
对于img元素,若是src中带有{{…}},因为在AngularJS解析前浏览器就尝试直接去获取改src的资源,便会致使错误。所以,能够将src属性改写为ng-src:
<img ng-src="{{phone.imageUrl}}">
相似的还有Title元素,能够用ng-bind-template来替换,如:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
关于ng-bind-template和ng-bind的区别,官方解释以下:
ng-bind-template
The ngBindTemplate directive specifies that the element text content should be replaced with the interpolation of the template in the ngBindTemplate attribute. Unlike ngBind, the ngBindTemplate can contain multiple {{ }} expressions. This directive is needed since some HTML elements (such as TITLE and OPTION) cannot contain SPAN elements.
即ng-bind-template能够包含{{}},而ng-bind则不能包含{{}}(ng-bind内的模型直接使用便可,不须要{{}})
AngularJS能够方便地实现前端路由和多视图功能,能够在一个页面内,在不彻底刷新的状况下跳转到另外一个页面。
在这种状况下,index.html是一个空模版:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="css/app.css">
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
body内的div具备ng-view属性,表明他是载入其余页面的容器。
AngularJS的路由功能,可让该容器,在不一样URL上载入不一样的页面模版。
使用路由功能的JS代码以下:
'use strict';
/* App Module */
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
$routeProvider.when告诉了浏览器路由规则,包括使用的模版页面以及对应的控制器。
注意/phones/:phoneId,冒号声明的变量将会被提取,并存放到$routeParams中,调用方法以下:
controllers.js
'use strict';
/* Controllers */
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}]);
过滤器,这名字乍一听,以为会和前面讲的”迭代过滤器”混淆。对他进行一番了解后,以为”映射器”的名字更为合适,也更容易理解。他的功能很容易理解:
假若有一个模型的值是true或者false,咱们能够利用过滤器,将其在视图中映射为√或者×。
过滤器关键代码以下:
'use strict';
/* Filters */
angular.module('phonecatFilters', []).filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';//√和×的unicode编码
};
});
这样咱们新建了一个名为phonecatFilters的模块,其中有名为checkmark的过滤器。以后在phonecatApp里添加对phonecatFilters模块的依赖:
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers',
'phonecatFilters'
]);
以后就能够在模型中使用了:
...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...
AngularJS有本身的一套事件处理机制,click事件绑定以下,首先定义事件处理函数:
function PhoneDetailCtrl($scope, $routeParams, $http) {
...
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}
而后在模版中进行绑定
<img ng-src="{{mainImageUrl}}" class="phone">
...
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
</li>
</ul>
...
我对定制服务的理解还不是很是深入。定制服务的优点,原文是这样描述的
对咱们应用所作的最后一个改进就是定义一个表明RESTful客户端的定制服务。有了这个客户端咱们能够用一种更简单的方式来发送XHR请求,而不用去关心更底层的$http服务(API、HTTP方法和URL)。
咱们定义的服务写在services.js中,另外咱们还须要ngResource模块的$resource服务,他被定义在angularjs-resource.js中。
services.js
'use strict';
/* Services */
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
在app.js中引入phonecatServices模块
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers',
'phonecatFilters',
'phonecatServices'
]);
以后,咱们就能够把controllers.js改写为下面这样
'use strict';
/* Controllers */
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
function($scope, Phone) {
$scope.phones = Phone.query({phoneId:'phones'});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
function($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);
对services.js做一些说明:
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
//声明一个phonecatServices模块
phonecatServices.factory('Phone', ['$resource',
//使用工厂方法,声明一个名为Phone的服务
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
//定义Phone服务的query方法,用于GET资源'phones/:phoneId.json',其中:phoneId是变量,该默认值被定义为"phones"。固然也能够不声明默认变量,而是在调用时传入实参,下面会说起。
});
}]);
对controller.js做一些说明:
'use strict';
/* Controllers */
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
function($scope, Phone) {
$scope.phones = Phone.query();
/* 以前:$http.get('phones/phones.json').success(function(data) { $scope.phones = data;}); 如今:$scope.phones = Phone.query(); 可见这里被简化了。若是不给Phone.query传入实参,他会使用默认参数phones,固然咱们也能够手动传入参数:Phone.query({phoneId:'phones'}) */
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
function($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
/* 在services.js中并无定义Phone.get方法,为何可使用它呢? 经过查阅API文档可知,$resource方法的返回值是一个可被扩展的对象,其自带get,save,query(query已经被咱们扩展了),remove,delete函数。 经过get方法,并传入被点击的连接对应的phoneId做为参数,获取手机详细页所须要的json文件,在成功后执行回调函数。 */
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);