书名:Ionic学习手册
ISBN:978-7-115-45340-2
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [印度] Arvind Ravulavaru
译 刘明骏 李 阳 等
责任编辑 傅道坤
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright © 2015 Packt Publishing. First published in the English language under the title Learning Ionic.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
Ionic是一个用来开发混合手机应用的开源代码库,它可以优化HTML、CSS和JavaScript的性能,构建高效的应用程序。
本书作为Ionic的学习手册,重点讲解了使用Ionic来开发移动混合应用的方法。本书共分为9章,内容包含Ionic产生的背景、依赖的技术和简单介绍,Ionic的组件构成,如何使用SCSS更改Ionic的主题,如何使用Ionic的指令和服务加速开发,通过一个示例来详解Ionic的开发步骤,如何借助于Cordova和ngCordova与设备的功能进行集成,综合利用所学知识开发一个聊天App,以及与发布Ionic App相关的知识。
本书内容实用、步骤详细,适合对移动应用开发感兴趣的读者阅读。
本书是Arvind Raulavaru耗费了数月,精心编写的。我和Arvind Raulavaru有过多次合作,每次合作都十分愉快,他是一名优秀的开发者和作者。本书是一本很好的Ionic入门图书,提供了丰富而详细的实例,即便是经验并不丰富的开发者也会大有收获。
本书不仅讲解了如何安装Ionic,还说明了如何构建出原生安装包。此外,本书还涉及了大量Ionic的基础知识,比如Ionic的组件、UI-router导航、自定义样式以及Ionic特有的API。最后,作者还提供了一个书店App的实例和一个实时聊天App的实例。
对于经验丰富的开发者来说,本书详解了如何通过Cordova的插件机制来使用设备原生API。你将学到如何在AngularJS语法中使用ngCordova(Ionic团队的另一个项目)和Cordova插件。在聊天App实例中,本书会讲到如何连接外部数据库,比如Firebase,同时还会讲解如何在多设备间同步数据。
在加入Ionic团队之前,我曾为另一家公司制作了许多内部的混合模式App(Hybrid App),其间评估了多个框架,最终选择了Ionic。我之所以选择Ionic,是因为它的完备性,它提供了一套完备的混合移动开发的解决方案,大量常用的功能在Ionic中都有提供。我不用关心如何搭建一套好的App框架,只需要关心如何实现我的App功能即可。
Ionic提供了完整的混合模式移动应用开发的生态系统,是一种替代原生开发的优秀方案——成本低、性能高。2015年的五月我们先发布了Ionic的稳定版本,随后我们又发布了三个平台服务。在支持开源的IonicSDK上,我们仍将不遗余力地加紧迭代。在Ionic中,我接触到很多开发者,有经验丰富的,也有新手,大家互相学习、探讨、让我深深感受到了大家对Ionic的热爱,Ionic社区是多么的有热情洋溢、积极向上。
你将发现本书会是Ionic的全面介绍,你也会通过本书来学习一些SDK相关的知识。感谢你成为Ionic社区的一员。请享受阅读本书的乐趣!
Ionic团队核心成员
Mike Hartington
Arvind Ravulavaru是一名全能的全栈工程师,在软件开发方面已经有超过6年的经验了。最近两年,他主要从事JavaScript相关的研发工作,涉及客户端和服务器端。在此之前,Arvind主要从事大数据分析、云存储等工作。此外,Arvind还擅长使用多种数据库以及Java和ASP.NET架构应用程序。
一年半前,Arvind开始写博客(名为The Jackal of JavaScript)(http://thejackalof javascript.com), Arvind经常会在博客中写些使用JavaScript编写整个应用程序的文章。此外,他还写了许多其他主题的文章,比如使用JavaScript分析DNA、使用JavaScript做情绪分析、通过JavaScript对树莓派编程,还有基于node-webkit和WebRTC打造的视频聊天客户端。
除此之外,Arvind还为公司提供技术培训,帮助公司掌握市场上可用的最新的技术和最好的技术。他还举办了一些研讨会,并使用当今一些优秀的工具堆栈来讲解快速成型的方法。Arvind还提供了在短时间内将一些创意应用到市场中的信息。
Arvind还不断地为开源社区做贡献,为开发人员提供便利。作为顾问,Arvind还常常提出一些中肯的商业建议(技术相关),以此推动整个行业的发展。
Arvind最近在海德拉巴市开设了自己的公司,这家公司致力于以可接受的价格提供人人可享的物联网相关产品。
Arvind的博客地址是http://thejackalofjavascript.com。
同时,Arvind还是Data-oriented Development with AngularJS一书的审稿人。
感谢史蒂夫·乔布斯,他的事迹鼓舞着我,激励着我。高价卖出一件人们并不十分需要的产品是十分困难的,但他一次又一次地做到了,从Mac book到Apple Watch。安息吧,乔布斯先生。
感谢我的家人,感觉千言万语都无法表达出我的感激之情,特别是对我的母亲。他们与我一同度过了快乐时光,也一同克服艰难困苦,无时无刻不在鼓励着我。没有他们的帮助,我无法取得今天的成就。
此外,我还要特别感谢 Nicholas Gault和Andrew Nuss,感谢你们对我的支持与鼓励。
感谢Udaykanth Rapeti赠予我的金玉良言:“要想真正成功,先将自己数字化 [1]。”特别感谢我的同事Karthik Naidu和Pavan Musti。这是最好的时代!
感谢Ali Baig(自称yoda),感谢他提供的各种建议和见解。我在Accenture和Cactus迸发出了大量的想法,我珍视和同事们一同学习、玩乐、摇滚的日子。
这里还要感谢我的初恋。你教会了我很多,让我成为了更优秀的人。我们的分手是我寄情于工作的原因,这也造就了我的今天。十分感谢。
本书共有9章和一篇附录,编写起来也是工程浩大,又是编辑又是审阅。真诚地感谢Merwyn D'souza(本书的内容开发编辑),感谢Bramus Van Damme、Ian Pridham和Indermohan Singh(本书的审稿人),因为他们本书才变得出色。很荣幸能和Shashank Desai(本书的技术编辑)合作。还要感谢Nikhil Nair以及Packt Publishing团队中的所有人,你们让一切成为可能。
十分感谢Hemal Desai找到我编写本书,没有她就没有这本书。 特别感谢的是我博客的读者,你们是好样的!
最后,感谢开发者社区的Robin Hoods,感谢你开发并共享的代码!
[1] 译者注:指的是多开设博客等,让自己在互联网有存在感。
Bramus Van Damme是比利时的一位Web爱好者,1997年互联网诞生时,他就对互联网产生了浓厚的兴趣。
Bramus Van Damme以Web开发人员的身份在多家Web机构从事了多年的研发工作,然后在比利时的一所技术大学中讲授Web技术。他不仅教授HTML、CSS和JavaScript,还教授SQL的使用。作为一名讲师,他还负责编写和维护服务器端脚本语言(PHP)和Web及移动开发课程。
从事了7年的教育工作后,他又加入了Small Town Heroes(位于比利时的根特),为电视提供更具交互性的解决方案。
Bramus也是使用了RDS moniker(https://www.3rds.be/)的自由职业者。他会定期参加一些Web聚会和会议,并发表演讲。当他看到优化且安全的代码时,会发自内心的高兴。
闲暇时间,他研究Web相关的技术,以此来更新他的博客(https://www.bram.us/)。作为一名侦查兵,他对地图技术十分痴迷。他还结合了他的技术知识,审阅了Google Maps JavaScript API Cookbook一书。
目前,他和他的儿子Finn和女儿Tila住在比利时的芬克特,另外,他还喜欢小猫。
Ian Pridham是一名具有15年研发经历的全栈工程师,现居住在伦敦。Ian为Department of Transport(一个政府的客户端)创建了电动汽车充电点的API,同时还为SB.TV(英国领先的在线青年广播公司)开发了网站。这个网站的特色是能在Google的Chrome浏览器上投放电视广告。此外Ian为用AngularJS/Ionic创业的企业提供咨询和开发服务,以便能让他们的想法更快地投放市场。目前他担任OpenPlay(一家活动市场平台公司)的CTO。
Indermohan Singh是一名Ionic开发人员,同时也是一名热情的创业者,他在美丽的卢迪亚纳城市运营着一家移动应用程序开发工作室。他也是Ragakosh(印度古典音乐学习App)的创始人。Indermohan的博客地址是http://inders.in,他在卢迪亚纳举办了AngularJS的聚会。他同时也是Sublime Text和Atom Editor的Ionic插件开发人员。如果他不在计算机前,那么他就有可能在弹他的塔布拉鼓(一种乐器)。
感谢我的家庭—我的父母以及兄弟,感谢你们在我审阅本书期间提供的帮助。也感谢上苍能给予我强壮的身体以及良好的教育,让我有能力审阅此书。
使用Ionic可以很容易地构建移动混合应用。无论是集成REST API端点的简单App,还是涉及大量原生功能的复杂App,我们都可以通过Ionic提供的简单API和功能来方便地创建App。
只要我们会HTML、CSS以及AngularJS,就可以将我们的想法通过几行代码做成App。
在本书中,我们就将介绍如何做到这一切。
第 1章,Ionic——基于AngularJS框架,将带你领略AngularJS的强大能力,说明Ionic选择它的原因。
第 2 章,Ionic入门,介绍了移动混合应用框架 Cordova。通过它,Ionic可以起到更大的作用,构建更强的App。本章还会介绍如何安装Ionic开发所需要的软件。
第3章,Ionic CSS组件和导航,教你如何在开发移动Web应用时,像使用CSS框架那样使用Ionic。本章还介绍如何在AngularJS中集成Ionic CSS,以及如何使用Ionic中的路由功能。
第4章,Ionic和SCSS,探讨如何使用内置的SCSS支持来更改Ionic的主题。
第 5 章,Ionic指令和服务,介绍了如何使用Ionic内置的指令(Directive)和服务(Service),并通过它们加快开发速度。
第6章,构建书店App,运用目前为止所学的知识构建一个使用了安全的REST API的Ionic客户端。本章介绍了开发Ionic App的详细步骤(这会用到REST API端点)。
第7章,Cordova和ngCordova,学习如何在Ionic App中使用Cordova和ngCordova调用设备的原生功能,如摄像机和蓝牙等。
第8章,构建聊天App,运用所学的知识构建一个可以注册、登录、相互交流以及分享图片和位置的聊天App。
第9章,发布Ionic应用,主要介绍如何借助于Ionic CLI和PhoneGap构建系统,为使用Cordova和Ionic开发的App构建安装包。
附录A,其他实用命令及工具,讨论如何高效使用Ionic CLI和Ionic云服务来构建、部署和管理你的Ionic应用。
要用Ionic开发App,我们需要对HTML、CSS、JavaScript以及AngularJS有一定了解。如果了解移动应用、设备的原生功能和Cordova,那就更好了。
同时,如果要使用Ionic,还需要安装Node.js、Cordova CLI和Inoic CLI。如果你还需要加入一些主题支持和其他第三方类库的话,还需要安装Git和Bower。如果你要使用设备的原生能力,比如相机或蓝牙,你还需要安装移动操作系统。
本书适合想要使用Ionic来开发混合移动应用的人阅读。如果你想要处理Ionic应用的主题、与REST API进行集成,或者想学习可以使用ngCordova来调用设备功能(比如相机和蓝牙)的知识,也可以阅读本书。
Ionic是目前应用最广泛的移动混合框架。在本书写作时,GitHub上就已超过了17000颗星,fork的数量更超过2700次。Ionic是基于AngularJS打造的,众所周知,AngularJS是一款功能强大的框架,用于构建MVW应用。本章我们将着重介绍AngularJS,理解它是如何为Ionic提供强大功能的。这里我们将介绍Ionic中广泛使用的几个AngularJS组件——指令(Directive)和服务(Service)。
提示:
本书假定你对AngularJS已有一定了解。如果还没有了解,可以参考AngularJS Essentials(Rodrigo Branas编写),或者看视频教程Learning AngularJS(Jack Herrington制作),这些都由Packt Publishing出品。这些资料可以让你对AngularJS有一定了解。
在本章中,我们只会着重介绍AngularJS的指令和服务。对于AngularJS中其他的一些关键概念,可以参考前面提到的书籍和视频。
本章中,我们将讲解以下内容:
服务器端Web应用已经发展了一段时间。随着Web应用的界面发展越来越迅速,我们不得不将焦点从服务器端转向客户端。从前那些由服务器端来决定客户端行为与用户界面显示的日子已一去不复返。
如今Web页面的交互体验越来越好,异步交互也越来越多,客户端驱动应用的优势就会体现出来,在实现更优秀的用户体验上会比服务器端驱动的应用更容易。jQuery和Zepto这样的库也让这一点变得更容易。接下来,让我们先来看个典型的例子,用户将在一个文本框中输入信息,随后点击Submit按钮。输入的信息将通过AJAX传输给服务器,最后将服务器端的返回展现在界面上,期间不会刷新页面。
如果我们使用的是jQuery(使用伪语法),那么代码应该如下:
//假设已加载jQuery,并且已初始化了输入框、按钮以及结果展示区
var textBox = $('#textbox');
var subBtn = $('#submitBtn');
subBtn.on('click', function(e) {
e.preventDefault();
var value = textbox.val().trim();
if (!value) {
alert('Please enter a value');
return;
}
// 通过AJAX调用获取数据
var html2Render = '';
$.post('/getResults', {
query: value
})
.done(function(data) {
// 处理服务器返回
var results = data.results;
for (var i = 0; i < results.length; i++) {
// 循环拼装结果用以显示
var res = results[i];
html2Render += ' < div class = "result" > ';
html2Render += ' < h2 > ' + res.heading + ' < /h2>';
html2Render += ' < span > ' + res.summary + ' < /span>';
html2Render += ' < a href = "' + res. link + '" > ' + res.linkText + ' < /a>';
html2Render += ' < /div>'
}
// 将拼装好的HTML注入到结果显示区域中
$('#resultsContainer').html(html2Render);
});
});
提示:
以上代码无法正常运行,只是一个用于参考的例子。
当点击按钮时,输入框中的内容会被传输到服务器上。然后,将服务器返回的JSON对象拼装成HTML,最终把结果显示在指定的区域中。
但是,如果我们想更好地维护管理这些代码,又该如何做呢?
又或者你将如何做到对每个功能块的分开测试呢?比如,我们想测试校验功能是否正常,又或者想看看服务器返回是不是正常。
如果我们想修改结果显示区域的显示模板(例如,增加一个图标),在上面代码的基础上,又该如何快速修改呢?
这个时候我们就需要用到SOC,通过SOC我们可以将验证、AJAX请求以及组装HTML代码进行解耦。目前它们是相互耦合的,其中任何一个步骤都无法独立运行,但它们之间会互相影响。
如果我们要将这些不同的执行代码放到不同的组件中,则可以通过模型视图控制器(MVC)架构来实现。通常在MVC架构中,model是一个用于存放数据的实体,controller用于传递数据给view,view用于展示内容。
不同于服务器端的MVC架构,客户端的MVC架构会多一个路由。路由通常是一个页面的URL,通过这个URL可以决定加载哪个model/view/controller。
MVC架构是AngularJS的基本思想,它不仅实现了关注分离,还提供了单页面应用的架构。
回看下前面的例子,我们可以将AJAX与服务器交互的部分从主代码中抽出来,然后按需集成到不同的controller。
接下来让我们看一些重要的AngularJS组件,以便更好地了解这一点。
AngularJS主要通过HTML来使用,这点和许多客户端的JavaScript框架不同。在一个典型的Web应用中,AngularJS负责为你编写关键的代码段。通过在HTML页面中添加一些AngluarJS提供的指令并包含AngularJS源文件,我们甚至可以做到不写一行JavaScript代码而开发出一款简单应用。
接下来,我们将通过构建一个带有验证功能的登录框来说明如何做到上面这点。
比如下面这段代码:
<html ng-app="">
<head>
<script src="angular.min.js" type="text/JavaScript"></script>
</head>
<body>
<h1>Login Form</h1>
<form name="form" method="POST" action="/authenticate">
<label>Email Address</label>
<input type="email" name="email" ng-model="email" required>
<label>Password</label>
<input type="password" name="password" ngmodel="password" required>
<input type="submit" ng-disabled="!email || !password" value="Login">
</form>
</body>
</html>
在上面的代码块中,以ng-开头的属性称为AngluarJS指令。
如果用户输入的e-mail和password不合法,ng-disabled
指令会在Submit按钮上添加disabled 属性,让按钮不可点击。
而且,这个指令的作用域会被限定在所在的元素及其子元素内,这样就可以避免因定义变量不当而作用在Global Object(全局对象,这里是Window Object)中的问题。
提示:
如果你之前没有了解过作用域,建议你访问https://docs.angularJS.org/guide/scope。对作用域的理解对阅读本书十分重要。
接下来,我们将介绍另一个AngularJS组件——依赖注入(DI)。DI可以在需要的地方注入所需的代码段,通过DI我们可以实现关注分离。
你可以在需要的时候注入不同的AngularJS组件。比如,在controller中注入某个服务(service)。
提示:
DI是AngularJS中的核心组件,同时也是你需要掌握的。更多信息,可以访问https://docs.angularJS.org/guide/di。
要理解service和controller,需要先理解一下MVC。在通常的客户端MVC框架中,model用于存放数据,view用于展示数据,controller用于对model中的数据进行处理,然后再传递给view。
在AngularJS中,我们可以这么理解它们的关系:
在AngularJS中,HTML相当于模板。AngularJS的controller会获取scope对象中的数据或一个服务的响应,然后根据最终view所需展示的效果做数据融合。对应到我们之前的搜索例子中,就相当于我们将服务器的返回进行处理,构造成HTML字符串,然后将这段HTML注入到DOM中。
接下来,我们把之前搜索实例中涉及到的功能进行拆分。
在下面的代码中,HTML相当于模板,factory组件负责实现AJAX请求。最后controller将从factory组件中获取的数据进行处理,然后传给用于展示的视图。
下面就是AngularJS版本的搜索引擎实例:
index.html
中的代码如下:
<html ng-app="searchApp">
<head>
<script src="angular.min.js" type="text/JavaScript">
<script src="app.js" type="text/JavaScript">
</head>
<body ng-controller="AppCtrl">
<h1>Search Page</h1>
<form>
<label>Search : </label>
<input type="text" name="query" ng-model="query"
required>
<input type="button" ng-disabled="!query" value="Search"
ng-click="search()">
</form>
<div ng-repeat="res in results">
<h2>{{res.heading}}</h2>
<span>{{res.summary}}</span>
<a ng-href="{{res.link}}">{{res.linkText}}</a>
</div>
</body>
</html>
app.js
中的代码如下:
var searchApp = angular.module('searchApp',[]);
searchApp.factory('ResultsFactory', ['$http', function($http) {
return {
getResults : function(query){
return $http.post('/getResults', query);
}
};
}]);
searchApp.controller('AppCtrl', ['$scope','ResultsFactory',function($s
cope,ResultsFactory){
$scope.query = '';
$scope.results = [];
$scope.search = function(){
var q = {
query : $scope.query
};
ResultsFactory.getResults(q)
.then(function(response){
$scope.results = response.data.results;
});
}
}]);
提示:
在AngularJS中,factory组件和service组件是可以互换使用的。如果想了解更多细节,可以访问http://stackoverflow.com/questions/15666048/service-vs-provider-vs-factory。
index.html
文件由HTML模板组成,在页面加载时,这些模板会被隐藏。当结果数组使用数据填充时,ng-repeat
指令会根据模板生成HTML代码块。
在app.js
中,我们首先定义了一个名为searchApp
的AngularJS模块,然后我们又创建了factory组件,名为ResultsFactory
,该组件的唯一作用是发起AJAX请求以及返回一个promise(promise是一种用异步方式处理值的方法,promise是对象,代表了一个函数最终可能的返回值或者抛出的异常,在与远程对象打交道时,我们可以把它看作是远程对象的一个代理)。最后,我们创建了名为AppCtrl
的controlller,用于协调factory组件以及更新显示界面。
。
提示:
如果你不太了解promise,可以访问http://www.dwmkerr.com/promises-in-angularJS-the-definitive-guide/。
在按钮的ng-click
指令上,我们声明了search
函数,然后在AppCtrl中的定义了该函数。只有在搜索框中输入合法的数据时,按钮才可以被点击。当点击Search按钮时,就会调用之前在按钮上注册的函数,此处为AppCtrl中的search函数。这里我们定义了用于传给后端服务器的query对象,然后调用了ResultsFactory
的getResults
方法。getResults
方法会返回一个promise,当请求后端服务器成功时则会继续执行。这里我们假设是成功的,我们会将服务器返回的结果赋予$scope.results
。
$scope
对象的results
数组一旦发生变化,就会触发结果数组中所有实例的更新,这反过来会触发HTML模板中的ng-repeat
指令,于是便会解析新results
数组并生成新的HTML代码块,然后搜索结果的UI也会跟着变。
下载示例代码:
可以访问http://www.packtpub.com/support,然后申请以邮件方式获取示例代码。对于本章,你也可以在GitHub(https://github.com/learning-ionic/Chapter-1)上与作者沟通交流。
在前面的例子中,我们看到了如何编写易于维护、易于测试的代码。现在如果我们要对应用做些调整就比较简单了(比如在搜索结果旁边添加图片),只要开发者有一定Web开发的经验,就可以轻松地完成修改。
下面这段话摘自AngularJS文档。
“站在一个更高的角度理解,指令是DOM元素的标记(比如属性、元素名、评论或CSS类名),这个标记会告诉AngulaJS的HTML编译器($compile)将某些特定的行为赋予该DOM元素甚至改变DOM元素及它的子元素。”
当我们想对页面中的通用功能进行抽象时,指令就有大用了。AngularJS的指令就是这样用的。
ng-app
:用于初始化AngularJS模块,若未传入值,将初始化默认模块;若传入值,将初始化传入值所对应的模块。ng-model
:将输入元素的值绑定到当前scope对象中。ng-show
:当值为true时,显示该DOM元素。ng-hide
:当值为true时,隐藏该DOM元素。ng-repeat
:根据传入ng-repeat
的表达式,循环输出当前标签及其子元素。现在让我们回到之前构建的搜索应用,想象下如果应用中有多个页面都会用到这样的搜索表单,此时你会怎么办?将代码多复制几份,四处粘贴吗?
肯定不能如此,所以我们要对搜索功能进行抽象,抽象成一个自定义的指令,而不是到处复制粘贴。
通过属性声明,比如<div my-search></div>
,我们可以将一个使用该属性的DOM元素初始化成指令。此外,你也可以创造出自己的标签/元素,比如<my-search></my-search>
。
这样我们就可以一处编写,多处使用了。当view(视图)使用到该自定义指令时,AngularJS会自动对它进行初始化,当view不再使用时,AngularJS则会自动销毁它。是不是很赞?
接下来,就让我们来编写一个自定义指令(my-search)
,并在搜索应用中使用它吧。这个指令(directive)的唯一功能是展示一个文本框和一个按钮。当用户点击其中的Search按钮时,将从服务器获取数据并将结果展现出来。
好了,让我们开动吧。
与任何AngularJS组件一样,自定义指令也可以与模块绑定。下面我们就会将指令与之前searchApp
的模块进行绑定。
searchApp.directive('mySearch', [function(){
return {
template : 'This is Search template',
restrict: 'E',
link: function (scope, iElement, iAttrs) {
}
};
}]);
这里的指令名为采用驼峰格式命名的mySearch
。这样,当在HTML中使用时,AngularJS就会匹配名为my-search
的指令。此外,我们给template
属性设置一段默认值,同时限定该指令只可以用作元素(E)。
提示:
在AngularJS指令中,你可以限定的其他值还有A(attribute)、C(class)以及M(comment)。同时,你也可以允许指令支持所有的4种格式(ACEM)。
这里我们还创建了一个link方法,该方法会在指令被展示时调用。该方法有3个参数,如下所示:
scope
:该参数指的是该指令在DOM中所对应标签的作用域。它可以是AppCtrl
内部的作用域,甚至可以是rootScope(ng-app)
内部的作用域。iElement
:被展示指令元素对应的DOM节点对象。iAttrs
:当前元素所具有的属性。在这个例子中,my-search
标签中没有任何属性,同时我们也不会用到iAttrs
。
如果指令比较复杂,最好将指令模板单独存放在一个文件中,在要用到的时候通过templateUrl
属性引用。接下来,我们就会这么做。
在index.html
的同级目录中创建directive.html
文件,并添加以下内容:
<form>
<label>Search : </label>
<input type="text" name="query" ng-model="query" required>
<input type="button" ng-disabled="!query" value="Search" ng-
click="search()">
</form>
<div ng-repeat="res in results">
<h2>{{res.heading}}</h2>
<span>{{res.summary}}</span>
<a ng-href="{{res.link}}">{{res.linkText}}</a>
</div>
然后,将index.html
中与搜索应用相关的部分去除掉。
接下来,我们为指令中按钮的click
事件编写监听函数(search函数)。代码如下:
searchApp.directive('mySearch', [function() {
return {
templateUrl: './directive.html',
restrict: 'E',
link: function postLink(scope, iElement, iAttrs) {
scope.search = function() {
var q = {
query : scope.query
};
// 接下来与factory交互
}
}
};
}])
当按钮上的click
事件被触发时,就会执行scope.search
函数,同时scope.query
会得到输入框中的值。这和我们之前在controller中做的差不多。
当用户输入一些信息后点击Search按钮时,我们需要调用ResultsFactory
的getResults
方法。然后,当得到返回结果时,将这些结果赋给scope.results。
完整的指令代码如下:
searchApp.directive('mySearch', ['ResultsFactory',
function(ResultsFactory) {
return {
templateUrl: './directive.html',
restrict: 'E',
link: function postLink(scope, iElement, iAttrs) {
scope.search = function() {
var q = {
query : scope.query
};
ResultsFactory.getResults(q).
then(function(response){
scope.results = response.data.results;
});
}
}
};
}])
接着修改index.html
文件:
<html ng-app="searchApp">
<head>
<script src="angular.min.js" type="text/JavaScript"></script>
<script src="app.js" type="text/JavaScript"></script>
</head>
<body>
<my-search></my-search>
</body>
</html>
然后修改app.js
文件:
var searchApp = angular.module('searchApp', []);
searchApp.factory('ResultsFactory', ['$http', function($http){
return {
getResults : function(query){
return $http.post('/getResults', query);
}
};
}]);
searchApp.directive('mySearch', ['ResultsFactory',
function(ResultsFactory) {
return {
templateUrl: './directive.html',
restrict: 'E',
link: function postLink(scope, iElement, iAttrs) {
scope.search = function() {
var q = {
query : scope.query
};
ResultsFactory.getResults(q).
then(function(response){
scope.results = response.data.results;
});
}
}
};
}]);
简单而强大!
现在通过使用<my-search></my-search>
标签,你就可以很方便地在需要的地方添加搜索栏了。
如果想做得更好,我们可以给该指令传入一个名为results-target
的属性。该属性的值代表页面中某个元素的ID。利用这点,我们可以将搜索结果显示在页面中的指定位置,而不是固定显示在某个位置。
提示:
AngularJS内嵌了jqLite(jQuery的轻量级版本)。但jqLite不支持选择器查找,如果要使用选择器查找,需要用jQuery代替AngularJS中的jqLite。更多jqLite信息,可以访问https://docs.angularJS.org/api/ng/function/angular.element。
这点使得AngularJS的指令成为了处理DOM时的最佳方案——可复用的组件。
所以,如果你想为你的Ionic应用添加新的导航栏,只需在目标页面使用ion-nav-bar
标签,就像下面这样:
<ion-nav-bar class="bar-positive">
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
这样就搞定了。
通过学习自定义指令,我们可以方便地与Ionic组件(通过AngularJS指令构建)协作。
AngularJS服务是一个可替换对象,通过依赖注入可以向指令和controller中注入不同的AngularJS服务。这些AngluarJS服务对象包含了一些通用业务逻辑代码。
AngularJS服务具有延迟加载特性,组件只有在用到它们后才会被初始化。同时,每个Angular服务都是单例,每个App只会被初始化一次。这使得AngularJS服务有利于在controller间共享数据,或者将这些数据暂存起来。
$interval
是AngularJS中可使用的一种服务。$interval
和setTimeInterval()
作用是一样的。当注册该服务时,$interval
相当于封装了setTimeInterval()
并返回了一个Promise。这个Promise可以用于在之后销毁$interval
。
另一个简单服务是$log
。该服务将信息记录到浏览器的控制台上。下面是一个例子:
myApp.controller('logCtrl', ['$log', function($log) {
$log.log('Log Ctrl Initialized');
}]);
现在你已看到了AngularJS服务的强大能力,实现通用业务逻辑是多么简单。
此外,你也可以自己编写在App中可重用的自定义服务。比如,你可以编写一个计算器服务,方法有add
、subtract
、multiply
、divide
和square
等。
在之前的搜索应用中,我们使用factory组件来负责与服务器端的通信。现在,我们将通过自定义服务来实现。
提示:
服务和factory组件是可以相互替换的。更多信息可参考http://stackoverflow.com/questions/15666048/service-vs-provider-vs-factory。
比如,当用户搜索某个关键词并得到返回结果时,我们会将返回结果保存在本地存储中,以便下次用户再搜索相同的关键词时,可以直接显示相同的结果,而不再通过AJAX请求(类似离线模式)。
接下来,我们为该服务定义以下3个方法。
isLocalStorageAvailable()
:该方法用于检查当前浏览器是否支持存储API。saveSearchResult(keyword,searchResult)
:该方法用于在本地存储中保存关键词和搜索结果。isResultPresent(keyword)
:该方法用于根据关键词获取搜索结果。我们服务的代码如下:
searchApp.service('LocalStorageAPI', [function() {
this.isLocalStorageAvailable = function() {
return (typeof(localStorage) !== "undefined");
};
this.saveSearchResult = function(keyword, searchResult) {
return localStorage.setItem(keyword,
JSON.stringify(searchResult));
};
this.isResultPresent = function(keyword) {
return JSON.parse(localStorage.getItem(keyword));
};
}]);
提示:
本地存储无法保存对象,因此我们需要在存入本地存储前将对象序列化,在取出时将对象反序列化。
现在,我们让之前自定义的mySearch指令通过该服务来处理搜索请求。改造后的mySearch
指令如下:
searchApp.directive('mySearch', ['ResultsFactory', 'LocalStorageAPI',
function(ResultsFactory, LocalStorageAPI) {
return {
templateUrl: './directive.html',
restrict: 'E',
link: function postLink(scope, iElement, iAttrs) {
var lsAvailable =
LocalStorageAPI.isLocalStorageAvailable();
scope.search = function() {
if (lsAvailable) {
var results = LocalStorageAPI.
isResultPresent(scope.query);
if (results) {
scope.results = results;
return;
}
}
var q = {
query: scope.query
};
ResultsFactory.getResults(q).
then(function(response) {
scope.results = response.data.results;
if (lsAvailable) {
LocalStorageAPI.saveSearchResult(scope.query,
data.data.results);
}
});
}
}
};
}]);
这里我们检查了浏览器是否支持本地存储,然后使用LocalStorageAPI
服务来保存和获取结果。
与指令相类似,Ionic也提供了一些常用的服务(Service),在第5章中我们将会看到。
这里举个Ionic加载服务(loading servcie)的例子。该服务会显示一个可以定制文字的加载条。就像下面这样使用:
$ionicLoading.show({
template:'Loading...'
});
然后,你会看到一个覆盖层,在这个覆盖层上会展示目前正在进行的操作,并禁止用户操作。
下面我将列举出一些优秀AngularJS资源的Github地址,通过这些Github库,你可以了解AngularJS的强大以及最新动态。地址如下:
jmcunningham/AngularJS-Learning
地址为https://github.com/jmcunningham/AngularJS-Learninggianarb/awesome-angularjs
地址为https://github.com/gianarb/awesome-angularjsaruzmeister/awesome-angular
地址为https://github.com/aruzmeister/awesome-angular
提示:
注意,本书使用的Ionic版本为1.0.0,对应使用的AngularJS版本为1.3.13。
本章中,我们学习了什么是关注分离(SOC),同时了解了AngularJS是如何做到SOC的。随后,我们快速学习了Ionic中会用到的一些核心AngularJS组件。我们也学习了创建自定义指令和自定义服务的方法与它们的用途,以及在处理HTML元素(DOM)时如何使用指令,将可复用的代码块创建成AngularJS服务或factory组件。这些都是我们在本章中学习到的经验。
我们还学习了如何使用Ionic中的AngularJS来构建移动混合应用。
在下一章中,我们将会继续介绍Ionic。我们会学习如何安装、搭建一个基础应用,并学会理解项目结构。此外,我们还会更深入地学习如何开发移动混合应用。