简介
创建一个简单的邮件应用程序,学习核心的AngularJS概念。在结束该教程的学习时,你将能够看到(虚构的)邮件应用程序,在该程序中,可以根据邮件主题查询邮件,还可以阅读或删除邮件。
学习的先决条件:
- 会使用jQuery创建简单的JavaScript应用
- 懂得如何启动简单的HTTP服务器(如 python -m SimpleHTTPServer)
- 会从Github上克隆一个项目
涵盖的主题
- 单页面应用(SPA)
- 客户端MVC模式
- 双向数据绑定
- 路由与模板
- AngularJS构建块:指令标签(Directives),工厂(Factories)以及控制器(Controllers)
客户端MVC
- 模型(Model):可以说是数据;一个应用的业务信息;
- 视图(View):HTML和数据的呈现。是用户所见到和交互的界面;
- 控制器(Controller):让应用中的所有不同模块协同工作的连接器。
开始使用AngularJS
在页面中引入AngularJS
在HTML页面的底部<body>结束标签之前引入AngularJS的.js文件。
<html>
<head>
</head>
<body>
<div></div>
<script src="lib/jquery-v1.11.1.js"></script>
<script src="lib/angular-v1.2.22.js"></script>
</body>
</html>
Angular自身内置了一个叫做“jQLite”的东西,它是一个类似jQuery的微型库。jQLite是一个非常轻量级的库,因此也就没有你可能需要的强大的jQuery方法。这里引入jQuery主要是出于可能后续会引入一些依赖于jQuery库的插件。
设置作用域(Scopes)和指令标签(Directives)
Angular非常基础的一个概念是作用域(scopes)。Scopes保存着你的Models(也就是你的数据),它与你的控制器(Controllers)一起协作,同时为视图(Views)提供了所需的一切。Angular的scopes概念与常见的JavaScript中的作用域(scope)概念非常相似。
第一步是需要设置整个应用的作用域,它标识出了Angular应用起作用的范围。通常我们会在<html>标签中通过ng-app属性来设置它。
<html ng-app="myApp">
<head></head>
<body></body>
</html>
另外一个与scope相关的指令是ng-controller,它决定了一个控制器的作用域范围。在一个应用中可以包含多个控制器。每个控制器都有其自身的作用域范围。
<div ng-controller="InboxCtrl">
<!-- inside InboxCtrl scope -->
</div>
ng-app和ng-controller都是Angular指令标签。可以把Angular指令标签看作是对HTML的扩展。
你的第一个控制器:一个简单的例子
把我们刚刚学到的用在实践中
1 创建一个空的HTML文档
2 引入Angular和jQuery文件
<script src="lib/jquery-v1.11.1.js"></script>
<script src="lib/angular-v1.2.22.js"><script>
3 在<html>标签中添加ng-app="myApp"
4 在body中创建一个控制器示例
<div ng-controller="TestCtrl"><h1></h1>
<input type="text" ng-model="title">
</div>
稍后我们会解释ng-model="title"的含义。
5 创建内嵌的JavaScript脚本,确保该脚本在所引用库文件之后。
<script>
function TestCtrl($scope) {
$scope.title = 'Write a title here...';
};
</script>
是否已发现函数的名字跟ng-controller的值是一样的?Angular会在JavaScript中寻找相同名称的函数来作为控制器。
以下是完整的代码:
<!doctype html>
<html ng-app>
<head>
<title>Sample AngularJS Controller</title>
</head>
<body>
<div ng-controller="TestCtrl">
<h1></h1>
<input type="text" ng-model="title">
</div>
<script src="lib/jquery-v1.11.1.js"></script>
<script src="lib/angular-v1.2.22.js"></script>
<script>
function TestCtrl($scope) {
$scope.title = 'Write a title here...';
};
</script>
</body>
</html>
ngView与路由(Routes)
将URL与作用域关联
另外一个重要的构建块是ng-view指令标签,它能够将应用中的某个URL关联到作用域中。
<html ng-app="myApp">
<head>
</head>
<body>
<div ng-view></div>
</body>
</html>
这里的ng-view标签能够根据用户访问的URL告诉Angular我们想插入的HTML。
模块(Modules)
angular.module()
每个应用需要一个模块,而Angular通过angular.module()为我们提供了模块命名空间的能力。该方法既可以设置模块也可以获取一个模块,关键取决于如何使用它。创建(设置)一个新的模块,如:
angular.module('myApp', []);
获取一个模块的引用,用以注册controllers,factories,filters等,可以不用后面的那个数组而只需通过模块的名称:
angular.module('myApp');
路由(Routing)与依赖注入(Dependency Injection)
路由
接下来是配置路由,路由使应用能够根据我们访问的URL来确定加载哪个视图。
RouteProvider
从Angular 1.2.0版本开始,核心的Angular中不在包含$routeProvider,因此需要将其作为一个独立的模块引入(angular-route.js)。
<body>
<!-- ... -->
<!-- Extra routing library -->
<script src="lib/angular-route-v1.2.22.js"></script>
</body>
该文件为我们提供了一个名为ngRoute的模块,在我们自己的模块定义的时候需要将其作为依赖引入:
var app = angular.module('app', [
'ngRoute'
]);
配置阶段
Angular中每个模块具有一个.config()方法,可以给该方法传入一个回调函数,该回调函数将在很多其他函数执行前调用。就是在这个地方配置我们的路由:
app.config(function () {/*...*/});
依赖注入
在config回调函数中,我们需要$routeProvider来配置路由,可以简单地把$routeProvider作为config函数的参数传入,剩下的事情交由Angular处理。
app.config(function ($routeProvider) {
/* Now we can use the routeProvider! :D */
});
配置路由
app.config(function ($routeProvider) {
$routeProvider
.when('/inbox', {
templateUrl: 'views/inbox.html',
controller: 'InboxCtrl',
controllerAs: 'inbox'
})
.when('/inbox/email/:id', {
templateUrl: 'views/email.html',
controller: 'EmailCtrl',
controllerAs: 'email'
})
.otherwise({
redirectTo: '/inbox'
});
});
控制器(Controllers)
连接模型与视图
一个好的控制器只包含尽可能少的逻辑在里面,且只完成两项工作:将模型绑定到视图(初始化视图);为视图添加帮助函数。
app.controller('InboxCtrl', function () {
// Model and View bindings
// Small helper function not needed anywhere else
});
每个控制器都能够访问$scope。通过$scope可以往视图上添加属性和方法。
app.controller('InboxCtrl', function ($scope) {
// initialize the title property to an array for the view to use
$scope.title = "This is a title";
});
在标签中可以这样与之对应:
<div ng-controller="InboxCtrl">
{{ title }}
</div>
工厂(Factories)
工厂常用于通过HTTP与服务端交互。
可以通过angular.factory()方法创建一个工厂,如:
app.factory('ExampleFactory', function ExampleFactory($rootScope, $http, $location) {
return function myReusableFunction() {
// do something fancy
};
});
在该应用中,需要获取邮件信息,可以创建一个方法来实现。Angular使用$http服务来与服务端交互,因此需要将它也引入:
app.factory('InboxFactory', function InboxFactory ($http) {
var exports = {};
exports.getMessages = function () {
return $http.get('json/emails.json')
.error(function (data) {
console.log('There was an error!', data);
});
};
return exports;
});
关联工厂与控制器
app.controller('InboxCtrl', function($scope, InboxFactory) {
InboxFactory.getMessages()
.success(function(jsonData, statusCode) {
console.log('The request was successful!', statusCode, jsonData);
// Now add the Email messages to the controller's scope
$scope.emails = jsonData;
});
});
模板(Templating)
视图渲染
<!-- JavaScript inside expression -->
<h1>{{ [ firstName, lastName ].join(" ") }}</h1>
<!-- currency filter applied to a multiplication of 2 numbers -->
<div class="total-info">{{ cost * quantity | currency }}</div>
<!-- Using a ternary expression inside the expression -->
<div class="budget">{{ ( total > budget ) ? "Too Expensive" : "You can buy it!" }}</div>
指令标签概览
自定义HTML标签
指令标签是Angular提供的自定义HTML标签,它为封装数据、模板与行为提供了一种可重用的方式。
在我们的视图中,有时可以见到如下的标签:
<div id="someview">
{{ data.scopePropertyAsUsual }}
<my-custom-element></my-custom-element>
</div>
上面的<my-custom-element>标签会被我们定义的标签模板和逻辑替代。以下为一个简单的指令标签结构示例:
app.directive('myCustomElement', function myCustomElement() {
return {
restrict: 'EA',
replace: true,
scope: true,
template: [
"<div>",
" <h1>My Custom Element's Heading</h1>",
" <p>Some content here!</p>",
" <pre>{{ ctrl.expression | json }}</pre>,"
"</div>"
].join(""),
controllerAs: 'ctrl',
controller: function ($scope) {
this.expression = {
property: "Example"
}
},
link: function (scope, element, attrs) {}
}
});
工厂与指令协同工作
angular.module('EmailApp')
.factory('InboxFactory', function InboxFactory ($q, $http, $location) {
'use strict';
var exports = {};
exports.messages = [];
exports.goToMessage = function(id) {
if ( angular.isNumber(id) ) {
// $location.path('inbox/email/' + id)
}
}
exports.deleteMessage = function (id, index) {
this.messages.splice(index, 1);
}
exports.getMessages = function () {
var deferred = $q.defer();
return $http.get('json/emails.json')
.success(function (data) {
exports.messages = data;
deferred.resolve(data);
})
.error(function (data) {
deferred.reject(data);
});
return deferred.promise;
};
return exports;
});
app.directive('inbox', function () {
return {
restrict: 'E',
replace: true,
scope: true,
templateUrl: "js/directives/inbox.tmpl.html",
controllerAs: 'inbox',
controller: function (InboxFactory) {
this.messages = [];
this.goToMessage = function (id) {
InboxFactory.goToMessage(id);
};
this.deleteMessage = function (id, index) {
InboxFactory.deleteMessage(id, index);
};
InboxFactory.getMessages()
.then( angular.bind( this, function then() {
this.messages = InboxFactory.messages;
}) );
},
link: function (scope, element, attrs, ctrl) {
/*
by convention we do not $ prefix arguments to the link function
this is to be explicit that they have a fixed order
*/
}
}
});
用于上述指令标签的完整HTML模板:
<div class="inbox">
<div class="inbox__count">
You have {{ inbox.messages.length && inbox.messages.length || 'no' }} messages
</div>
<div ng-hide="!inbox.messages.length">
<input type="text" class="inbox__search"
ng-model="inbox.search"
placeholder="Search by 'from', e.g. TicketMaster">
</div>
<ul class="inbox__list">
<li ng-show="!inbox.messages.length">
No messages! Try sending one to a friend.
</li>
<li ng-repeat="message in inbox.messages | filter:{ from: inbox.search }">
<div class="inbox__list-info">
<p class="inbox__list-from"> from: {{ message.from }} </p>
<p class="inbox__list-date"> {{ message.date | date: 'dd/MM/yyyy' }} </p>
<p class="inbox__list-subject"> {{ message.subject }} </p>
</div>
<div class="inbox__list-actions">
<a href="#" ng-click="inbox.goToMessage(message.id);">
Read
</a>
<a href="" ng-click="inbox.deleteMessage(id, $index);">
Delete
</a>
</div>
</li>
</ul>
</div>
内置指令标签
ng-show, ng-repeat, ng-click
<ul>
<li ng-repeat="item in items">
{{ item.name }}
</li>
</ul>
<li ng-repeat="message in inbox.messages | filter:{ from: inbox.search }">
<input type="text" class="inbox__search" placeholder="Search" ng-model="inbox.search">
本文翻译自这里。
翻译过程中加入了译者本人的理解,并作了适当的筛减。