Ionic-秘籍-全-
Ionic 秘籍(全)
原文:
zh.annas-archive.org/md5/caa442d8b0085ac26329df52c9ca242f译者:飞龙
前言
移动应用程序开发已经是一个热门话题有一段时间了。有多个平台和不同屏幕尺寸和形态的设备,以适应各种需求。这使得移动应用程序开发变得非常困难。幸运的是,Ionic 就是这样的工具之一,它通过允许我们为所有平台和设备编写一次代码来帮助我们减轻这个问题。
在这本书中,读者将学习如何使用 Ionic 创建移动应用程序。我们将从非常基础的事情开始,例如设置开发环境、在应用程序中使用导航、通过 REST API 与后端工作、动画、用户认证、接收推送通知、本地化应用程序、生成文档以及发布应用程序,仅举几例。读者还将了解有关 Angular 和 Ionic CLI 的内容。我希望这本书能够帮助新手开发者以及高级开发者,因为内容是易于理解和高级 Ionic 内容的混合体。
本书面向的对象
《Ionic 烹饪书》面向希望创建跨平台移动应用程序的前端开发者。这本书将帮助您通过深入讲解 Angular、Cordova 和 Sass,成为一个足够舒适地承担困难应用程序的移动应用程序开发者。本书的目的是通过解决现实世界的问题(如认证、推送通知、使用摄像头等)来教授 Ionic。尽管如此,如果您是前端开发的新手,您仍然能够跟随本书。
要充分利用本书
-
在这本书中,我假设您对 Angular 有一些了解。大多数时候,您会遇到的问题将是关于 Angular 而不是 Ionic。
angular.io在这种情况下将是您的最佳朋友。 -
如果您想复习 Angular 的知识,我建议您阅读 Victor Savkin 和 Jeff Cross(前 Angular 团队成员)的这本书
www.packtpub.com/application-development/essential-angular。 -
即使您可以在不安装 Android 或 iOS 的平台 SDK 的情况下运行大多数示例,我还是建议您从一开始就做这件事,以便在实际设备上测试应用程序。请查看以下指南:
下载示例代码文件
您可以从 www.packtpub.com 的账户下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在www.packtpub.com登录或注册。
-
选择支持选项卡。
-
点击代码下载与勘误。
-
在搜索框中输入书名,并遵循屏幕上的说明。
下载文件后,请确保使用最新版本解压缩或提取文件夹。
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Ionic-Cookbook-Third-Edition。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包。请访问github.com/PacktPublishing/。查看它们!
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”
代码块设置如下:
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="One"
tabIcon="water"></ion-tab> <ion-tab [root]="tab2Root"
tabTitle="Two"
tabIcon="leaf"></ion-tab> <ion-tab [root]="tab3Root"
tabTitle="Three"
tabIcon="flame"></ion-tab>
</ion-tabs>
任何命令行输入或输出都按以下方式编写:
$ ionic start LeftRightMenu sidemenu
$ cd LeftRightMenu
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“从管理面板中选择系统信息。”
警告或重要注意事项看起来像这样。
小贴士和技巧看起来像这样。
部分
在本书中,您会发现一些频繁出现的标题(准备就绪,如何操作...,它是如何工作的...,还有更多...,以及另请参阅)。
为了清楚地说明如何完成食谱,请按照以下方式使用这些部分:
准备就绪
本节告诉您在食谱中可以期待什么,并描述如何设置任何软件或任何为食谱所需的初步设置。
如何操作...
本节包含遵循食谱所需的步骤。
如何操作...
本节通常包含对前一个部分发生情况的详细解释。
还有更多...
本节包含有关食谱的附加信息,以便您对食谱有更深入的了解。
另请参阅
本节提供了指向其他对食谱有用的信息的链接。
联系我们
我们欢迎读者的反馈。
一般反馈:请发送电子邮件至 feedback@packtpub.com 并在邮件主题中提及书名。如果您对本书的任何方面有疑问,请发送电子邮件至 questions@packtpub.com。
勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果你在这本书中发现了错误,我们将不胜感激,如果你能向我们报告这一点。请访问www.packtpub.com/submit-errata,选择你的书籍,点击勘误提交表单链接,并输入详细信息。
盗版: 如果你在互联网上遇到任何形式的我们作品的非法副本,如果你能提供其位置地址或网站名称,我们将不胜感激。请通过copyright@packtpub.com与我们联系,并附上材料的链接。
如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
评论
请留下评论。一旦你阅读并使用了这本书,为何不在你购买它的网站上留下评论呢?潜在读者可以查看并使用你的客观意见来做出购买决定,Packt 公司可以了解你对我们的产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问packtpub.com.
第一章:使用 Ionic 创建我们的第一个应用
在本章中,我们将涵盖以下主题:
-
设置开发环境
-
通过 CLI 创建 HelloWorld 应用
-
通过 Ionic Creator 创建 HelloWorld 应用
-
使用你的网络浏览器查看应用
-
使用 Ionic CLI 查看应用
-
使用 Xcode 查看 iOS 应用
-
使用 Genymotion 查看 Android 应用
-
使用 Ionic View 查看应用
简介
现在开发移动应用有很多选择。原生应用需要对每个平台(如 iOS、Android 和 Windows phone)进行独特的实现。在某些情况下,如需要高性能 CPU 和 GPU 处理以及大量内存消耗时,这是必需的。任何不需要过度图形和密集 CPU 处理的程序,都可以从成本效益高、一次编写到处运行的 HTML5 移动实现中受益。
对于选择 HTML5 路线的人来说,市场上有很多不错的选择。一些选项可能很容易开始,但它们可能很难扩展或可能遇到性能问题。商业选项通常对小开发者来说成本很高,难以找到产品/市场契合点。最佳实践是首先考虑用户。有些情况下,简单的响应式网站设计是最好的选择;例如,当业务主要包含固定内容且更新需求最小或内容更适合在网络上进行 SEO 优化时。
Ionic 框架相对于其竞争对手有多个优势,如下所示:
-
Ionic 基于 Angular,这是一个强大的应用开发框架。你可以在其中找到所有构建和结构化应用的组件。
-
UI 性能强大,因为它使用了
requestAnimationFrame()技术。 -
它提供了一套美丽且全面的默认样式,类似于以移动为中心的 twitter Bootstrap。
-
Sass可用于快速、简单和有效的主题定制。
在 AngularJS 1.x 的发布和 Angular 之间发生了许多重大变化。所有这些变化也适用于 Ionic。以下是一些示例:
-
Angular 使用TypeScript,它是ECMAScript 6(ES6)标准的超集,将你的代码编译成 JavaScript。这允许开发者在编译步骤中利用 TypeScript 的特性,如类型检查。
-
在 AngularJS 中不再有控制器和指令。以前,控制器被分配给一个 DOM 节点,而指令将模板转换成类似组件的架构。然而,由于控制器和/或冲突指令的使用不当,大型 AngularJS 1.x 应用很难进行扩展和调试。在 Angular 中,只有一个概念——组件,它最终有一个与 HTML 模板对应的选择器和一个包含函数的类。
-
在 Angular 中,
$scope对象不再存在,因为所有属性现在都在组件内部定义。这实际上是个好消息,因为在 AngularJS 1.x 中调试$scope中的错误(尤其是嵌套场景)非常困难。 -
最后,Angular 提供了更好的性能,并支持 ES5 和 ES6 标准。您可以用 TypeScript、Dart 或纯 JavaScript 编写 Angular。
在本章中,您将通过几个 HelloWorld 示例来引导您的 Ionic 应用程序。这个过程将为您提供基本框架,您可以用它来开始构建更全面的应用程序。大多数应用程序都有类似的用户体验流程,例如标签和侧边菜单。
设置开发环境
在创建您的第一个应用程序之前,您的环境必须准备好所需的组件。这些组件确保开发、构建和测试过程顺利。默认的 Ionic 项目文件夹基于 Cordova。因此,您需要 Ionic CLI 来自动添加正确的平台(即 iOS、Android 或 Windows 手机)并构建项目。这将确保所有 Cordova 插件都正确包含。该工具有许多选项,可以在浏览器或模拟器中运行您的应用程序,并实现实时刷新。
准备工作
您需要安装 Ionic 及其依赖项以开始。Ionic 本身只是一个 CSS 样式、Angular 组件和标准 Cordova 插件的集合。它也是一个命令行工具,用于帮助管理所有技术,如 Cordova。安装过程将为您提供命令行以生成初始代码并构建应用程序。
Ionic 使用npm作为安装程序,它包含在安装 Node.js 时。请从nodejs.org/en/download/安装最新版本的 Node.js。
您需要安装cordova、ios-sim(一个 iOS 模拟器)和ionic:
$ npm install -g cordova ionic ios-sim
您可以使用单个命令行安装所有三个组件,而不是分别发出三个命令行。-g参数用于全局安装包(而不仅仅是当前目录)。
对于 Linux 和 Mac,您可能需要使用sudo命令来允许系统访问,如下所示:
$ sudo npm install -g cordova ionic ios-sim
以下是一些常见的集成开发环境(IDE)选项:
-
Xcode for iOS
-
Android Studio for Android
-
微软 Visual Studio Code (VS Code)
-
JetBrains 的 WebStorm
-
Sublime Text (
www.sublimetext.com/)用于 Web 开发
所有这些都有免费许可。你可以在 Xcode 或 Android Studio 中直接编码,但它们对于网络应用来说有些重量级,尤其是当你有很多窗口打开,只需要简单编码时。Sublime Text 对非商业开发者免费,但如果你是商业开发者,你必须购买许可证。大多数前端开发者更喜欢使用 Sublime Text 进行 HTML 和 JavaScript 编码,因为它非常轻量级,并且拥有一个支持良好的开发者社区。Sublime Text 已经存在很长时间,并且非常用户友好。然而,Ionic 中有许多功能使 Visual Studio Code 非常吸引人。例如,它具有完整 IDE 的外观和感觉,但又不笨重。你可以在 VS Code 中直接调试 JavaScript,以及获得自动完成(例如,IntelliSense)。以下说明涵盖了 Sublime Text 和 VS Code,尽管本书的其余部分将使用 VS Code。
如何操作...
VS Code 在 Mac、Windows 和 Linux 上运行。以下是说明:
-
下载并安装适用于你的特定操作系统
-
解压下载的文件
-
将
.app文件拖入Applications文件夹,并将其拖到 Mac 的 Dock 上 -
打开 Microsoft Visual Studio Code
-
按 Ctrl + Shift + p 打开命令面板
-
在命令面板中输入 shell 命令
-
点击 shell 命令:在 PATH 命令中安装代码命令以将脚本添加到你的终端 $PATH
-
重新启动 Visual Studio Code 以使更改生效
之后,你只需直接从 Ionic 项目文件夹和 VS Code 中编写代码(包括点号),VS Code 将自动将该文件夹作为项目打开。
注意以下截图是从 Mac 上拍摄的:

-
如果你决定使用 Sublime Text,你将需要 Package Control (
packagecontrol.io/installation),它类似于插件管理器。由于 Ionic 使用 Sass,安装 Sass 语法高亮包是可选的。 -
导航到 Sublime Text | 首选项 | Package Control。
-
前往 Package Control:安装包。你也可以直接输入部分命令(即
inst),它将自动选择正确的选项:

- 输入
Sass,搜索结果将显示一个 TextMate & Sublime Text 的选项。选择该选项进行安装:

还有更多...
有许多 Sublime Text 包你可能想使用,例如 HTML、JSHint、JSLint、Tag 和 ColorPicker。你可以访问 sublime.wbond.net/browse/popular 以满足额外需求。
通过 CLI 创建 HelloWorld 应用程序
最快开始你的应用的方式是使用现有的模板。Ionic 通过命令行为你提供了一些标准模板:
-
空白:这是一个带有最少 JavaScript 代码的简单页面
-
标签页:这些是带有路由的多页。一个路由 URL 会跳转到标签页
-
侧边菜单:这是一个带有左右菜单和中心内容区域的模板
-
超级:这是一个带有预构建页面和提供者的模板,强调 Ionic 应用程序开发的最佳实践
如何做到这一点...
- 要使用
blank模板从ionic设置应用程序,请使用以下命令:
$ ionic start HelloWorld_Blank blank
- 如果将
blank替换为tabs,它将创建一个tabs模板,如下所示:
$ ionic start HelloWorld_Tabs tabs
- 同样,以下命令将创建一个带有
sidemenu的应用程序:
$ ionic start HelloWorld_Sidemenu sidemenu
- 同样,以下命令将创建一个带有
super模板的应用程序:
$ ionic start HelloWorld_Super super
在 GitHub 页面上提供了关于 Ionic CLI 的额外指导:github.com/ionic-team/ionic-cli。
它是如何工作的...
本章将向您展示如何快速启动您的代码库并可视化结果。关于 Angular 及其模板语法的更多细节将在本书的各个章节中讨论,然而,核心概念如下:
-
组件:Angular 非常模块化,因为你可以将代码写入一个文件,并使用导出类将其转换为组件。如果你熟悉 AngularJS 1.x,这类似于控制器以及它与 DOM 节点的绑定。组件将有自己的私有和公共属性和方法(即函数)。要判断一个类是否是 Angular 组件,你必须使用
@Component装饰器。这是 TypeScript 中的另一个新概念,因为你可以对任何类施加特性(元数据),以便它们以某种方式行为。 -
模板:模板是一个 HTML 字符串或一个单独的
.html文件,它告诉 AngularJS 如何渲染一个组件。这个概念与其他任何前端和后端框架非常相似。然而,Angular 有自己的语法,允许在 DOM 上执行简单的逻辑,例如重复渲染(*ngFor)、事件绑定(click)或自定义标签(<my-tag>)。 -
指令:这允许你操作 DOM,因为指令绑定到了一个 DOM 对象上。所以,
*ngFor和*ngIf就是指令的例子,因为它们会改变该 DOM 的行为。 -
服务:这指的是除了
get/set所需的抽象来管理模型或复杂逻辑的集合。与组件不同,没有服务装饰器。因此,任何类都可以是一个服务。 -
管道:这主要用于在模板中处理表达式并返回一些数据(即四舍五入数字和添加货币)使用
{{ expression | filter }}格式。例如,{{amount | currency}}如果amount变量是 100,将返回$100。
Ionic 自动创建一个如下所示的项目文件夹结构:

您大部分时间将花费在 /src 文件夹中,因为那里将放置您的应用程序组件。这与 Ionic 1.x 非常不同,因为这里的 /www 文件夹实际上是由 TypeScript 编译的。如果您为 iOS 构建应用程序,Ionic 构建命令行也会在 /platforms/ios/www 创建另一个副本,这是专门为 Cordova 指定的。Angular 中的另一个有趣的变化是,您的应用程序有一个根组件,位于 /src/app 文件夹中,所有其他页面或屏幕都在 /src/pages 中。由于 Angular 是基于组件的,每个组件都会包含 HTML、CSS 和 JS。如果您添加更多的 JavaScript 模块,可以将它们放在 /src/assets 文件夹中,或者更好的做法是使用 npm install,这样它就会自动添加到 /node_modules 文件夹中。Ionic 完全弃用了 Grunt 和 Bower。所有内容都简化为 package.json,其中将列出您的第三方依赖项。
没有必要手动修改 /platforms 或 /plugins 文件夹,除非需要进行故障排除。否则,Ionic 或 Cordova CLI 将会自动化这些文件夹的内容。
默认情况下,从 Ionic 模板来看,Angular 应用程序名称为 MyApp。您将在 app/app.component.ts 中看到类似的内容,这是整个应用程序的根组件文件:

您的应用程序的根组件以及所有内容都将注入到 index.html 中的 <ion-app></ion-app> 内。
注意,如果您双击 index.html 文件在浏览器中打开它,会显示一个空白页面。这并不意味着应用程序没有工作。原因是 Ionic 的 Angular 组件会动态加载所有的 .js 文件,这种行为需要通过 http:// 协议进行服务器访问。如果您在本地打开文件,浏览器会自动将其视为文件协议 (file://),因此 Angular 将没有能力加载额外的 .js 模块以正确运行应用程序。有几种运行应用程序的方法,稍后将会讨论。
通过 Ionic Creator 创建 HelloWorld 应用程序
另一种开始您的应用程序代码库的方法是使用 Ionic Creator。这是一个出色的界面构建器,可以通过拖放风格加速您的应用程序开发。您可以通过基于网络的界面快速获取现有的组件,并将它们定位以可视化应用程序中的外观。大多数常见组件,如按钮、图像和复选框都是可用的。
Ionic Creator 允许用户将所有 .html、.css 和 .js 文件导出为一个项目。您应该能够在 /app 文件夹中编辑内容,以在界面之上构建。
准备工作
要开始使用 Ionic Creator,您需要在 creator.ionic.io/ 注册一个免费账户。
如何操作...
-
创建一个名为
myApp的新项目:![]()
-
验证,以确保您看到以下屏幕:
![img/03cb16d0-347e-46f5-8cfb-04c0abf3cec0.png]
-
中心区域是您的应用界面。左侧为您提供页面列表。每个页面都是一个单独的路由。您还可以访问许多 UI 组件,通常您需要手动在 HTML 文件中编码这些组件。右侧的面板显示了任何选中组件的属性。
-
您可以自由地在这里进行任何需要做的事情,只需将组件拖放到中心屏幕即可。如果您需要创建一个新页面,您必须点击页面面板中的“添加页面”。每个页面都表示为一个链接,这基本上是 Angular UI-Router 定义中的路由。要导航到另一个页面(例如,在点击按钮后),您只需更改链接属性并指向该页面。
-
顶部有一个编辑按钮,您可以在编辑模式和预览模式之间切换。看到您的应用将如何看起来和表现是非常有用的。
-
完成后,点击导航栏顶部的导出按钮。您有以下四种选项:
-
使用 Ionic CLI 工具获取代码
-
将项目作为 ZIP 文件下载
-
将其导出到原生代码(类似于 PhoneGap Build),如图所示:![img/0cbcd00b-b04d-4d32-afb2-f0fb5d5d2462.png]
-
使用 Creator 应用将其导出到预览模式
-
学习 Ionic Creator 的最佳方式是玩它。
还有更多...
要切换到预览模式,在那里您可以在设备模拟器中看到 UI,请点击右上角的切换按钮以启用测试,如图所示:
![img/54cfb320-3ae1-47d6-928e-5999081f1dc3.png]
在此模式下,您应该能够像在设备上实际部署一样,在 Web 浏览器中与组件进行交互。
如果您弄坏了什么,启动一个新的项目非常简单。这是一个用于原型设计和获取初始模板或项目脚手架的出色工具。您应该继续在常规 IDE 中编码其余的应用程序。Ionic Creator 目前还不能为您做所有事情。例如,如果您想访问特定的 Cordova 插件功能,您必须单独编写那段代码。
此外,如果您想在 Ionic Creator 允许的范围之外调整界面,还需要对.html和.css文件进行特定的修改。
使用您的 Web 浏览器查看应用
为了运行 Web 应用,您需要将您的/www文件夹转换成一个 Web 服务器。再次强调,有众多方法可以实现这一目标,人们往往倾向于坚持一两种方法以保持事情简单。一些其他选项可能不可靠,例如 Sublime Text 的实时监控包或静态页面生成器(例如,Jekyll 和 Middleman 应用)。它们检测变化的速度较慢,可能会冻结您的 IDE,因此在此不予以提及。
准备工作
推荐的方法是使用 Ionic serve 命令行。它基本上启动一个 HTTP 服务器,这样您就可以在桌面浏览器中打开您的应用。
如何操作...
- 首先,你需要进入项目文件夹。让我们假设它是侧边菜单
HelloWorld:
$ cd HelloWorld_Sidemenu
- 从那里,只需发出简单的命令行,如下所示:
$ ionic serve
就这样!无需进入 /www 文件夹或确定使用哪个端口。当网络服务器运行时,命令行将提供以下选项:

这里最常用的选项是在完成时使用 Ctrl + C 退出。
要以正确的设备分辨率查看应用程序,还有额外的步骤:
-
如果你的电脑上还没有安装,请安装 Google Chrome。
-
从 Ionic serve 中打开链接(例如,
http://localhost:8100/)Google Chrome。
-
打开开发者工具。例如,在 Mac 的 Google Chrome 中,导航到视图 | 开发者 | 开发者工具:
![]()
-
在 Chrome 开发者工具区域点击小型的移动图标,如图所示:
![]()
-
将会有一长串设备可供选择,如图所示:
![]()
-
选择设备后,你需要刷新页面以确保 UI 已更新。Chrome 应该会给出设备的精确视图分辨率。

大多数开发者更愿意使用这种方法进行编码,因为你可以使用 Chrome 开发者工具调试应用程序。它的工作方式与任何其他网络应用程序完全相同。你可以在控制台创建断点或输出变量。
它是如何工作的...
注意,Ionic serve 实际上正在监视 /src 文件夹下的所有内容,并在 /www 下即时将 TypeScript 代码转换为 JavaScript。这很有道理,因为系统不需要扫描每个文件,因为其改变的概率非常小。
当网络服务器运行时,你可以回到 IDE 并继续编码。例如,让我们打开 page1.html 或任何其他模板文件,并将第一行更改为以下内容:
<ion-view view-title="Updated Playlists">
返回到打开新页面的网络浏览器;应用程序界面会立即更改标题栏,无需刷新浏览器。当需要在代码更改和即时检查应用程序的工作或外观之间来回切换时,这是一个非常好的功能。
使用 Ionic CLI 查看应用程序
到目前为止,你一直在测试 Ionic 的网络应用程序部分。大多数时候,你需要实际在物理设备或至少在模拟器上运行应用程序,以查看应用程序的行为以及所有原生功能是否正常工作。
准备工作
你将需要安装模拟器。iOS 模拟器在执行 npm install -g ios-sim 时安装,Android 模拟器与 Android Studio 一起安装。要在物理设备上测试应用程序,你必须通过 USB 连接将设备连接到你的电脑。
如何做到...
- 使用以下命令行添加特定平台(如 iOS)并构建应用程序:
$ ionic cordova platform add ios
$ ionic cordova build ios
-
注意,您需要添加平台来构建应用程序。但是,如果您使用来自 Ionic CLI 的标准模板,它应该已经包含了 iOS 平台。要为 Android 构建和运行,您可以替换 iOS 为 Android。
-
要使用
ios模拟器来模拟应用程序,请使用以下命令行:
$ ionic cordova emulate ios
- 要在真实的物理 iPhone 设备上运行应用程序,请使用以下命令行:
$ ionic cordova run ios --device
使用 Xcode 查看 iOS 应用程序
您也可以使用 Xcode(在 Mac 上)运行应用程序。
如何做到...
-
前往
/platforms/ios文件夹。 -
查找具有
.xcodeproj的文件夹并在 Xcode 中打开它。 -
点击 iOS 设备图标并选择您选择的 iOS 模拟器!
![图片]()
-
点击运行按钮,您应该能够看到应用程序在运行
模拟器。
更多内容...
您可以通过 USB 端口连接一个物理设备,它将显示在 iOS 设备列表中供您选择。然后,您可以直接在您的设备上部署应用程序。请注意,此操作需要 iOS 开发者会员资格。这种方法比仅通过网页浏览器查看应用程序更复杂。
然而,当您想要测试有关设备功能(如相机或地图)的代码时,这是必须的。如果您更改了 /src 文件夹中的代码并想要在 Xcode 中再次运行它,您必须首先执行 Ionic Cordova build ios,因为正在运行的代码位于 Xcode 项目的 Staging 文件夹中,如图所示:
!
对于调试,Xcode 控制台可以输出 JavaScript 日志。然而,您可以使用 Safari 的更高级的Web 检查器(类似于 Google Chrome 的开发者工具)来调试您的应用程序。请注意,只有 Safari 可以调试在连接的物理 iOS 设备上运行的 Web 应用程序,因为 Chrome 在 Mac 上不支持此功能。启用此功能很容易,可以通过以下步骤完成:
-
通过导航到“设置”|“Safari”|“高级”并启用“Web 检查器”来允许 iOS 设备进行远程调试!
![图片]()
-
通过 USB 将物理 iOS 设备连接到您的 Mac 并运行应用程序
-
打开 Safari 浏览器
-
选择“开发”|“您的设备名称”或iOS 模拟器|index.html,如图所示:
!
如果您在 Safari 中看不到“开发”菜单,您需要导航到首选项|高级并勾选“在菜单栏中显示开发菜单”。
Safari 将为该特定设备打开一个新的控制台,就像它在计算机的 Safari 中运行一样。
使用 Genymotion 查看 Android 应用程序
虽然可以在 Mac 上安装 Google Android 模拟器,但许多开发者在使用过程中遇到了不一致的体验。有许多商业和免费的替代方案提供更多便利和广泛的设备支持。Genymotion 提供一些独特的优势,例如允许用户切换 Android 模型和版本,支持在应用内进行网络连接,以及允许模拟 SD 卡。
在本节中,您将首先学习如何设置 Android 开发者环境(在本例中为 Mac)。然后,您将安装和配置 Genymotion 以进行移动应用开发。
如何做到这一点...
-
第一步是正确设置 Android 开发环境。从
developer.android.com/studio/index.html下载并安装 Android Studio。 -
如果您的机器没有正确的依赖项,您可能需要安装其他库。如果是这种情况,您应该从命令行运行
sudo apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0 lib32stdc++6来安装它们。 -
运行 Android Studio。
-
您需要安装所有必需的包,例如 Android SDK。只需在设置向导屏幕上点击两次“下一步”,然后点击“完成”按钮以开始安装包。
-
安装完成后,您需要安装额外的包和其他 SDK 版本。在快速入门屏幕上,选择配置:
![快速入门配置]()
-
然后,选择 SDKManager,如图所示:
![SDKManager]()
-
安装上一个版本,如 Android5.0.1 和 5.1.1,是一个好习惯。您可能还希望安装所有工具和附加组件以供以后使用:
![安装工具和附加组件]()
-
点击安装包...按钮。
-
在接受许可的框中勾选,并选择安装。
-
SDK 管理器将在顶部显示 SDK 路径。请复制此路径,因为您需要修改环境路径。
-
进入终端并输入以下命令:
$ touch ~/.bash_profile; open ~/.bash_profile
-
这将打开一个文本编辑器以编辑您的 bash 配置文件。插入以下内容:
该行,其中
/YOUR_PATH_TO/android-sdk应该是 SDK 路径。之前复制的:
export ANDROID_HOME=/YOUR_PATH_TO/android-sdk
export PATH=$ANDROID_HOME/platform-tools:$PATH
export PATH=$ANDROID_HOME/tools:$PATH
-
保存并关闭该文本编辑器。
-
返回终端并输入:
$ source ~/.bash_profile
$ echo $ANDROID_HOME
-
您应该看到输出的是您的 SDK 路径。这验证了您已正确配置 Android 开发者环境。
-
下一步是安装和配置 Genymotion。从
sudo apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0 lib32stdc++6下载并安装 Genymotion 和 Genymotion Shell。 -
运行 Genymotion。
-
点击添加按钮以开始添加新的 Android 设备,如图所示:
![添加 Android 设备]()
-
选择您想要模拟的设备。在本例中,让我们选择三星 Galaxy S5,如下所示:
![选择三星 Galaxy S5]()
-
您将看到设备被添加到您的虚拟设备中。点击该设备!
![]()
-
然后点击开始!
![]()
-
模拟器将花费几秒钟时间启动,并显示另一个窗口。这只是一个空白模拟器,其中还没有运行您的应用程序。
-
运行 Genymotion Shell。
-
从 Genymotion Shell,您需要获取设备列表并保留连接的设备的 IP 地址,即三星 Galaxy S5。输入
devices list。 -
输入
adb connect 192.168.56.101(或从设备列表命令行中看到的任何 IP 地址)。 -
输入
adb devices以确认已连接。 -
输入
Ionic Cordova platform以添加 Android,将 Android 添加为您的应用程序的平台。 -
最后,输入 Ionic Cordova run android。
-
您应该能够看到显示您的应用程序的 Genymotion 窗口。
虽然要完成这项工作需要许多步骤,但您不必重复同样的过程的可能性要小得多。一旦您的环境设置完成,您只需要保持 Genymotion 运行,同时编写代码。如果需要在不同 Android 设备上测试应用程序,很容易在 Genymotion 中添加另一个虚拟设备并连接到它。
使用 Ionic View 查看应用程序
Ionic View是一个可以从 App Store 或 Google Play 下载的应用程序查看器。在开发过程中,当应用程序尚未完成时,您不想立即将其提交给 Apple 或 Google,而是限制测试人员的访问。Ionic View 可以帮助在 Ionic View 中加载您的应用程序,并使其像真实应用程序一样运行,同时可以访问一些原生设备功能。此外,Ionic View 还允许您在 iOS 设备上使用应用程序,而无需任何认证要求。
由于 Ionic View 使用 Cordova 的InAppBrowser插件来启动您的应用程序,因此必须对设备功能进行破解才能使其工作。目前,Ionic View 仅支持 SQLite、电池、摄像头、设备运动、设备方向、对话框/通知、地理位置、全球化、网络信息、振动、键盘、状态栏、条形码扫描器和 zip。在使用 Ionic View 之前检查更新的支持列表是一个好主意,以确保您的应用程序能够正常工作。
如何操作...
使用 Ionic View 有两种方式。您可以选择上传自己的应用程序或加载他人的应用程序 ID。如果您测试自己的应用程序,请按照以下步骤操作:
-
从 App Store 或 Google Play 下载 Ionic View。
-
确保在ionic.io上注册一个账户。
-
前往您的应用程序项目文件夹。
-
搜索 Ionic 上传。
-
输入您的凭据。
-
CLI 将上传整个应用程序并为您提供应用程序 ID,在本例中为 152909f7。您可能希望保留此应用程序 ID,以便稍后与其他测试人员分享!
![]()
-
在移动设备上打开 Ionic View 应用程序,如果您尚未登录,请登录。
-
现在,您应该能够在“我的应用”页面上看到应用名称。按照图示选择应用名称(在本例中为 myApp),如下所示:
![选择应用名称]()
-
选择“查看应用”来运行应用,如图所示:
![查看应用]()
-
您会看到应用界面出现初始说明,说明如何退出应用。由于您的应用将覆盖 Ionic View 的全屏,您需要像图中所示,用三根手指向下滑动来退出回到 Ionic View:

如果没有代码更新,过程相同,只是您需要从菜单中选择“同步到最新”。
还有更多...
总结来说,使用 Ionic View 有几个好处,其中一些如下:
-
这很方便,因为只有一个命令行就可以推送应用。
-
任何人都可以通过输入应用 ID 来访问您的应用。
-
使用 Ionic 开始开发不需要 iOS 开发者会员资格。苹果有自己的 TestFlight 应用,非常相似。
-
您可以在开发应用的同时让测试人员测试应用,从而保持敏捷的开发流程。
-
Ionic View 支持广泛的设备功能,并且这种支持还在不断增长。
第二章:添加 Ionic 组件
在本章中,我们将介绍以下与使用 Ionic 组件相关联的任务:
-
使用标签添加多个页面
-
添加左右菜单导航
-
使用状态参数导航多个页面
-
在一个应用中同时使用菜单、标签和段
-
使用 Ionic 网格创建复杂的 UI
简介
可以用少量页面编写一个简单的应用。Ionic 提供许多开箱即用的组件,允许简单的即插即用操作。当应用增长时,管理不同视图及其特定时间或触发事件的自定义数据可能会非常复杂。Ionic 对状态和导航的处理进行了一些更改。在 Ionic 1 中,你可以使用 UI-Router 进行高级路由管理机制。在 Ionic 中,NavController实现了导航的 push/pop 实现。
由于 Ionic 引入了许多新的组件,你必须理解这些组件如何影响你的应用状态层次结构,以及何时触发每个状态。
使用标签添加多个页面
本节将解释如何使用 Ionic 标签界面,并将其扩展到其他用例。所使用的示例非常基础,包含三个标签页,每个标签页中都有一些示例 Ionic 组件。这是你在许多应用中都会遇到的一种非常常见的结构。你将学习 Ionic 如何构建标签界面,以及它是如何转换为单个文件夹和文件的。
在这个示例中,你将构建三个标签页,如下所示:
-
一个显示简单纯文本页面的标签页,以解释组件的放置位置
-
一个显示注册表单的标签页
-
一个显示水平滑块框的标签页
虽然这个应用非常直接,但它将教会你许多 Angular 和 Ionic 的关键概念。其中一些是组件装饰器、主题和 TypeScript 编译器过程。
这是选中中间标签的示例应用的截图:

准备工作
由于这是你从头开始构建的第一个应用,你需要确保你已经按照第一章,“使用 Ionic 创建我们的第一个应用”,来设置环境和 Ionic CLI。如果你已经有了 Ionic 1,它必须更新。为此,你可以使用安装它时使用的相同命令行,如下所示:
$ sudo npm install -g cordova ionic ios-sim
如何做到...
以下是为创建示例应用提供说明:
- 使用
tabs模板创建一个新的PagesAndTabs应用,并进入PagesAndTabs文件夹以启动 Visual Studio Code,如下所示:
$ ionic start PagesAndTabs tabs
$ cd PagesAndTabs
$ code .
blank模板只提供了一个基本页面。在 Mac 上打开Finder应用或在 Windows 上打开 Windows 资源管理器,可以看到以下文件夹结构:

你只需修改 /src 文件夹中的内容,而不是 /www,正如在 Ionic 1 中那样。/src 文件夹中的所有内容都将被构建,而 /www 文件夹将自动创建。我们还将尽可能保留文件夹名称和文件名,因为这里的主要目标是理解标签模板的工作方式和你可以修改的区域。
-
使用以下命令打开并编辑
/src/pages/tabs/tabs.html模板文件:以下代码:
<ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="One"
tabIcon="water"></ion-tab> <ion-tab [root]="tab2Root"
tabTitle="Two"
tabIcon="leaf"></ion-tab> <ion-tab [root]="tab3Root"
tabTitle="Three"
tabIcon="flame"></ion-tab>
</ion-tabs>
新的模板只更新标题和图标。这是因为这个示例想要保留 root 变量的命名。你可以根据需要使用 <ion-tab> 添加更多标签页。
- 要添加一个页面,你需要确保
tab1Root指向一个现有的文件夹和模板。由于你将重用现有的标签结构,你只需修改/src/pages/home/home.html模板,如下所示,因为这将是你的第一个页面:
<ion-header>
<ion-navbar>
<ion-title>One</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2>Welcome to Ionic!</h2>
<p>
This starter project comes with simple tabs-based layout for
apps
that are going to primarily use a Tabbed UI.
</p>
<p>
Take a look at the <code>src/pages/</code> directory to add or
change tabs, update any existing page or create new pages.
</p>
</ion-content>
- 此外,在同一个
/home文件夹中,编辑home.ts,它与相同的模板相对应,并在其中输入代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController) {
}
}
- 对于第二页,
tab2Root,你将遵循类似的过程,通过编辑/src/pages/about/about.html模板,如下所示:
<ion-header>
<ion-navbar>
<ion-title>
Two
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-input type="text" placeholder="First Name"></ion-input>
</ion-item>
<ion-item>
<ion-input type="text" placeholder="Last Name"></ion-input>
</ion-item>
<div padding>
<button ion-button primary block>Create Account</button>
</div>
</ion-list>
</ion-content>
- 在前一步骤相同的文件夹中编辑
about.ts:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
constructor(public navCtrl: NavController) {
}
}
- 最后,对于
tab3Root页面,你可以更改模板,使其在/src/pages/contact/contact.html中显示滑块,如下所示:
<ion-header>
<ion-navbar>
<ion-title>
Three
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-slides #mySlider index=0 (ionSlideDidChange)="onSlideChanged($event)">
<ion-slide style="background-color: green">
<h2>Slide 1</h2>
</ion-slide>
<ion-slide style="background-color: blue">
<h2>Slide 2</h2>
</ion-slide>
<ion-slide style="background-color: red">
<h2>Slide 3</h2>
</ion-slide>
</ion-slides>
</ion-content>
- 在
/contact文件夹中,你需要使用以下代码编辑contact.ts:
import { Component, ViewChild } from '@angular/core';
import { NavController, Slides } from 'ionic-angular';
@Component({
selector: 'page-contact',
templateUrl: 'contact.html'
})
export class ContactPage {
@ViewChild('mySlider') slider: Slides;
constructor(public navCtrl: NavController) {
}
onSlideChanged(e) {
let currentIndex = this.slider.getActiveIndex();
console.log("You are on Slide ", (currentIndex + 1));
}
}
- 前往你的终端,并输入以下命令行来运行应用:
$ ionic serve
它是如何工作的...
实际上,这个简单的应用中包含了很多新的信息和许多概念。在更高层次上,这是应用的结构方式:
-
当你运行应用时,Cordova 会加载
/www/index.html文件作为第一个打开的文件。你所有的代码和模板都将合并到一个文件中,即/www/build/main.js。 -
/app文件夹是大多数逻辑所在的地方。它以app.component.ts作为引导文件。 -
/pages文件夹下的每个子文件夹将代表一个页面,这是 Ionic 中的一个新概念。一个页面由一个 HTML 模板、TypeScript 代码和一个.scss文件组成,用于自定义该特定模板。 -
/theme文件夹将包含全局级别的变量和自定义设置,以覆盖来自 Ionic 的默认主题。
现在,让我们从 /app 文件夹中的所有内容开始。
app.component.ts 文件只导入启动应用所需的所有页面和组件。此示例默认需要以下四个导入:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { TabsPage } from '../pages/tabs/tabs';
你必须始终从 Ionic 中导入 Component、Platform 和 StatusBar,因为这将为你提供 @Component 装饰器来引导你的应用。装饰器放置在其类之前,为类提供元数据。以下示例显示 MyApp 类具有组件的特性,具有 template 属性:
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = TabsPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen:
SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
由于这是一个简单的示例,你不需要声明很多,除了模板信息。类似于 Ionic 1,你可以使用 template 或 templateUrl 来指向一个本地文件。在我们的例子中,它是 app.html,内容如下:
<ion-nav [root]="rootPage"></ion-nav>
类是 ES6 中的另一个新概念。然而,开发者已经在各种编程语言中声明了类,例如 Java 和 C#。在 ES6 中,你可以使用类来更有效地重用代码,并实现更好的抽象。类可以仅存在于该文件上下文中。考虑以下示例:
class Example {}
然而,如果你想在其他地方使用该类,你必须导出:
export class Example {}
在一个类中,你可以有以下几种:
-
一个变量,例如
this.a或this.b -
一个方法,例如
doSomething() -
一个在用该类创建对象时自动执行(或初始化)的构造函数
更多关于类的信息可以在以下找到 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes.
ES6 的另一个优点是箭头函数,如下所示:
platform.ready().then(() => {
});
前面的内容等同于:
platform.ready().then(function() {
});
一个示例(通过传递参数)如下所示:
var a1 = a.map( s => s.length );
同样的代码可以重写为如下所示:
var a1 = a.map(function(s){ return s.length });
更多关于箭头函数的信息可以在以下找到:
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions.
在 app.component.ts 中有一个重要的事情,那就是你必须声明一个 root 页面。你可以从模板中通过 [root]="rootPage" 看到它,然后在构造函数中再次通过 this.rootPage = TabsPage。围绕 root 的方括号 [] 表示它是一个 DOM 节点的属性。这是 Angular 的新概念,因为它试图摆脱使用 DOM 属性,如 ngmodel(这往往会导致性能降低)。这里的赋值是告诉 Ionic 2 你将使用之前导入的 TabsPage,并将其作为 root 页面。然后,ion-nav 指令将查看自己的 root 属性以开始渲染页面。与 Ionic 1 相比,这里似乎有更多的抽象和样板代码。然而,这种做法是为了确保更好的分离和扩展。
一旦你理解了 app.component.ts 的工作原理,理解其他页面的概念就会更容易。让我们看看 /pages/tabs/tabs.ts 文件,因为在那里你定义了 TabsPage 类。从这个文件中,你需要导入三个其他页面,如下所示:
import { Component } from '@angular/core';
import { HomePage } from '../home/home';
import { AboutPage } from '../about/about';
import { ContactPage } from '../contact/contact';
该页面的模板位于 tabs.html。然而,你也可以在 .ts 文件中将模板放在一个字符串中,如下所示:
@Component({
template:
` <ion-tabs>
<ion-tab [root]="tab1Root" tabTitle="One"
tabIcon="water"></ion-tab><ion-tab [root]="tab2Root" tabTitle="Two"
tabIcon="leaf"></ion-tab><ion-tab [root]="tab3Root"
tabTitle="Three"
tabIcon="flame"></ion-tab>
</ion-tabs>`
})
ES6 还引入了一个新特性,称为多行模板字符串。您可能已经意识到前面的模板字符串没有join()或字符串连接(+)运算符。原因是您可以使用反引号(`)来允许多行模板。
因此,您不需要这样做:
console.log("string text line 1\n"+
"string text line 2");
您现在可以这样做:
console.log(`string text line 1
string text line 2`);
在页面装饰器下方,您需要导出TabsPage(以便您可以在app.component.ts中使用它),并告诉构造函数使用tab1Root、tab2Root和tab3Root作为标签导航中其他页面的根,如下所示:
export class TabsPage {
tab1Root: any = HomePage;
tab2Root: any = AboutPage;
tab3Root: any = ContactPage;
constructor() {
}
}
Ionic 标签声明与 Ionic 1 非常相似,如下所示:
<ion-tabs>
<ion-tab><ion-tab>
</ion-tabs>
您只需确保root属性指向另一个页面。
tab1Root实际上非常容易理解,因为它是一个文本页面,您可以在<ion-content>元素内添加自己的内容和设计,如下所示:
<ion-content padding>
<h2>Welcome to Ionic 2 Tabs!</h2>
<p>
This starter project comes with simple tabs-based layout for
apps that are going to primarily use a Tabbed UI.
</p>
</ion-content>
如果您想更改标题,您可以简单地更改以下行:
<ion-title>One</ion-title>
tab2Root和tab3Root在结构上非常相似。Ionic 为您提供了便利,可以在AboutPage类中直接绑定到事件,如下所示:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
constructor(public navCtrl: NavController) {
}
ionViewWillEnter() {
console.log('Enter Page 2');
}
}
在前面的about.ts示例中,如果用户输入tab2Root,它将自动调用ionViewWillEnter()函数。这是一个显著的改进,因为在 Ionic 1 中,您必须在$scope变量上使用$ionicView.enter。再次强调,$scope的概念在 Angular 中不再存在。
对于可扩展的应用,最好将模板分离到不同的文件中,并避免在 JavaScript 代码中混合模板。templateUrl必须始终指向.html文件的相对位置。
在./src/pages/contact/contact.html中,您可以使用滑动框并绑定到滑动更改事件,如下所示:
<ion-header>
<ion-navbar>
<ion-title>
Three
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-slides #mySlider index=0 (ionSlideDidChange)="onSlideChanged($event)">
<ion-slide style="background-color: green">
<h2>Slide 1</h2>
</ion-slide>
<ion-slide style="background-color: blue">
<h2>Slide 2</h2>
</ion-slide>
<ion-slide style="background-color: red">
<h2>Slide 3</h2>
</ion-slide>
</ion-slides>
</ion-content>
要在 Angular(或 Ionic)中获取事件,您必须使用括号( ),因为ng-click或类似的概念已不再可用。在这种情况下,如果幻灯片根据ionSlideDidChange更改,ion-slides指令将触发ContactPage类中的onSlideChanged()函数。
您不能在没有 TypeScript 进行代码转译成 JavaScript 的情况下直接运行 TypeScript。当您运行ionic serve时,这个过程会在幕后自动发生。同样,当您在项目中更改一些代码时,Ionic 会检测这些更改并在更新浏览器之前重新构建文件。您无需每次都刷新。
参见
-
Mozilla 开发者网络提供了关于 ECMAScript 6 的非常广泛的文档,您可以通过以下链接找到:
developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla。 -
对于 Angular 2 特定的信息,您可以直接从 Angular 2 文档中阅读,文档链接为
angular.io/docs/ts/latest/index.html。
添加左右菜单导航
菜单导航是许多移动应用中非常常见的组件。您可以使用菜单让用户在应用中切换到不同的页面,包括登录和登出。菜单可以放置在应用的左侧或右侧。Ionic 还允许您检测事件并进一步自定义菜单的外观和感觉。
这是您将要开发的应用的截图:

该应用将包含两个页面和两个菜单。您可以切换左侧或右侧菜单(但不能同时切换)。实际上,同时拥有两个菜单的可能性很小,但为了演示目的,这个应用将包含两个菜单,以便展示您可以设置的菜单的不同属性。左侧菜单将改变页面,而右侧菜单将允许您捕捉到点击的确切项目。
准备工作
此应用可以在您的网页浏览器上运行,因此不需要有物理设备。再次强调,您只需要在您的计算机上安装 Ionic。
如何实现...
下面是创建示例应用的说明:
- 使用
sidemenu模板创建一个新的LeftRightMenu应用,如图所示,并进入LeftRightMenu文件夹:
$ ionic start LeftRightMenu sidemenu
$ cd LeftRightMenu
- 确认您的应用文件夹结构类似于以下结构:

- 编辑
./src/app/app.component.ts并将其替换为以下代码:
import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { HomePage } from '../pages/home/home';
import { ListPage } from '../pages/list/list';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
@ViewChild(Nav) nav: Nav;
text: string = '';
rootPage: any = HomePage;
pages: Array<{title: string, component: any}>;
constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen) {
this.initializeApp();
// used for an example of ngFor and navigation
this.pages = [
{ title: 'Home', component: HomePage },
{ title: 'List', component: ListPage }
];
}
initializeApp() {
this.platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
}
rightMenuClick(text) {
this.text = text;
}
}
- 打开并编辑以下代码的
./src/app/app.html文件:
<ion-menu id="leftMenu" [content]="content" side="left" type="overlay">
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button menuClose ion-item *ngFor="let p of pages"
(click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-menu id="rightMenu" [content]="content" side="right" type="reveal">
<ion-header>
<ion-toolbar>
<ion-title>Items</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item (click)="rightMenuClick('Item One')">
Item One
</button>
<button ion-item (click)="rightMenuClick('Item Two')">
Item Two
</button>
</ion-list>
<ion-card *ngIf="text">
<ion-card-content>
You just clicked {{ text }}
</ion-card-content>
</ion-card>
</ion-content>
</ion-menu>
<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
在这个模板中,有两个菜单作为兄弟元素。它们也与ion-nav处于同一级别,而不是父或子。这种结构对于菜单导航来说很重要。
- 现在,让我们创建两个页面,您只需要修改
sidemenu模板中的标准页面。打开并编辑./src/app/pages/home/home.html模板:
<ion-header>
<ion-navbar>
<ion-title>Getting Started</ion-title>
<ion-buttons left>
<button ion-button menuToggle="leftMenu">
<ion-icon name="menu"></ion-icon>
</button>
</ion-buttons>
<ion-buttons right>
<button ion-button menuToggle="rightMenu">
<ion-icon name="menu"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding class="getting-started">
<h3>Welcome to the Menu Experiment</h3>
<p>
You can open both left and right menu using below buttons or top
navigation bar!
</p>
<ion-row>
<ion-col width-50>
<button ion-button primary block menuToggle="leftMenu">Toggle Left</button>
</ion-col>
<ion-col width-50>
<button ion-button primary block menuToggle="rightMenu">Toggle Right</button>
</ion-col>
</ion-row>
</ion-content>
- 在同一文件夹中,打开并编辑
.css类通过home.scss,如图所示:
page-home {
.getting-started {
p {
margin: 20px 0;
line-height: 22px;
font-size: 16px;
}
}
.bar-button-menutoggle {
display: inline-flex;
}
}
注意,由于您正在使用sidemenu模板,它已经包含了一个第二页(例如,列表)。在这个特定示例中,您不需要修改该页面。
- 打开并编辑第二个页面的模板
./src/pages/list/list.html,如图所示:
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>List</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item *ngFor="let item of items" (click)="itemTapped($event, item)">
<ion-icon [name]="item.icon" item-left></ion-icon>
{{item.title}}
<div class="item-note" item-right>
{{item.note}}
</div>
</button>
</ion-list>
<div *ngIf="selectedItem" padding>
You navigated here from <b>{{selectedItem.title}}</b>
</div>
</ion-content>
- 前往您的终端并运行应用:
$ ionic serve
它是如何工作的...
由于这个应用只是对菜单导航的介绍,它不会管理页面路由和状态参数。在更高层次上,这是应用的流程:
-
app.ts在app.html中加载了两个menu模板。 -
左侧菜单将触发
openPage()函数以打开PageTwo。 -
右侧菜单将触发
rightMenuClick()函数以改变this.text属性并在屏幕上显示。
在app.html模板中,左侧菜单具有以下属性:
side="left" type="overlay"
然而,右侧菜单有以下的分配:
side="right" type="reveal"
side 属性将决定菜单应在屏幕上的哪个位置显示。有两种类型的菜单。overlay 选项将保持中心页面不变,不移动。reveal 选项将推动整个屏幕以显示菜单。你选择哪种类型取决于你应用程序的设计。
每个 ion-menu 指令都必须声明 [content]="content",因为它将使用内容区域来绑定左右滑动。在这种情况下,它基本上是 ion-nav 中的一个局部变量,如下所示:
<ion-nav id="nav" [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
如果你想要为菜单设置标题,ion-menu 内的 ion-toolbar 的使用是可选的。要显示菜单项的关键是使用 ion-list 和 ion-item。你可以通过循环数组来动态显示菜单项,如图所示:
<ion-list>
<button menuClose ion-item *ngFor="let p of pages"
(click)="openPage(p)">
{{p.title}}
</button>
</ion-list>
*ngFor 是在 Ionic 1 中 ng-repeat 的替代品。你需要使用 let p,因为它与声明一个名为 p 的局部变量相同。这是变量隔离的最佳实践。否则,其概念与 Ionic 1 非常相似,你可以为 pages 数组中的每个项目获取 p.title。
在右侧菜单中,你不需要通过 nav.setRoot() 跳转到不同的页面,你只需设置一些文本,并在菜单中动态显示文本,如图所示:
<ion-card *ngIf="text">
<ion-card-content>
You just clicked {{ text }}
</ion-card-content>
</ion-card>
因此,如果 text 变量不存在(这意味着用户尚未点击任何内容),则 ion-card 将不会通过 *ngIf 显示任何内容。
对于每个页面,你必须声明相同的 ion-navbar。否则,你将失去顶部导航和菜单按钮:
<ion-header>
<ion-navbar>
<ion-title>Getting Started</ion-title>
<ion-buttons start>
<button ion-button menuToggle="leftMenu">
<ion-icon name="menu"></ion-icon>
</button>
</ion-buttons>
<ion-buttons end>
<button ion-button menuToggle="rightMenu">
<ion-icon name="menu"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
注意,leftMenu 和 rightMenu 必须与你在 app.html 模板中使用的相同 id。
在第一页,还有两个按钮可以触发内容页内的菜单,如图所示:
<ion-row>
<ion-col width-50>
<button primary block menuToggle="leftMenu">Toggle
Left</button>
</ion-col>
<ion-col width-50>
<button primary block menuToggle="rightMenu">Toggle
Right</button>
</ion-col>
</ion-row>
这两个按钮同样调用 menuToggle 来触发菜单。按钮被放置在 Ionic 网格系统中。由于 Ionic 使用 Flexbox,使用起来非常简单——你只需要创建 ion-col 和 ion-row。带有数字的 width 属性将决定宽度百分比。
参见
-
要进一步了解 Ionic 菜单的使用,你可以查看以下链接:
-
Ionic 菜单的 API 文档也可在以下地址找到:
使用状态参数导航多个页面
应用程序导航是一个重要的话题,因为它是一个用户体验的核心。你希望管理用户在提交表单或打开新标签页后对会发生什么的预期。此外,你可能还想确保用户数据在正确的页面或正确的状态下可用。当涉及到返回导航的要求时,这可能会变得更加复杂。
本节将教你如何使用 NavController 和 NavParams,这两个是管理应用所有导航的重要基类。这是你将开发的应用的截图:

此应用展示了三种不同的导航到不同页面以及如何
传递参数。当你点击任何按钮时,它将显示第二页,该页如下
如下所示:

第二页基本上是捕获参数并在屏幕上显示它们。它还提供了三个不同的选项来导航回上一页。
在本例中,你将学习以下内容:
-
如何使用
NavController和NavParams -
如何在模板中直接使用
[navPush]和[navParams] -
如何在输入框中添加双向数据绑定
-
如何使用管道将 JSON 对象转换为字符串并在屏幕上渲染
准备工作
你只需要有可用的 Ionic CLI 来运行此应用。
如何做到...
这里是说明:
- 使用空白模板创建一个新的
Navigation应用,如图所示,并进入Navigation文件夹:
$ ionic start Navigation blank
$ cd Navigation
- 使用以下代码编辑
./src/app/app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { OtherPage } from '../pages/otherPage/otherPage';
@NgModule({
declarations: [
MyApp,
HomePage,
OtherPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage,
OtherPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
你必须修改此文件的主要原因是为了通过 NgModule 声明 OtherPage 为一个动态加载的模块。你将不得不在 home.ts 文件中再次声明 OtherPage。
- 编辑
./src/app/pages/home/home.html:
<ion-header>
<ion-navbar>
<ion-title>
Home
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-card>
<ion-card-header>
NavPush 1
</ion-card-header>
<ion-card-content>
<p>Use both <code>navPush</code> and
<code>navParams</code></p>
<button ion-button block [navPush]="otherPage"
[navParams]="myString">
OtherPage 1
</button>
</ion-card-content>
</ion-card>
<ion-card>
<ion-card-header>
NavPush 2
</ion-card-header>
<ion-card-content>
<p>Use only <code>navPush</code> and pass an array instead</p>
<ion-list>
<ion-item>
<ion-label floating>Name</ion-label>
Adding Ionic 2 Components 58
<ion-input type="text" [(ngModel)]="myJSON.text"></ion-
input>
</ion-item>
</ion-list>
<div>
<button ion-button block color="secondary"
[navPush]="otherPage"
[navParams]="myJSON">OtherPage 2</button>
</div>
</ion-card-content>
</ion-card>
<ion-card>
<ion-card-header>
NavPush 3
</ion-card-header>
<ion-card-content>
<p>Use click event to trigger <code>nav.push</code> in
Javascript </p>
<button ion-button block color="dark"
(click)="gotoOtherPage()">OtherPage 3</button>
</ion-card-content>
</ion-card>
</ion-content>
- 使用以下代码编辑
./src/app/pages/home/home.ts:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { OtherPage } from '../otherPage/otherPage';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
public myString: string = 'Example 1 - This is just a string';
public myJSON: any = {text: ''};
otherPage: any = OtherPage;
constructor(public navCtrl: NavController) {
}
gotoOtherPage() {
this.navCtrl.push(OtherPage, {text: 'Example 3 - This is an object'});
}
}
-
创建
./src/app/pages/otherPage文件夹 -
在之前创建的
otherPage文件夹中创建otherPage.html文件:
<ion-header>
<ion-navbar>
<ion-title>Other</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card *ngIf="navParams.data">
<ion-card-header>
navParams.data
</ion-card-header>
<ion-card-content>
{{ navParams.data | json }}
</ion-card-content>
</ion-card>
<button ion-button block (click)="goBack()">
goBack()
</button>
<button ion-button block navPop>
nav-pop
</button>
</ion-content>
- 在同一文件夹中,添加
otherPage.ts,代码如下:
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-other',
templateUrl: 'otherPage.html',
})
export class OtherPage {
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad OtherPage');
}
goBack() {
this.navCtrl.pop();
}
}
- 进入你的终端并运行应用:
$ ionic serve
你也可以使用 Ionic CLI 的生成命令来生成新的页面。例如,要生成一个新页面,你可以使用以下 ionic 命令:ionic generate page pageName。在这里,pageName 是新页面的名称。
你不仅可以生成页面,还可以生成组件、管道以及许多其他东西。请查看 ionicframework.com/docs/cli/generate/
它是如何工作的...
从高层次来看,这是应用的架构:
-
应用将通过
app.ts启动并加载home.html作为root页面 -
/home文件夹中的所有内容都是你的第一页 -
/otherPage文件夹中的所有内容都是你的第二页 -
这两个页面使用
NavParams(或来自(模板))
让我们看看 home.ts。你必须导入 NavController 和 NavParams:
import { NavController, NavParams } from 'ionic-angular';
对于你的构造函数,你需要做一些事情,如下所示:
public myString: string = 'Example 1 - This is just a string';
public myJSON: any = {text: ''};
otherPage: any = OtherPage;
constructor(public navCtrl: NavController) {
}
this.navCtrl 变量将引用导入的 NavController。你应该像这样将其引入,以便使用内部导航功能。myString 和 myJSON 是你将传递给第二页参数的变量。你还需要在模板中稍后使 OtherPage 类可用,并使其对 navPush 可访问。
如所示,gotoOtherPage() 方法做了一件事:将页面推送到当前导航:
gotoOtherPage() {
this.navCtrl.push(OtherPage, {text: 'Example 3 - This is an
object'});
}
这样做,你的应用将立即切换到 OtherPage,这也会包括参数。
第一页(即 home.html)的 home.html 模板演示了以下三种情况:
- 你可以直接在模板中使用
[navPush]和[navParams]。你只需要传递处理此页面的类的内部对象。因此,在这种情况下,你必须传递otherPage而不是OtherPage(注意大写O):
<button block [navPush]="otherPage"
[navParams]="myString">OtherPage 1</button>
- 你也可以将 JSON 对象作为参数传递给
[navPush]:
<button ion-button block color="secondary"
[navPush]="otherPage" [navParams]="myJSON">OtherPage
2</button>
- 第三种情况是手动导航到新页面,如下所示,使用页面类内部实现的方法:
<button block dark (click)="gotoOtherPage()">OtherPage 3</button>
与 Angular 1 或 Ionic 1 不同,你不能再使用 ng-model 进行双向绑定。新的语法将是任何输入元素的 [(ngModel)]。
在你的第二页中,你只需要从构造函数中使 NavController 和 NavParams 可用。
让我们看看你的 otherPage.js 文件:
constructor(public navCtrl: NavController, public params: NavParams) {
}
第二页(即 otherPage.html)的模板非常简单。首先,顶部的导航栏是为了启用默认的返回按钮:
<ion-header>
<ion-navbar>
<ion-title>Other</ion-title>
</ion-navbar>
</ion-header>
返回按钮是 Ionic 中的自动机制,所以你不必担心它何时会显示。
以下代码将在存在状态参数的情况下显示变量内容:
<ion-card *ngIf="params.data">
<ion-card-header>
params.data
</ion-card-header>
<ion-card-content>
{{ params.data | json }}
</ion-card-content>
</ion-card>
ion-card 使用 *ngIf 来决定是否渲染此 DOM。由于 params.data 可能是一个 JSON 对象,你需要将其转换为字符串以在屏幕上显示。Angular 1 有过滤器,但 Angular 将此功能重命名为管道。然而,基本概念是相同的。{{ params.data | json }} 代码基本上告诉 Angular 将 json 函数应用于 params.data 并渲染输出。
你可以使用 nav.pop() 函数返回上一页,如下所示:
<button block (click)="goBack()">
goBack()
</button>
或者,你可以使用指令 navPop 返回,并将其放入你的按钮中,如下所示:
<button block navPop>
nav-pop
</button>
因此,这些都是 Ionic 导航功能中的可能性。
参见
更多信息,请参阅以下链接中的官方 Ionic 文档,了解 NavController 和 NavParams:
要了解更多关于 Angular 管道如何工作,您可以查看以下页面,了解关于 JSON 管道的先前示例:angular.io/api/common/JsonPipe
在应用中同时使用菜单、标签页和分段
在前面的示例中,我们使用了标签页和菜单进行导航。我们在两个不同的应用中使用了它们。但是,有时我们可能想在单个应用中使用标签页和菜单。在这个示例中,我们将使用标签页、菜单和分段。应用的第二页将类似于以下截图:

如果您仔细查看前面的截图,您会看到有一个汉堡菜单按钮,底部有三个标签页,以及页面标题之后的两按钮。这两个按钮实际上是分段按钮。在 UX 中,它们与标签页相似,但在工作方式上非常不同。您将在后面的代码中看到它们是如何不同的。
准备工作
运行此应用需要 Ionic CLI 和网页浏览器。
如何操作...
创建示例应用的说明如下:
- 使用
tabs模板创建一个新的MenuTabsSegment应用,如图所示,并进入MenuTabsSegment文件夹:
$ ionic start MenuTabsSegment tabs
$ cd MenuTabsSegment
- 使用以下代码编辑
./src/app/app.html:
<ion-menu [content]="content">
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item menuToggle>
Close Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav #content [root]="rootPage"></ion-nav>
- 使用以下代码编辑
./src/app/pages/about/about.ts:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
seg:string = "flame";
constructor(public navCtrl: NavController) {
}
}
- 使用以下代码编辑
./src/app/pages/about/about.html:
<ion-header>
<ion-navbar>
<button ion-button menuToggle icon-only>
<ion-icon name='menu'></ion-icon>
</button>
<ion-title>
About
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-segment [(ngModel)]="seg" color="danger">
<ion-segment-button value="flame">
<ion-icon name="flame"></ion-icon>
</ion-segment-button>
<ion-segment-button value="leaf">
<ion-icon name="leaf"></ion-icon>
</ion-segment-button>
</ion-segment>
<div *ngIf="seg === 'flame'">
<ion-card>
<ion-card-header>
Flame
</ion-card-header>
<ion-card-content>
A flame (from Latin flamma) is the visible, gaseous part of
a fire. It is caused by a highly exothermic reaction taking place in
a thin zone.
</ion-card-content>
</ion-card>
</div>
<div *ngIf="seg === 'leaf'">
<ion-card>
<ion-card-header>
Leaf
</ion-card-header>
<ion-card-content>
A leaf is an organ of a vascular plant and is the principal
lateral appendage of the stem.
</ion-card-content>
</ion-card>
</div>
</ion-content>
它是如何工作的...
到目前为止,我们已经使用了菜单和标签页,但它们在不同的应用中使用。在这个示例中,我们在单个应用中同时使用它们。让我们再次查看我们的app.html:
<ion-menu [content]="content">
<ion-header>
<ion-toolbar>
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item menuToggle>
Close Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav #content [root]="rootPage"></ion-nav>
您会注意到我们正在使用ion-menu来显示菜单。我们还使用rootPage初始化 Ionic 导航。如果您检查app.component.ts,您会看到我们将rootPage初始化为等于TabsPage,如下所示:
rootPage:any = TabsPage;
这是使用单页面上同时使用侧边菜单和标签页的关键:
此外,我们在about.html的应用第二页添加了一个分段。我之所以在菜单和标签页旁边使用分段,是因为分段在用户体验方面与标签页非常相似。用户点击它,会根据分段看到不同的视图/内容。但在 Ionic 中,它与标签页有很大不同。请看以下about.html的代码片段:
<ion-segment [(ngModel)]="seg" color="danger">
<ion-segment-button value="flame">
<ion-icon name="flame"></ion-icon>
</ion-segment-button>
<ion-segment-button value="leaf">
<ion-icon name="leaf"></ion-icon>
</ion-segment-button>
</ion-segment>
上述 HTML 代码用于渲染segment容器和segment按钮。我们通过ngModel在AboutPage类中使用seg属性将分段与AboutPage关联起来。当用户点击任何分段按钮时,seg属性初始化为分段按钮的值。在这个例子中,seg属性可以具有flame或leaf的值。根据这个值,我们在about.html的其他部分向用户展示内容,如下面的代码块所示:
<div *ngIf="seg === 'flame'">
<ion-card>
<ion-card-header>
Flame
</ion-card-header>
<ion-card-content>
A flame (from Latin flamma) is the visible, gaseous part of a
fire.
It is caused by a highly exothermic reaction taking place in a
thin zone.
</ion-card-content>
</ion-card>
</div>
<div *ngIf="seg === 'leaf'">
<ion-card>
<ion-card-header>
Leaf
</ion-card-header>
<ion-card-content>
A leaf is an organ of a vascular plant and is the principal
lateral appendage of the stem.
</ion-card-content>
</ion-card>
</div>
你还应该记住,当你加载 AboutPage 时,seg 属性的值将是未定义的。因此,为了进行默认选择,我们必须在 About.ts 中初始化 seg 属性的值,如下所示:
seg:string = "flame";
参见
查看 Ionic 的 MenuController 文档ionicframework.com/docs/api/components/app/MenuController/。它有很好的示例说明如何在同一个应用中使用多个菜单。
使用 Ionic 网格创建复杂 UI
由于本章是关于 Ionic 组件的,我将提到我最喜欢的组件之一是 Ionic 网格——一个非常有用的组件来布局你的应用。基于 Flexbox,它与 Bootstrap 的网格非常相似。Ionic 网格的文档说明:
“网格由三个单元组成——网格、行和列。列将扩展以填充其行,并调整大小以适应额外的列。它基于一个 12 列布局,具有基于屏幕大小的不同断点。列数和断点可以完全使用 Sass 进行自定义。”
我们将使用非常少的代码创建一个复杂的 UI 结构。应用看起来如下所示:

准备工作
就像之前的示例一样,你只需要使用 Ionic CLI 来操作和运行这个示例应用。
如何操作...
这里是创建示例应用的说明:
- 使用
blank模板创建一个新的IonicGrid应用,如图所示,然后进入IonicGrid文件夹:
$ ionic start IonicGrid blank
$ cd IonicGrid
- 使用以下代码编辑
./src/app/pages/home.ts:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
socialFeed:Array<any>;
constructor(public navCtrl: NavController) {
this.socialFeed = [
{ post: 'Building Complex Mobile App Layout using Ionic Grid. You can
nest ion-row inside ion-col and so. It is really awesome.'},
{ post: 'Web components are really great web technology to create
reusable web components which are standard compliant.'},
{ post: 'Nothing is in my mind. I am just writing to make sure there
are at least 3 rows in feed'}
]
}
}
- 使用以下代码编辑
./src/app/pages/home/home.html:
<ion-header>
<ion-navbar color="danger">
<ion-title>
Jon Doe
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-grid>
<ion-row text-center id="info-row">
<ion-col>
<ion-avatar>
<img src="img/avatar.png">
</ion-avatar>
<h3>Based in</h3>
<h4>Vienna, Austria</h4>
</ion-col>
</ion-row>
<ion-row text-center id="contact-icons">
<ion-col><ion-icon name="call" color="danger"></ion-icon>
</ion-col><ion-col><ion-icon name="text" color="danger"></ion-icon>
</ion-col><ion-col><ion-icon name="globe" color="danger"></ion-icon>
</ion-col><ion-col><ion-icon name="more" color="danger"></ion-icon>
</ion-col></ion-row>
<h2 text-center><ion-icon name="paper" color="danger"></ion-icon>
Social Feed</h2>
<ion-row class="social-row" *ngFor="let feed of socialFeed">
<ion-col col-2>
<ion-avatar>
<img src="img/avatar.png">
</ion-avatar>
</ion-col>
<ion-col col-10>
<ion-row>
<ion-col><h3>Jon Doe @jondoe</h3></ion-col>
</ion-row>
<ion-row>
<p>{{feed.post}}</p>
</ion-row>
<ion-row text-center class="social-interaction-row">
<ion-col><ion-icon name="undo" color="danger"></ion-icon>
</ion-col>
<ion-col><ion-icon name="repeat" color="danger"></ion-icon>
</ion-col>
<ion-col><ion-icon name="heart" color="danger"></ion-icon>
</ion-col>
</ion-row>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
- 使用以下代码编辑
./src/app/pages/home/home.scss:
page-home {
#info-row {
ion-col {
ion-avatar img{
margin: 0 auto;
border-radius:50%;
}
}
}
#contact-icons {
ion-icon {
font-size:40px;
}
}
.social-row {
ion-avatar {
margin-top: 0.8rem;
}
p {
font-size:1.6rem;
}
.social-interaction-row {
font-size: 20px;
}
}
}
工作原理...
在 Ionic 网格中,有三种类型的组件。第一种是 ion-grid,第二种是 ion-row,第三种是 ion-col。ion-grid 作为 ion-row 和 ion-col 的容器。它占据父容器的全部宽度。ion-row 用于在网格中创建行。它占据 ion-grid 的全部宽度。ion-col 用于在 ion-row 内部创建列。正如我在前面的章节中提到的,它是一个 12 列网格。因此,在一行中你可以有最多十二列,而不需要换行。
现在,让我们了解我们如何构建我们的示例。
我们的 home.html 页面是一个模拟社交网站的个人资料页面。我们有一个用户头像,他们的位置,姓名,各种联系图标,然后是社交动态。
这就是用户头像和位置在应用中的样子:

以下是这个的代码:
<ion-row text-center id="info-row">
<ion-col>
<ion-avatar>
<img src="img/avatar.png">
</ion-avatar>
<h3>Based in</h3>
<h4>Vienna, Austria</h4>
</ion-col>
</ion-row>
非常简单。我们有一行,一行中有一个列。那个列包含用户头像和位置。请注意,我们已经将头像图片保存在 assets/img 文件夹中。
接下来是联系图标。在我们的应用中,它们看起来如下:

<ion-row text-center id="contact-icons">
<ion-col><ion-icon name="call" color="danger"></ion-icon></ion-
col><ion-col><ion-icon name="text" color="danger"></ion-icon></ion-
col><ion-col><ion-icon name="globe" color="danger"></ion-icon></ion-
col><ion-col><ion-icon name="more" color="danger"></ion-icon></ion-
col></ion-row>
这也很直接。但从这个例子中我们学到的是,如果你在 ion-row 内部有多个 ion-col,Ionic 网格会自动将宽度平均分配给每个 ion-col。这是因为 ion-row 是一个弹性父元素,而 ion-col 是弹性子元素。
最后,我们有了社交动态,其外观如下:

社交动态的代码如下:
<ion-row class="social-row" *ngFor="let feed of socialFeed">
<ion-col col-2>
<ion-avatar>
<img src="img/avatar.png">
</ion-avatar>
</ion-col>
<ion-col col-10>
<ion-row>
<ion-col><h3>Jon Doe @jondoe</h3></ion-col>
</ion-row>
<ion-row>
<p>{{feed.post}}</p>
</ion-row>
<ion-row text-center class="social-interaction-row">
<ion-col><ion-icon name="undo" color="danger"></ion-icon></ion-
col>
<ion-col><ion-icon name="repeat" color="danger"></ion-icon>
</ion-col>
<ion-col><ion-icon name="heart" color="danger"></ion-icon>
</ion-col>
</ion-row>
</ion-col>
</ion-row>
从这个例子中我们可以学到三件事:
-
你可以使用
col-width*属性强制设置col的宽度。我们将width*替换为 1-12 之间的一个值,那么这个列将只占据ion-row空间的width*/12。 -
我们可以在
ion-col内部嵌套ion-row,依此类推。你会注意到我们有一个占据ion-row宽度的 10/12 的ion-col。在这个ion-column内部,我们有一个子ion-row元素,显示用户的帖子,还有一个ion-row来显示三个用于社交分享和互动的图标。 -
每个子元素
ion-row将占据其ion-col父元素的完整宽度。
以下图片展示了这个结构及其说明:

参见
-
想了解更多关于 Ionic 网格的信息,请查看 Ionic 文档:
ionicframework.com/docs/api/components/grid/Grid/。 -
如果你想了解 Flexbox 的工作原理,请查看这个非常好的介绍:
css-tricks.com/snippets/css/a-guide-to-flexbox/。
第三章:使用 Angular 构建块扩展 Ionic
在本章中,我们将介绍与创建自定义组件、指令和过滤器相关的以下任务,使用 Angular:
-
创建自定义披萨订购组件
-
创建自定义用户名输入指令
-
创建自定义管道
-
创建一个用于向多个页面提供数据的共享服务
-
将现有页面作为 HTML 元素重用
简介
大多数 Ionic 的内置功能实际上是预构建的组件。在本节中,你将学习如何使用包含 Ionic 组件的 HTML 模板创建自己的自定义组件。
组件实际上定义了 Angular。组件不过是一个具有自我描述功能的类。例如,<ul>是一个你已经熟悉的组件。以前,你使用了各种 Ionic 组件,如<ion-list>和<ion-item>。组件是一个装饰器(即@Component),用于向类添加元数据以描述以下内容:
-
选择器:这是要在 DOM 中使用的名称(例如,
<my-component>) -
模板或 templateUrl:这指的是组件的渲染方式
-
指令:这指的是你计划在内部使用的指令依赖项列表
组件
-
提供者:这是你计划在内部使用的提供者(即服务)列表
组件
当然,还有许多其他选项,但前面的四个选项是最常见的。
创建自定义披萨订购组件
在本节中,你将构建一个应用程序来演示具有其私有变量和模板的自定义组件。观察以下披萨订购组件的截图:

用户不会注意到哪个区域是页面的一部分,而不是一个自包含的组件。你这里的自定义组件是唯一一个列表正在“监听”素食者复选框的区域:

准备工作
此应用程序示例可以在浏览器或物理设备上运行。
如何做到这一点...
执行以下说明:
- 使用
blank模板创建一个新的MyComponent应用程序,如图所示,然后进入MyComponent文件夹:
$ ionic start MyComponent blank
$ cd MyComponent
- 打开
./src/pages/home/home.html文件,将其内容替换为以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Pizza App
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-card>
<ion-card-header>
App Homepage
</ion-card-header>
<ion-card-content>
Please start to order your pizza now!
</ion-card-content>
</ion-card>
<my-component></my-component>
</ion-content>
这是包含<my-component>的根页面,它将在稍后定义。
- 打开
./app/pages/home/home.ts文件进行全局编辑,以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController) {
}
}
你只需将MyComponent声明为依赖项。组件基本上只是一个具有自我描述功能的类(假设你熟悉 Angular 1 的指令概念)。
- 现在,让我们首先创建一个指令,然后创建组件,如下所示代码所示:
$ mkdir ./src/components
- 在你刚刚创建的
components目录中创建一个foo.ts文件,如下所示:
import { Component } from '@angular/core';
@Component({
selector: 'my-component',
templateUrl: 'foo.html'
})
export class MyComponent {
public data: any = {myToggle: true};
constructor() {}
isClicked(val) {
console.log('Vegetarian: ' + val);
}
}
- 在
./src/components文件夹中创建foo.html,如下所示:
<ion-list>
<ion-item>
<ion-label>Vegetarian</ion-label>
<ion-toggle (click)="isClicked(data.myToggle)" [(ngModel)]="data.myToggle"></ion-toggle>
</ion-item>
<ion-card *ngIf="data.myToggle">
<ion-card-header>
I only eat vegetarian foods
</ion-card-header>
<ion-list>
<button ion-item>
Mushroom
</button>
<button ion-item>
Spinach
</button>
<button ion-item>
Red Peppers
</button>
</ion-list>
</ion-card>
<ion-card *ngIf="!data.myToggle">
<ion-card-header>
I love meat
</ion-card-header>
<ion-list>
<button ion-item>
Beef
</button>
<button ion-item>
Chicken
</button>
<button ion-item>
Sausage
</button>
</ion-list>
</ion-card>
</ion-list>
- 修改
./src/app/app.module.ts,如图所示,以便你可以声明MyComponent。观察以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { MyComponent } from '../components/foo/foo';
@NgModule({
declarations: [
MyApp,
HomePage,
MyComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage,
MyComponent
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 打开你的终端,使用以下命令运行应用:
$ ionic serve
它是如何工作的...
你可能会想知道为什么需要创建一个组件来切换披萨配料选项列表。答案是,这只是一个演示,说明你可以如何使用组件来模块化你的应用。你完成的关键步骤如下:
-
你创建了一个自定义组件,名为
<my-component>,可以在任何地方使用,包括在你的应用外部。 -
组件内的数据是完全私有的。这意味着没有人在不调用组件类中的方法的情况下可以访问它。
-
你可以在组件内部添加或更改行为,而不会影响组件外部的其他区域。
要创建一个组件,你需要确保从 Angular 本身(而不是从 Ionic)导入@Component装饰器,如下所示:
import { Component } from '@angular/core';
@Component({
selector: 'my-component',
templateUrl: 'foo.html'
})
在你的component模板中,所有内容都是局部于component类内部的。因此,你可以使用click绑定点击事件,如下面的代码所示:
<ion-item>
<ion-label>Vegetarian</ion-label>
<ion-toggle (click)="isClicked(data.myToggle)"
[(ngModel)]="data.myToggle"></ion-toggle>
</ion-item>
就像在 Angular 1 中一样,你需要使用[(ngModel)]来声明你想要data.myToggle作为你的模型。[(..)]部分是为了告诉 Angular 2 这是一个双向绑定。
有两个披萨配料列表。第一个列表如下:
<ion-card *ngIf="data.myToggle">
<ion-card-header>
I only eat vegetarian foods
</ion-card-header>
<ion-list>
<button ion-item>
Mushroom
</button>
<button ion-item>
Spinach
</button>
<button ion-item>
Red Peppers
</button>
</ion-list>
</ion-card>
第二个披萨配料列表如下所示:
<ion-card *ngIf="!data.myToggle">
<ion-card-header>
I love meat
</ion-card-header>
<ion-list>
<button ion-item>
Beef
</button>
<button ion-item>
Chicken
</button>
<button ion-item>
Sausage
</button>
</ion-list>
</ion-card>
要根据data.myToggle模型切换每个列表的可见性,你可以使用*ngIf,它与 Angular 1 中的ng-if非常相似。
参见
要了解更多关于 Angular 文档中组件的信息,你可以访问angular.io/docs/ts/latest/guide/architecture.html#!#component。
创建自定义用户名输入指令
由于你在前一节中已经完成了创建组件的过程,你可能想知道组件和指令之间的区别是什么。如果你有一些 Angular 1 的经验,你可能注意到它没有组件的定义。从 Angular 2 开始,有以下三种类型的指令:
| 类型 | 描述 |
|---|---|
| 组件 | 它们有一个与组件关联的模板和类(即ion-input) |
| 结构指令 | 它在它所在的范围内改变 DOM 结构(即*ngIf或*ngFor) |
| 属性指令 | 它通过拦截其显示或事件来改变当前 DOM 的外观 |
你可能在一个指令中同时具有结构和属性特征。在本节中,你将学习如何创建一个属性指令,它可以防止在用户名中输入某些字符,并通过切换其可见性显示另一个 DOM 节点(即显示“你正在输入用户名”)。观察以下应用的截图:

GO 按钮只是为了美观,你不需要为它编写任何代码。
准备工作
此应用示例可以在浏览器或物理设备上运行。
如何操作...
仔细观察以下说明:
- 使用
blank模板创建一个新的MyIonicInputDirective应用,如下所示,并进入MyIonicInputDirective文件夹:
$ ionic start MyIonicInputDirective blank
$ cd MyIonicInputDirective
- 打开
./src/app/pages/home/home.html文件,并用以下代码替换内容:
<ion-header>
<ion-navbar color="danger">
<ion-title>
Login
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item>
<ion-input type="text" placeholder="Username" [(ngModel)]="username" [myIonicInput]="myStyles"></ion-input>
</ion-item>
<ion-item>
<ion-input type="password" placeholder="Password"></ion-input>
</ion-item>
</ion-list>
<p *ngIf="myStyles.showUsername" class="hint">
You are typing username
</p>
<ion-fab bottom center>
<button ion-fab>GO</button>
</ion-fab>
</ion-content>
如前所述,GO 按钮只是 Ionic 新浮动按钮功能的一个示例。你所需要做的只是包含bottom和center来定位它。这些实际上是属性指令的好例子。
- 在前一步骤所在的文件夹中打开
home.ts,进行编辑并插入以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private myStyles = { showUsername: false };
constructor(public navCtrl: NavController) {
}
}
- 创建
./src/directives文件夹,如下所示:
$ mkdir ./src/directives
- 在
directives文件夹中创建my-ionic-input.ts文件,并复制以下代码:
import { Directive, Input, ElementRef, OnInit } from '@angular/core';
@Directive({
selector: '[myIonicInput]', // Attribute selector
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()'
// '(keypress)': 'onKeyPress'
}
})
export class MyIonicInputDirective {
@Input('myIonicInput') myStyles: any;
constructor(private el: ElementRef) {
el.nativeElement.onkeypress = function(e) {
console.log(e);
if ('~!@#$%^&*()-='.includes(String.fromCharCode(e.keyCode))) {
e.preventDefault();
return;
}
}
}
ngOnInit() {
console.log(this.myStyles);
}
onMouseEnter() {
this.myStyles.showUsername = true;
}
onMouseLeave(e) {
this.myStyles.showUsername = false;
}
// onKeyPress will not work with ion-input directly
// because the actual input element is a child of ion-input
// onKeyPress() {
// console.log("onKeyPress");
// }
}
- 打开并编辑
./src/app/app.module.ts以声明你的新指令,如下所示:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { MyIonicInputDirective } from '../directives/my-ionic-input';
@NgModule({
declarations: [
MyApp,
HomePage,
MyIonicInputDirective
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 前往你的终端并运行应用,如下所示:
$ ionic serve
它是如何工作的...
homepage模板(home.html)非常典型,使用了ion-list和ion-item,它们包含了你的输入元素。然而,有两件重要的事情需要注意。首先,在ion-input组件中有一个名为myIonicInput的属性。观察以下代码:
<ion-item >
<ion-input type="text" placeholder="Username"
[(ngModel)]="username" [myIonicInput]="myStyles"></ion-input>
</ion-item>
其次,myStyles对象现在用于切换<p>元素的可见性,
如下所示:
<p *ngIf="myStyles.showUsername" class="hint">
You are typing username
</p>
这个myStyles对象实际上是你home.ts文件中HomePage类的一个private变量,如下所示:
export class HomePage {
private myStyles: Object = {showUsername: false};
}
使用 TypeScript,你可以将一个类型(即一个对象)赋给一个变量,并设置一个默认值。你也许还会注意到,MyIonicInputDirective应该被声明以便将其注入到模板指令中。
要创建一个基本的指令,你必须至少导入Directive和ElementRef以操作 DOM。然而,由于这个Directive有输入(即myStyles),你应在my-ionic-input.ts中导入Input,如下所示:
import {Directive, ElementRef, Input} from '@angular/core';
在你的指令中,有selector和host元数据,如下所示:
@Directive({
selector: '[myIonicInput]',
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()'
// '(keypress)': 'onKeyPress'
}
})
myIonicInput选择器将从 DOM 中查询,并触发该 DOM 节点的动作。对于 DOM 上的事件检测,你必须将事件名称映射到class方法。例如,mouseenter事件将触发指令类MyIonicInputDirective中的onMouseEnter()方法的调用。
现在,让我们更仔细地看看指令的类:
export class MyIonicInputDirective {
@Input('myIonicInput') myStyles: any;
constructor(private el: ElementRef) {
el.nativeElement.onkeypress = function(e) {
console.log(e);
if ('~!@#$%^&*()-
='.includes(String.fromCharCode(e.keyCode))) {
e.preventDefault();
return;
}
}
}
onMouseEnter() {
this.myStyles.showUsername = true;
}
onMouseLeave(e) {
this.myStyles.showUsername = false;
}
// onKeyPress will not work with ion-input directly because the
actual input element is a child of ion-input
// onKeyPress() {
// console.log("onKeyPress");
// }
}
@Input装饰器用于声明你将从模板中引入一个变量。这就是为什么你必须有[myIonicInput]="myStyles"的原因。否则,myStyles将只是一个字符串,而不是指向HomePage类中myStyles对象的表达式。
这里还有一个值得注意的有趣之处是 constructor 中的代码。ElementRef 指向你放置属性指令的相同 DOM。你想要使用 el.nativeElement.onkeypress 来修改键盘的行为,以便不允许特殊字符。如果用户输入了特殊字符,它将触发 e.preventDefault() 而不会发生任何事情。键盘事件基本上被丢弃。你可能想知道为什么我们不能直接使用 keypress 事件并将其映射到 onKeyPress,这被故意注释掉了。原因是你在 ion-input 上放置了 myIonicInput 指令。但实际上 <input> DOM 只是 ion-input 的子元素。因此,如果你在父 ion-input 上监听 keypress 事件,你将无法绑定它。
onMouseEnter 和 onMouseLeave 方法是自我解释的,因为它们只是切换 myStyles.showUsername 变量。再次强调,这个 myStyles 对象只是对 HomePage 中的 myStyles 的引用。因此,如果你在这里更改变量,它也会在主页级别上更改。
参见
-
关于 Angular 2 指令的更多信息,你可以参考官方文档:
angular.io/docs/ts/latest/guide/attribute-directives.html -
由于这是 TypeScript 首次出现的地方,查看手册以获取更多细节可能会有所帮助:
www.typescriptlang.org/docs/tutorial.html
创建自定义管道
管道也是 Angular 的一个特性,并不特定于 Ionic。如果你熟悉 Angular 1,那么一个 pipe 与一个 filter 完全相同。你可能想要使用管道的主要原因是在视图中以不同的格式显示数据。你不想在组件中更改实际值。这使得事情变得非常方便,因为你不需要在代码中决定具体的格式,同时在视图层保持灵活性。以下是一些有用的内置管道列表(来自 angular.io/docs/ts/latest/api/#!?apiFilter=pipe):
-
AsyncPipe -
DatePipe -
NumberPipe -
SlicePipe -
DecimalPipe -
JsonPipe -
PercentPipe -
UpperCasePipe -
LowerCasePipe -
CurrencyPipe -
ReplacePipe
在本节中,你将学习如何使用 @Pipe 装饰器创建一个自定义管道。以下是该应用的截图:

虽然应用界面非常简单,但本例的目的是向你展示如何创建一个管道来提取对象数据。
准备工作
没有必要在物理设备上进行测试,因为 Angular 管道在网页浏览器中运行得很好。
如何做到这一点...
仔细阅读以下说明:
- 使用
blank模板创建一个新的CustomPipe应用,如图所示,然后转到CustomPipe文件夹:
$ ionic start CustomPipe blank
$ cd CustomPipe
- 打开
./src/pages/home/home.html文件,并使用以下代码修改内容:
<ion-header>
<ion-navbar>
<ion-title>
User
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h4>Unformatted Value</h4>
<ion-card>
<ion-card-header>
<code>user</code>
</ion-card-header>
<ion-card-content>
{{ user | json }}
</ion-card-content>
</ion-card>
<h4>Formatted Value</h4>
<ion-list>
<ion-item>
<ion-label fixed>First Name</ion-label>
<ion-note item-right>{{ user | userExtract : "firstname" }}</ion-note>
</ion-item>
<ion-item>
<ion-label fixed>Last Name</ion-label>
<ion-note item-right>{{ user | userExtract : "lastname" }}</ion-note>
</ion-item>
<ion-item>
<ion-label fixed>Age</ion-label>
<ion-note item-right>{{ user | userExtract : "age" }}
</ion-note>
</ion-item>
</ion-list>
</ion-content>
你可以快速看到模板使用了 userExtract 管道来渲染正确的信息。
- 然后,将
./src/pages/home/home.ts的内容替换为以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private user: any;
constructor(public navCtrl: NavController) {
this.user = {
name: 'John Connor',
birthyear: 1985
};
console.log(this.user);
}
}
你还没有 custom-pipe.ts 文件,所以你需要先创建它。
- 使用以下命令创建
./src/utils文件夹:
$ mkdir ./utils/utils
你可以给这个文件夹起任何名字。然而,由于管道有时被认为是 工具 函数,让我们称它为 utils。
- 在
utils文件夹中创建custom-pipe.ts文件,并复制以下代码:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'userExtract' })
export class UserExtractPipe implements PipeTransform {
transform(value: any, arg): any {
let newVal: any;
if (arg == "firstname") {
newVal = value.name ? value.name.split(' ')[0] : '';
} else if (arg == "lastname") {
newVal = value.name ? value.name.split(' ').splice(-
1) : '';
} else if (arg == "age") {
var currentTime = new Date();
newVal = value.birthyear ? currentTime.getFullYear()
- value.birthyear : 0;
}
return newVal;
}
}
- 通过替换以下代码将
UserExtractPipe添加到./src/app/app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { UserExtractPipe } from '../utils/custom-pipe';
@NgModule({
declarations: [
MyApp,
HomePage,
UserExtractPipe
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 打开终端并运行应用程序,如下所示:
$ ionic serve
它是如何工作的...
你可以在视图中使用 Angular 管道来简单地转换或转换任何值到期望的值。你对管道的结构没有任何限制。Angular 会自动检测模板中的 | 符号,并将它前面的值转换为输入。要创建一个管道,你必须导入装饰器并提供一个名称(参见 custom-pipe.ts),如下所示:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'userExtract'})
来自模板的输入是以下 value 参数:
transform(value: any, arg) : any {
transform 方法返回的值将是视图的输出,如下面的代码所示:
return newVal;
在这个例子中,你正在为管道处理提供一个参数,如下面的代码所示:
if (arg == "firstname") {
newVal = value.name ? value.name.split(' ')[0] : '';
} else if (arg == "lastname") {
newVal = value.name ? value.name.split(' ').splice(-1) : '';
} else if (arg == "age") {
var currentTime = new Date();
newVal = value.birthyear ? currentTime.getFullYear() -
value.birthyear : 0;
}
例如,这是 home.html 模板中的内容:
<ion-item>
<ion-label fixed>First Name</ion-label>
<ion-note item-right>{{ user | userExtract : "firstname"
}}</ion-note>
</ion-item>
每个参数都放在冒号(:)之后。然后,在你的 @Pipe 类中,你可以使用 arg 来引用它。其余的代码非常简单,如前所述。观察以下内容:
-
如果是
firstname,取第一个单词 -
如果是
lastname,取最后一个单词 -
如果是
age,从出生年份减去当前年份
当然,你可能会有更复杂的管道场景。然而,总体建议是在视图中保持简单,以确保渲染性能。如果你需要进行大量处理,最好将其作为一个单独的变量来处理。
参见
要了解更多关于 Angular 管道的知识,你可以查看官方文档angular.io/docs/ts/latest/guide/pipes.html
创建一个共享服务以向多个页面提供数据
当你开发一个涉及大量页面和与后端通信的应用程序时,你需要有一种跨页面和组件进行通信的方式。例如,你可能有一个服务从后端请求用户数据并将其存储在公共本地服务中。然后,你需要提供一个让用户更新他们的用户数据并实时看到更新的方法。当用户导航到不同的页面时,相同的信息也会被拉取并渲染,而不需要多次访问后端。这是一个非常常见的场景,需要使用 Angular 中的 @Injectable 装饰器。
观察你将构建的应用的以下截图:

用户可以填写表格并实时查看更新。然后,他们可以转到下一页(转到第 2 页)并查看以下截图:

本页使用与前一页相同的service,并引用相同的日期,带有姓名和年龄。在本节中,你将学习以下主题:
-
使用
@Injectable创建服务 -
在多个页面间共享服务
-
在服务内部使用 getter 和 setter 检测变化
准备工作
此应用示例可以在浏览器或物理设备上运行。
如何操作...
观察以下说明:
- 使用如下所示的
blank模板创建一个新的SharedService应用,并转到SharedService文件夹:
$ ionic start SharedService blank
$ cd SharedService
- 由于你有两个页面和一个为两者共用的服务,你需要在目录中进行一些更改。让我们首先修改
./src/app/app.component.ts文件,以便rootPage指向Page1:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { Page1 } from '../pages/page1/page1';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = Page1;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}
- 创建
./src/pages/page1,如下所示:
$ mkdir ./src/pages/page1
- 在
page1文件夹中创建你的第一个模板page1.html,如下所示:
<ion-header>
<ion-navbar>
<ion-title>
Profile
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card>
<ion-card-header>
What you are entering
</ion-card-header>
<ion-card-content>
<ion-badge item-right>Name</ion-badge> {{ user.name }}
<br><br>
<ion-badge item-right>Age</ion-badge> {{ user.age }}
</ion-card-content>
</ion-card>
<ion-list>
<ion-item>
<ion-label fixed>Name</ion-label>
<ion-input type="text" [(ngModel)]="user.name">
</ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Password</ion-label>
<ion-input type="number" [(ngModel)]="user.age">
</ion-input>
</ion-item>
</ion-list>
<button ion-button full block (click)="goToPage2()">Go to Page 2</button>
</ion-content>
- 在
page1文件夹中创建page1.ts,如下所示:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { UserService } from '../../services/user';
import { Page2 } from '../page2/page2';
@Component({
selector: 'page-one',
templateUrl: 'page1.html'
})
export class Page1 {
private user: any;
private nav: any;
constructor(public navCtrl: NavController, user:
UserService, nav: NavController) {
console.log(user.name);
this.user = user;
this.nav = nav;
}
goToPage2() {
this.nav.push(Page2);
}
}
文件扩展名是.ts,而不是.js,因为你将使用一些 TypeScript 特定的功能,例如 getter 和 setter。
- 类似地,使用以下命令创建
page2文件夹:
$ mkdir ./src/pages/page1
- 在
page2文件夹中同样添加page2.html模板,如下所示:
<ion-header>
<ion-navbar>
<ion-title>
Confirmation
</ion-title>
</ion-navbar>
</ion-header>
<ion-content class="home">
<ion-card>
<ion-card-header>
Please confirm your profile
</ion-card-header>
<ion-card-content>
{{ user.name }} is {{ user.age }} years old
</ion-card-content>
</ion-card>
<button ion-button full block (click)="goToPage1()">Back to Page 1</button>
</ion-content>
这是你的第二页,带有相同的name和age信息。
- 在
page2文件夹中创建page2.ts,如下所示:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { UserService } from '../../services/user';
import { Page1 } from '../page1/page1';
@Component({
selector: 'page-two',
templateUrl: 'page2.html'
})
export class Page2 {
private user: any;
private nav: any;
constructor(public navCtrl: NavController, user:
UserService, nav: NavController) {
console.log(user.name);
this.user = user;
this.nav = nav;
}
goToPage1() {
this.nav.push(Page1);
}
}
- 使用以下命令创建
services文件夹:
$ mkdir ./src/services
- 将
UserService放入services文件夹中的user.ts文件,如下所示:
import { Injectable } from '@angular/core';
@Injectable()
export class UserService {
private _name: string;
private _age: number;
constructor() {
this._name = 'John Connor';
this._age = 31;
}
get name() {
return this._name;
}
set name(newVal) {
console.log('Set name = ' + newVal);
this._name = newVal;
}
get age() {
return this._age;
}
set age(newVal) {
console.log('Set age = ' + newVal);
this._age = newVal;
}
}
- 打开并编辑
./src/app/app.module.ts,以便你可以将UserService作为全局提供者注入,并声明Page1和Page2:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
;
import { Page1 } from '../pages/page1/page1';
import { Page2 } from '../pages/page2/page2';
import { UserService } from '../services/user';
@NgModule({
declarations: [
MyApp,
Page1,
Page2
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
Page1,
Page2
],
providers: [
StatusBar,
SplashScreen,
UserService,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 确认你的文件夹结构看起来像以下截图:

- 在你的终端中运行以下命令来运行应用:
$ ionic serve
你可以从page 1移动到page 2,然后再返回,数据将在页面间持久化。
它是如何工作的...
通常,如果你想在多个页面间使用一个通用服务,你必须在其最高级别注入它。在这个例子中,你将UserService作为依赖项放在app.module.ts的开始处,如下所示:
providers: [UserService]
之后,应用内的其他页面可以开始使用这个通用服务,而无需重新注入。主要原因在于,每次你注入一个服务或类时,它都会实例化一个新的对象,这最终会清除内存中所有现有的数据。如果你想数据在页面间持久化,它应该位于父应用中,以避免重新注入。
要在每一页使用UserService,你只需导入它,如下所示:
import { UserService } from '../../services/user';
引入服务的方法是在构造函数(page1.ts)中放置引用,
如下所示:
constructor(user: UserService, nav: NavController) {
console.log(user.name);
this.user = user;
this.nav = nav;
}
这会将 UserService 引用传递到页面的本地私有变量(在这种情况下,this.user)。
从模板的角度来看,使用 {{ user.name }} 和 {{ user.age }} 注入数据之间没有区别。
现在,让我们看看 UserService:
import {Injectable} from '@angular/core';
@Injectable()
export class UserService {
private _name: string;
private _age: number;
constructor() {
this._name = 'John Connor';
this._age = 31;
}
get name() {
return this._name;
}
set name(newVal) {
console.log('Set name = ' + newVal);
this._name = newVal;
}
get age() {
return this._age;
}
set age(newVal) {
console.log('Set age = ' + newVal);
this._age = newVal;
}
}
实际上,这里有几个事情在进行。首先,你需要从
@angular/core。
不要忘记在 @Injectable() 中使用括号。
其次,如果你想使用获取器和设置器,你需要创建单独的变量,称为 _name 和 _age,来存储数据。然后,你可以使用 get/set 方法在其他页面访问或设置这个公共类中的变量时进行额外的处理。如果你从“页面 1”更改 name 或 age,你可以在控制台中看到以下日志:

这个特性非常有用,因为你可以用它来替代 watch 或 observable。如果你还记得 Angular 1,你必须使用 $scope.$watch 来实现类似的功能。
参考以下内容
-
想了解更多关于 Angular 2 服务的相关信息,请访问官方文档:
angular.io/docs/ts/latest/tutorial/toh-pt4.html -
你可以在
angular.io/docs/ts/latest/cookbook/component-communication.html上找到关于许多组件通信技术的详细说明。
重复使用现有的页面作为 HTML 元素
到目前为止,我们已经使用了 Ionic 页面和 Ionic 的导航系统。在这个示例中,我们将扩展现有的页面,以便我们可以将其用作应用程序中的 HTML 元素/组件。以下是应用程序的第一个页面:

当你点击“显示联系页面”时,它会显示包含上一页信息的联系页面,如下面的截图所示:

我们将重新利用这个联系页面作为应用程序中的 HTML 元素。
准备工作
在这个示例中,我们使用 Web 功能,因此我们只需要一个 Web 浏览器来运行应用程序。
如何做到这一点...
按照以下说明操作:
- 使用
blank模板创建一个新的应用程序PageComponent,如下面的代码块所示,并进入PageComponent文件夹:
$ ionic start PageComponent blank $ cd PageComponent
- 打开
./src/app/pages/home/home.html文件,并用以下代码替换其内容:
<ion-header>
<ion-navbar>
<ion-title>
Ionic Blank
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item>
<ion-label fixed>Name</ion-label>
<ion-input type="text" value="" [(ngModel)]="user.name">
</ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Email</ion-label>
<ion-input type="text" [(ngModel)]="user.email">
</ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Phone</ion-label>
<ion-input type="text" [(ngModel)]="user.phone">
</ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Website</ion-label>
<ion-input type="text" [(ngModel)]="user.website">
</ion-input>
</ion-item>
</ion-list>
<button full ion-button (click)="toggleContact()">
TOGGLE CONTACT COMPONENT</button>
<button full ion-button (click)="openContact()">SHOW CONTACT PAGE</button>
<page-contact [userInput]="user" *ngIf="showContact">
</page-contact>
</ion-content>
- 打开
./src/pages/home/home.ts文件,并用以下内容替换其内容:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ContactPage } from "../contact/contact";
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
user:any = {};
showContact:Boolean = false;
constructor(public navCtrl: NavController) {}
openContact() {
this.navCtrl.push(ContactPage, { user: this.user });
}
toggleContact() {
this.showContact = !this.showContact;
}
}
- 现在创建一个文件夹,
./src/pages/contact,然后在文件夹中创建contact.html并添加以下内容:
<ion-header *ngIf="!userInput">
<ion-navbar>
<ion-title>
Contact Page
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card>
<ion-card-header>
User Contact
</ion-card-header>
<ion-card-content>
<ion-grid>
<ion-row>
<ion-col>Name</ion-col>
<ion-col>{{user.name}}</ion-col>
</ion-row>
<ion-row>
<ion-col>Email</ion-col>
<ion-col>{{user.email}}</ion-col>
</ion-row>
<ion-row>
<ion-col>Phone</ion-col>
<ion-col>{{user.phone}}</ion-col>
</ion-row>
<ion-row>
<ion-col>Website</ion-col>
<ion-col>{{user.website}}</ion-col>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</ion-content>
- 在同一个文件夹中,创建一个名为
contact.ts的文件,并将其内容添加如下:
import { Component, Input, OnChanges } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-contact',
templateUrl: 'contact.html'
})
export class ContactPage {
user:any = {};
@Input() userInput;
constructor(public navCtrl: NavController, private params:NavParams) {
this.user = params.get('user');
}
ngOnChanges() {
if(this.userInput) {
this.user = this.userInput;
}
}
}
- 现在打开
./src/app/app.modules.ts并在NgModule的declarations和entryComponents列表中添加ContactPage,如下面的代码所示:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ContactPage } from '../pages/contact/contact';
@NgModule({
declarations: [
MyApp,
HomePage,
ContactPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage,
ContactPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 现在转到终端,使用以下命令运行应用程序:
$ ionic serve
它是如何工作的...
我们已经在导航中使用了多个 Ionic 页面,所以我们知道 push/pop 导航是如何工作的。让我们更仔细地看看我们是如何定义 Ionic 页面的,如下所示:
@Component({
selector: 'page-contact',
templateUrl: 'contact.html'
})
export class ContactPage {}
你可以看到,Ionic 页面实际上是一个 Angular 组件,我们已经知道我们可以使用它们的 selector 将组件用作 HTML 元素。在上面的例子中,页面的选择器是 page-contact。所以从技术上讲,我们可以使用选择器在 HTML 中。但如果我们的页面正在使用 NavParams 从上一页获取数据,这就会成为一个问题。让我们看看 ContactPage 的构造函数,如下面的代码块所示:
constructor(public navCtrl: NavController, private params:NavParams) {
this.user = params.get('user');
}
在代码中,我们使用 Ionic 的导航从上一页获取用户信息。因此,如果我们想将此组件作为 HTML 元素使用,我们需要以某种方式将此数据传递给组件。
这就是 @Input 装饰器特别有用之处。@Input 装饰器允许我们将数据作为输入传递给 component。因此,我们有一个 @Input() userInput 作为组件的输入。这个 userInput 的值与前面代码中显示的 NavParams 中的用户相同。唯一的区别是,当我们使用页面组件作为 HTML 元素时,userInput 将会有值,而 params.get('user') 将会有值,当我们通过导航控制器使用此组件时。
以下是该应用程序的第一页:

当用户在输入字段中输入信息然后点击“显示联系人页面”时,它会打开 ContactPage,如下面的截图所示:

需要注意的是,当我们将 Ionic 页面作为其他页面中的 HTML 组件使用时,可能不希望显示页面的标题栏。如果你查看 contact.html**,你会看到当我们将其用作 HTML 组件时,我们会隐藏页面的标题,如下所示:
<ion-header *ngIf="!userInput">
<ion-navbar>
<ion-title>
Contact Page
</ion-title>
</ion-navbar>;
</ion-header>
因此,我们的意思是,只有当 userInput 为空时才显示标题。这个 userInput 是通过 @Input 传入的,正如章节中描述的那样。所以,如果你查看 home.html 文件,你将在其中添加以下代码以在 HomePage 内显示 ContactPage:
<page-contact [userInput]="user" *ngIf="showContact">
</page-contact>
当我们点击“切换联系人组件”时,它会在 HomePage 内显示相同的 Contact Page,如下面的截图所示:

参见
-
更多关于 Angular 组件的信息请参阅
angular.io/api/core/Component。 -
Angular 组件有生命周期和与之相关的事件。我们可以连接到任何生命周期事件,如销毁或初始化。更多关于生命周期钩子的信息请参阅
angular.io/guide/lifecycle-hooks。
第四章:验证表单和执行 HTTP 请求
在本章中,我们将涵盖与创建表单输入验证、模拟 API 调用和使用 Stripe 的支付页面相关的以下任务:
-
创建具有输入验证的复杂表单
-
在 Ionic 中创建响应式表单
-
通过使用静态 JSON 文件模拟的 API 获取数据
-
与 Stripe 集成进行在线支付
简介
所有移动应用程序都需要获取用户输入并将其发送到后端服务器。一个简单的例子是填写表单,如用户注册表或联系表。在发送到后端之前,信息将与一组规则进行验证。此外,还有许多其他场景,信息是基于用户在应用程序中的行为捕获的,例如他们在某个页面上的触摸或停留时间。无论如何,你都会遇到许多发送和检索数据的情况。
本章将涵盖以下三个基本示例:
-
如何验证用户输入,例如文本、数字以及必填与非必填,并将数据传递到另一页面
-
如何在没有实际后端的情况下渲染数据
-
如何使用 Stripe 处理支付
所有这些实际上在 Angular 2 中都是原生可用的。然而,由于 Angular 2 在处理数据和与后端服务器交互方面与 Angular 1 相比有很多变化,因此详细讨论这些主题是值得的。
创建具有输入验证的复杂表单
在本节中,你将构建一个应用程序来演示使用 ngForm 和 ngControl 进行表单验证。以下是表单的截图:

如果用户尝试提交而没有提供有效信息,表单将显示以下错误:

基本上,姓名字段是必需的。电话字段是数字类型,但为可选。评论字段是必需的,用户必须输入至少四个字符。当然,这只是为了演示输入长度。最后,用户必须通过切换输入同意条款和条件。
验证成功后,用户将被带到第二个屏幕,其中包含上一个屏幕的摘要,如下面的截图所示:

准备工作
此应用程序示例可以在浏览器或物理设备上运行。然而,你可以选择将物理设备连接到验证电话字段的数字键盘。
如何做到这一点...
- 按照示例,使用
blank模板创建一个新的MyFormValidation应用程序,然后进入MyFormValidation文件夹:
$ ionic start MyFormValidation blank
$ cd MyFormValidation
- 打开
./src/app/app.module.ts文件,并将其内容替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ThankyouPage } from '../pages/thankyou/thankyou';
import { MyFormService } from '../services/myform';
@NgModule({
declarations: [
MyApp,
HomePage,
ThankyouPage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage,
ThankyouPage
],
providers: [
StatusBar,
SplashScreen,
MyFormService,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
你可能会意识到应用程序中有一个共同的服务要使用,这里称为 MyFormService。此示例还有一个第二页,称为 ThankyouPage。
- 现在,让我们首先创建一个目录来创建服务,如下所示:
$ mkdir ./src/services
-
在你刚刚创建的组件目录中创建
myform.ts文件,如下所示:
import { Injectable } from '@angular/core';
@Injectable()
export class MyFormService {
public name: string = '';
public phone: number;
public comment: string = '';
}
这个例子将服务组件保持简单,以供演示目的。
- 打开并编辑
./src/pages/home/home.html模板,如下所示:
<ion-header>
<ion-navbar color="primary">
<ion-title>
Contact Form
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<p class="center">
<ion-icon class="large lighter" primary name="contact"></ion-icon>
</p>
<form #f="ngForm" novalidate (ngSubmit)="onSubmit(f)">
<ion-list>
<ion-item>
<ion-label floating>Name</ion-label>
<ion-input type="text" name="name" required
[(ngModel)]="data.name"></ion-input>
</ion-item>
<div [hidden]="f.controls.name && (f.controls.name.valid ||
(f.controls.name.pristine && !isSubmitted))" class="note danger">
Name is required
</div>
<ion-item>
<ion-label floating>Phone</ion-label>
<ion-input type="tel" name="phone" [(ngModel)]="data.phone">
</ion-input>
</ion-item>
<ion-item>
<ion-label floating>Comment</ion-label>
<ion-input type="text" required minlength=4 name="comment"
[(ngModel)]="data.comment">
</ion-input>
</ion-item>
<div *ngIf="(isSubmitted && f.controls.comment &&
f.controls.comment.pristine) || ((f.controls.comment) &&
(f.controls.comment.dirty && f.controls.comment.errors))" class="note
danger">
Please enter {{ 4 - (f.controls.comment.errors.minlength ?
f.controls.comment.errors.minlength.actualLength : 0) }} more
characters
</div>
<ion-item class="tos">
<ion-toggle item-left [(ngModel)]="data.tos" name="tos"
type="button" (click)="noSubmit($event)"></ion-toggle>
<ion-label item-right>Agree to terms and conditions
</ion-label>
</ion-item>
<div [hidden]="(!isSubmitted) || (f.controls.tos && data.tos)"
class="note danger">
Please check Agree!
</div>
</ion-list>
<div class="center">
<button ion-button type="submit" round outline>Submit</button>
</div>
</form>
</ion-content>
这可能是表单验证过程中最复杂的一部分,因为有很多地方你必须嵌入输入的验证逻辑。
- 打开并替换
./src/pages/home/home.scss文件的内容,如下所示:
page-home {
.center {
text-align: center;
}
ion-icon.large {
font-size: 7em;
}
ion-icon.lighter {
opacity: 0.5;
}
ion-list > .item:first-child {
border-top: 0;
}
ion-list > .item:last-child, ion-list > ion-itemsliding:last-child .item
{
border-bottom: 0;
}
.tos {
padding-top: 10px;
ion-toggle {
padding-left: 0px;
}
.item-inner {
border-bottom: 0;
}
}
.item ion-toggle {
padding-left: 0;
}
.note.danger {
padding-left: 16px;
color: #d14;
}
}
- 打开
./src/pages/home/home.ts文件进行编辑,如下所示:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ThankyouPage } from '../thankyou/thankyou';
import { MyFormService } from '../../services/myform';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private data: any;
private isSubmitted: Boolean = false;
constructor(public nav: NavController, private formData:
MyFormService) {
this.nav = nav;
this.formData = formData;
this.data = {
name: '',
phone: '',
comment: '',
tos: false
}
}
onSubmit(myForm) {
this.isSubmitted = true;
console.log('onSubmit');
console.log(myForm);
if ((myForm.valid) && (myForm.value.tos)) {
this.formData.name = this.data.name;
this.formData.phone = this.data.phone;
this.formData.comment = this.data.comment;
this.nav.push(ThankyouPage);
}
}
noSubmit(e) {
e.preventDefault();
}
}
你可能会注意到 JavaScript 部分中并没有太多的验证代码。这意味着模板处理了大部分验证。还有一个用于 thankyou 页面的 import 命令,你将不得不在下一步创建它。
- 现在,让我们创建
thankyou文件夹,如下所示:
$ mkdir ./src/pages/thankyou
- 在你刚刚创建的
Component目录中创建一个thankyou.ts文件,如图所示:
import { Component } from '@angular/core';
import { MyFormService } from '../../services/myform';
@Component({
selector: 'page-thankyou',
templateUrl: 'thankyou.html'
})
export class ThankyouPage {
constructor(private formData: MyFormService) {
this.formData = formData;
}
}
这个页面只是渲染 MyFormService 服务的数据,所以你可以让它非常简单。
- 在
./src/pages/thankyou文件夹中创建thankyou.html,如图所示:
<ion-header>
<ion-navbar color="secondary">
<ion-title>
Thank You
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h6 class="padding">
You submitted the following information
</h6>
<div class="my-table">
<ion-row>
<ion-col width-25 class="my-label">Name</ion-col>
<ion-col width-75>{{ formData.name }}</ion-col>
</ion-row>
<ion-row>
<ion-col width-25 class="my-label">Phone</ion-col>
<ion-col width-75>{{ formData.phone }}</ion-col>
</ion-row>
<ion-row>
<ion-col width-25 class="my-label">Comment</ion-col>
<ion-col width-75>{{ formData.comment }}</ion-col>
</ion-row>
</div>
</ion-content>
- 在
./src/pages/thankyou文件夹中创建thankyou.scss,如下所示:
page-thankyou {
h6.padding {
color: #4C555A;
padding: 10px;
}
.my-label {
text-align: right;
font-weight: bold;
}
.my-table {
ion-row {
color: #4C555A;
padding: 0;
height: 30px;
}
ion-row + ion-row {
margin-top: 0;
}
ion-row:nth-child(odd) ion-col {
background: #F9FAFB;
}
}
}
- 前往你的终端,使用以下命令运行应用程序:
$ ionic serve
它是如何工作的...
让我们从包含大部分验证代码的 home.html 文件开始。如果你看看这个页面的结构,它非常典型。你有一个 <ion-navbar>,里面有一个 <ion-title>。<form> 元素必须位于 <ion-content> 区域内。
使用 <form> 元素是 Angular 验证的一个要求。否则,将没有 submit 事件,你无法捕获每个输入的错误。
form 具有以下属性:
<form #f="ngForm" novalidate (ngSubmit)="onSubmit(f)">
要即时分配一个局部变量,你使用 # 符号。这意味着你希望 f 变量引用 ngForm,这是 Angular 自动创建的。这是一个包含与当前表单相关的所有内容的特殊对象。建议使用 novalidate 来绕过默认的 HTML5 验证,因为你正在使用 Angular 进行验证。否则,form 将会冲突。(ngSubmit) 几乎是一个事件,当 type=submit 的 button 被触摸或点击时,会触发 onSubmit(f) 函数。当你提交表单时,它将传递 f 变量,这样你就可以在 onSubmit 方法中处理该对象内部的对象。
form 模板仅由 <ion-list> 和 <ion-item> 组成。你只需要知道如何验证每个输入并显示错误。让我们以 Name 字段作为第一个例子。这是 Name 的 <ion-input>:
<ion-input type="text" name="name" required [(ngModel)]="data.name"></ion-input>
下面的错误显示如下:
<div [hidden]="f.controls.name && (f.controls.name.valid || (f.controls.name.pristine && !isSubmitted))" class="note danger">
Name is required
</div>
为了验证,你必须将 name 分配给一个局部变量名。这是为了在其他地方使用 f.controls.name 来引用该输入。回想一下,f 变量已经被声明为 ngForm 对象。以下是 ngForm 的结构视图:

你可以通过 Chrome 开发者控制台查看这一点,因为代码在提交表单时实际上会给出这个输出。
当以下任一条件成立时,错误信息Name is required将被隐藏:
-
表单尚未提交。否则,人们在输入任何内容之前就会立即看到错误信息。这不是一个好的用户体验。为了检查这一点,你必须使用一个临时的布尔值,称为
isSubmitted。f.controls.name.pristine变量表示输入尚未被修改。其相反的是f.controls.name.dirty。 -
f.controls.name.valid变量为true。然而,你不能立即检查这一点,因为如果输入为空,name对象尚不存在。这就是为什么在检查valid布尔值之前需要检查f.controls.name的存在。
没有必要检查电话要求;因此,你只需要分配name和一个模型,如下所示:
<ion-input type="tel" name="phone" [(ngModel)]="data.phone"></ion-input>
对于comment字段,你需要使用required和minlength=4进行验证,如下所示:
<ion-input type="text" required minlength=4 name="comment" [(ngModel)]="data.comment"></ion-input>
你可能会认为required是不必要的,因为如果长度为零,Angular 将触发一个错误标志。然而,这并不正确。当用户在输入框中没有输入任何内容时,输入将没有长度,因为变量不存在。这就是为什么你需要检查这两种情况。
对于comment字段的错误信息非常有趣,因为它显示了用户需要输入的字符数,如下面的代码所示:
<div *ngIf="(isSubmitted && f.controls.comment &&
f.controls.comment.pristine) || ((f.controls.comment) &&
(f.controls.comment.dirty && f.controls.comment.errors))"
class="note danger"> Please enter {{ 4 - (f.controls.comment.errors.minlength
?
f.controls.comment.errors.minlength.actualLength : 0) }} more
characters
</div>
这里的主要思想是,你只想在表单提交且通过f.controls.comment.pristine保持原始状态时显示这个div。这意味着用户在表单中未输入任何内容。你同样希望在表单被修改且有错误通过f.controls.comment.errors显示消息。如果你检查控制台,你可以在f.controls.comment.errors对象下看到许多详细的错误列表。为了告诉用户他们还剩下多少字符可以输入,你必须首先检查f.controls.comment.errors.minlength,因为如果该变量不存在,则没有错误或comment输入为空。如果你不检查这一点,你将在稍后得到解析错误。
在你的home.ts文件中,onSubmit方法必须将isSubmitted布尔值切换为true,如下面的代码片段所示:
onSubmit(myForm) {
this.isSubmitted = true;
console.log('onSubmit');
console.log(myForm);
if ((myForm.valid) && (myForm.value.tos)) {
this.formData.name = this.data.name;
this.formData.phone = this.data.phone;
this.formData.comment = this.data.comment;
this.nav.push(ThankyouPage);
}
}
然后,你必须对myForm.valid和myForm.value.tos进行一般性检查。你可能想知道为什么我们在这里检查tos而不是在模板内进行验证。原因是由于在 Angular 中没有验证切换按钮的意义,因为它不能被required,所以你必须在自定义验证中确保它在表单中为true。这意味着用户已经检查了同意条款和条件的切换按钮。
参考 W3 网站,在 www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html ,了解 button 元素的默认行为。
thankyou 页面非常直观,因为你只需通过从 MyFormService 服务获取数据来在模板中解析 formData 对象。
参见
查看以下链接以获取更多信息:
-
关于 Angular 2 文档中
form的更多信息,你可以访问angular.io/docs/ts/latest/guide/forms.html和angular.io/docs/ts/latest/api/forms/index/NgForm-directive.html。 -
ionic 文档有一个专门针对 Ionic 输入组件的页面,位于
ionicframework.com/docs/v2/resources/forms/ -
它还列出了你可以用于验证或键盘强制的 HTML5 输入类型,你可以在
ionicframework.com/html5-input-types/找到。
在 Ionic 中创建响应式表单
在上一个示例中,我们已经创建了一个具有验证的复杂表单。如果你仔细观察,我们已经在模板文件中使用了 angular 验证,特别是在 home.html 中。这类表单被称为模板驱动表单,其中大部分工作都是在模板端完成的。这与我们在 AngularJS 中所做的是非常相似的。
在 Angular 中还有另一种类型的表单,称为响应式表单。区别在于,在响应式表单中,我们在组件类中使用了验证和其他配置,而不是在模板中。以下是从 Angular 文档中的定义:
“使用 reactive 表单,你在组件类中创建一个 Angular 表单控件对象的树,并将它们绑定到组件模板中的原生表单控件元素。”
我们将创建一个注册表单,其外观如下:

准备工作
由于我们正在使用表单,我们只需要一个网络浏览器来运行这个食谱。
如何操作...
- 打开终端(或命令提示符)并创建一个基于
blank模板的新应用,命名为ReactiveForm,然后按照以下步骤进入文件夹:
$ ionic start ReactiveForm blank
$ cd ReactiveForm
- 打开
home.html并将其替换为以下内容:
<ion-header>
<ion-navbar color="primary">
<ion-title>
Reactive Form
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<p class="center">
<ion-icon class="large lighter" primary name="contact"></ion-icon>
</p>
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" novalidate>
<ion-list>
<ion-item>
<ion-label floating>Username</ion-label>
<ion-input type="text" formControlName="username"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Email</ion-label>
<ion-input type="email" formControlName="email"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Password</ion-label>
<ion-input type="password" formControlName="pass"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Confirm Password</ion-label>
<ion-input type="password" formControlName="repass"></ion-input>
</ion-item>
<ion-item text-wrap class="error">
<div *ngIf="registerForm.controls['username'].dirty && registerForm.controls['username'].invalid">
Username should at least have 10 Characters.
</div>
<div *ngIf="registerForm.controls['email'].dirty && registerForm.controls['email'].invalid">
Email is incorrect.
</div>
<div *ngIf="registerForm.controls['pass'].dirty && registerForm.controls['pass'].invalid">
Password should be 8 Character long.
</div>
<div *ngIf="registerForm.controls['repass'].dirty && registerForm.controls['repass'].invalid">
Choose same password in confirm password field.
</div>
</ion-item>
</ion-list>
<button ion-button full type="submit" [disabled]="registerForm.invalid">Register</button>
</form>
</ion-content>
- 打开
home.scss文件并将其内容替换为以下内容:
page-home {
.center {
text-align: center;
}
ion-icon.large {
font-size: 7em;
}
ion-icon.lighter {
opacity: 0.5;
}
.error {
color: red;
}
}
- 打开
home.ts文件并将其内容替换为以下内容:
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NavController, AlertController } from 'ionic-angular';
import { confirmPassword } from "../../app/confirmPassword";
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
registerForm:FormGroup;
constructor(public navCtrl: NavController, private fb:FormBuilder,
private alertCtrl: AlertController) {
this.registerForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(10)]],
email: ['', [Validators.required, Validators.email]],
pass: ['', [Validators.required, Validators.minLength(8)]],
repass: ['', [Validators.required, Validators.minLength(8)]],
}, {
validator: confirmPassword('pass', 'repass')
});
}
onSubmit() {
this.alertCtrl.create({
title: 'Your inputs are:',
message: JSON.stringify(this.registerForm.value),
buttons: ['Dismiss']
})
.present();
console.log(this.registerForm);
}
}
由于在我们的示例中,我们有一个包含密码和确认密码字段的注册表单,我们将创建一个自定义验证器以确保这两个字段都有一些值。
- 在
/app文件夹中创建一个名为confirmPassword.ts的文件。然后在其内部添加以下内容:
import { FormGroup } from '@angular/forms';
export function confirmPassword(passwordKey: string, passwordConfirmationKey: string) {
return (group: FormGroup) => {
let passwordInput = group.controls[passwordKey],
passwordConfirmationInput =
group.controls[passwordConfirmationKey];
if (passwordInput.value !== passwordConfirmationInput.value) {
return passwordConfirmationInput.setErrors({notEquivalent: true})
}
else {
return passwordConfirmationInput.setErrors(null);
}
}
}
- 确保你的
app.module.ts包含以下内容:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 打开终端并运行以下命令:
$ ionic serve
它是如何工作的...
由于这个配方使用了响应式表单,让我们首先看看 home.ts,它初始化并设置我们的表单。
在 HomePage 类中,我们有 registerForm:FormGroup 属性,这是 FormControl 的集合。每个 FormControl 都绑定到模板中的原生 FormControl。为了创建 FormGroup,我们可以做如下操作:
this.myForm = new FormGroup({
first: new FormControl('Nancy', Validators.minLength(2)),
last: new FormControl('Drew'),
});
在前面的例子中,我们创建了一个名为 myForm 的 FormGroup。FormGroup 构造函数接受一个对象作为输入,这是 FormControl 的集合。这个对象中的每个键都指向模板中的某个原生 FormControl。键值对中的值是一个 FormControl 对象。FormControl 构造函数接受初始值和一个验证器或验证器数组作为输入。模板看起来可能如下所示:
<form formGroup="myForm" novalidate>
<input type="text" formControlName="first"/>
<input type="text" formControlName="last" />
</form>
在这里,formControlName 设置为 FormControl 的键;例如,first。formGroup 设置为组的名称;在这个例子中,它是 myForm。
然而,如果我们有嵌套的 FormGroup,这个方法就会变得复杂。为了简化这个过程,Angular 通过 FormBuilder 提供了一个更好的 API。FormBuilder 是一个类,它允许我们通过一个非常好的 API 创建 FormGroup。看看这个例子:
this.registerForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(10)]],
email: ['', [Validators.required, Validators.email]],
pass: ['', [Validators.required, Validators.minLength(8)]],
repass: ['', [Validators.required, Validators.minLength(8)]],
}, {
validator: confirmPassword('pass', 'repass')
});
this.fb 是 FormBuilder 的一个实例,并且有一个 group 方法来创建 FormGroup。它接受一个包含键值对的对象。键与 formControlName 一起使用,以绑定到模板中的原生元素,而值是一个数组,包含原生元素的初始值和验证器列表。例如,username 有 required 和 minLength(10) 验证器;同样,其他字段也有验证。你可能注意到这里没有 FormControl 构造函数。这是 FormBuilder 提供的抽象。
你也可以将组级验证器作为第二个输入传递给 FormBuilder 的 group 方法。在先前的例子中,我们使用了一个自定义的 confirmPassword 验证器,输入为 pass 和 repass。这些是第一个对象中的键,并指向输入字段;验证器确保它们相等。
我们的自定义验证器是在 confirmPassword.ts 文件中创建的,如下所示:
export function confirmPassword(passwordKey: string, passwordConfirmationKey: string) {
return (group: FormGroup) => {
let passwordInput = group.controls[passwordKey],
passwordConfirmationInput = group.controls[passwordConfirmationKey];
if (passwordInput.value !== passwordConfirmationInput.value) {
return passwordConfirmationInput.setErrors({notEquivalent: true})
}
else {
return passwordConfirmationInput.setErrors(null);
}
}
}
这是一个只接受两个字符串类型参数的函数。这两个参数是我们想要比较的两个字段的名称。它比较两个字段中的值,并根据需要设置 repass(即确认密码)字段的验证。
在 home.html 中,我们有如下外观的表单元素:
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" novalidate>
[formGroup] 设置为 registerForm,当用户点击提交按钮时,它会触发 onSubmit 方法。
每个输入都配置如下:
<ion-item>
<ion-label floating>Username</ion-label>
<ion-input type="text" formControlName="username"></ion-input>
</ion-item>
我们还展示了错误消息,因为我们使用了表单验证器。以下是显示验证错误消息的方式。我们检查字段是否为 dirty 或 invalid,然后显示相应的错误。formName.controls['controlName'] 是获取控制器的语法。在这里,formName 是 formGroup 的名称,而 controlName 是控制器的名称:
<ion-item text-wrap class="error">
<div *ngIf="registerForm.controls['username'].dirty && registerForm.controls['username'].invalid">
Username should at least have 10 Characters.
</div>
<div *ngIf="registerForm.controls['email'].dirty && registerForm.controls['email'].invalid">
Email is incorrect.
</div>
<div *ngIf="registerForm.controls['pass'].dirty && registerForm.controls['pass'].invalid">
Password should be 8 Character long.
</div>
<div *ngIf="registerForm.controls['repass'].dirty && registerForm.controls['repass'].invalid">
Choose same password in confirm password field.
</div>
</ion-item>
错误看起来像这样:

最后,我们有了注册按钮,其代码如下:
<button ion-button full type="submit" [disabled]="registerForm.invalid">Register</button>
当表单无效时,我们禁用按钮。当用户点击它时,它会调用 onSubmit 方法,并显示一个包含用户输入的警告框,如下面的代码所示:
onSubmit() {
this.alertCtrl.create({
title: 'Your inputs are:',
message: JSON.stringify(this.registerForm.value),
buttons: ['Dismiss']
})
.present();
console.log(this.registerForm);
}
警告消息看起来如下所示:

参见
-
想要了解更多关于响应式表单的信息,请访问 Angular 的响应式表单指南,链接为
angular.io/guide/reactive-forms -
托德·莫托(Todd Moto)撰写了一篇关于响应式表单的优秀文章,链接为
toddmotto.com/angular-2-forms-reactive
通过使用静态 JSON 文件模拟 API 获取数据
作为前端和应用程序开发者,你通常在一个团队中工作,其中另一个人负责后端 API。然而,在开发前端时,并不总是有可能有后端可用。在最终后端 API 准备好的情况下,你必须模拟后端。
在这个菜谱中,你将学习如何使用 HttpClient 服务调用 REST API。API 端点将只是位于你本地机器上的静态 JSON。你还将学习如何利用占位符图像来满足设计要求。应用将显示图像源列表和描述,如下面的截图所示:

准备工作
这个应用示例可以在浏览器或物理设备上运行。然而,模拟的后端服务器必须在你的本地计算机上运行。
如何做到这一点...
-
首先,让我们快速创建一个模拟的后端服务器。你必须安装
http-server对于以下内容:
$ sudo npm install -g http-server
- 创建一个文件夹来存储你的
.json文件。让我们称它为MockRest,如下所示:
$ mkdir MockRest
$ cd MockRest
- 创建
test.json文件,并填写以下内容作为 REST 响应:
[
{
"title": "What a beautiful song",
"category": "objects",
"description": "Music is a moral law. It gives soul to the universe, wings to the mind, flight to the imagination, and charm and gaiety to life and to everything."
},
{
"title": "The world we live in",
"category": "nature",
"description": "Look deep into nature, and then you will understand everything better."
},
{
"title": "Life experiences",
"category": "people",
"description": "People who know little are usually great talkers, while
men who know much say little."
}
]
基本上,每次你发送 REST 请求时,你应该收到前面的内容作为响应。随着你的后端开发者更新 REST 响应,你总是可以相应地更改 test.json 文件的内容。
- 通过在
MockRest文件夹中的终端调用http-server来启动你的后端服务器,如下所示:
$ http-server --cors=Access-Control-Allow-Origin
前往您的浏览器并访问http://localhost:8080/test.json以验证您是否可以看到 JSON 内容。如果不行,您可能与其他 Web 服务器存在端口冲突。您需要确保没有其他应用程序正在使用端口8080。在完成您的后端后,打开另一个终端窗口,使用blank模板创建一个新的MyRestBackend应用程序,并进入MyRestBackend文件夹,如下所示:
$ ionic start MyRestbackend blank
$ cd MyRestbackend
您不得停止后端服务器或在MockRest文件夹内创建 Ionic 项目。它们是两个独立的项目文件夹
- 打开
home.html文件,并用以下代码替换内容:
<ion-header>
<ion-navbar>
<ion-title>
Home
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-card #myCard *ngFor="let item of quotes.data">
<img [src]='"https://source.unsplash.com/category/" + item.category + "/600x390"' [height]="myCard.clientWidth * 390 / 600" />
<ion-card-content>
<ion-card-title>
{{ item.title }}
</ion-card-title>
<p>
{{ item.description }}
</p>
</ion-card-content>
</ion-card>
</ion-content>
此示例使用来自source.unsplash.com/的免费照片,因为您可以轻松查询以获取满足您需求的随机照片。
- 打开
home.ts并使用以下代码进行编辑:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { QuoteService } from '../../services/quote';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public quotes:QuoteService) {
this.quotes = quotes;
this.quotes.getQuotes();
}
}
您尚未创建QuoteService服务。然而,您可能知道这个服务将使用getQuotes()方法调用模拟后端服务器以获取 JSON 内容。
- 对样式表
home.scss进行以下小修改:
page-home {
ion-card {
img {
background-color: #f4f4f4;
}
}
}
- 使用以下命令创建
./src/services文件夹:
$ mkdir ./src/services
- 在
services文件夹中创建quote.ts文件,并复制以下代码:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class QuoteService {
private http: any;
public data: any;
constructor(http: HttpClient) {
this.http = http;
}
getQuotes() {
this.http.get("http://localhost:8080/test.json")
.subscribe(res => {
this.data = res;
console.log(this.data, this.http);
}, error => {
console.log(error);
});
}
}
- 打开并编辑
./src/app/app.module.ts以声明QuoteService,如下所示:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { QuoteService } from '../services/quote'
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
HttpClientModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
QuoteService,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 前往您的终端并运行应用程序,如图所示:
$ ionic serve
- 您会注意到页面为空,控制台显示以下错误:

这意味着您的浏览器(在这种情况下,Chrome)不允许从http://localhost:8100调用 REST API 到http://localhost:8080。您需要安装允许控制允许源(CORS)插件,例如chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?hl=en,用于 Chrome。之后,打开 CORS,如图所示:

刷新您的浏览器以查看更新后的应用程序。
它是如何工作的...
您的模拟后端简单地返回当前MockRest文件夹中的任何文件。随着您从后端开发者那里获得更多样本响应,您可以将其复制到该文件夹中,以提供额外的后端端点。
本节不提供如何处理 POST 请求和复杂场景的示例,其中响应取决于请求参数。由于它们不是生产代码,您可能希望将处理临时情况的代码保持尽可能简单。建议为每个 POST 请求返回相同的内容。
让我们看看quote.ts文件,因为它是发起Http请求的主要地方。首先,您需要导入Injectable和Http,您可以按照以下方式操作:
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
@Injectable 装饰器用于允许其他页面和组件将 QuoteService 作为依赖项使用。Http 服务(或类)由 Angular(而非 Ionic)提供,这与 Angular 1 中的 $http 提供者类似。然而,Http 将返回一个 可观察 对象,以便您可以 订阅 它。以下所示 getQuotes() 方法是此文件最重要的部分:
getQuotes() {
this.http.get("http://localhost:8080/test.json")
.subscribe(res => {
this.data = res.json();
console.log(this.data);
}, error => {
console.log(error);
});
}
this.http 对象必须从构造函数中注入。然后,它将通过 this.http.get() 触发 GET 请求,就像 $http 提供者一样。然而,在 Angular 2 中没有 .then() 函数;您必须 订阅 对象。ES6 的新特性是箭头函数,正如您通过 res => {} 看到的。这与其他语言的 lambda 函数类似(例如 Python)。您不需要声明函数的名称,也不必每次都键入 function。此外,它自动传递参数(在这种情况下是 res)和函数内部的 this 上下文。
您可以在 TypeScript 文档中了解更多关于箭头函数的信息,请访问 www.typescriptlang.org/docs/handbook/functions.html。
您的模拟后端的 REST 响应将被分配给 QuoteService 服务的 this.data,如下所示:
this.data = JSON.parse(res._body);
如果您查看浏览器控制台,它将类似于以下截图:

在 home.html 模板中,另一个不错的技巧是在图片下载和渲染时显示灰色占位符,而不是将内容向下推,如下面的代码片段所示:
<ion-card #myCard *ngFor="let item of quotes.data">
<img [src]='"https://source.unsplash.com/category/" + item.category + "/600x390"' [height]="myCard.clientWidth * 390 / 600"/>
以下截图显示了在照片加载之前的快速示例:

为了让 <img> 标签具有确切的大小,您必须使用 [height]="myCard.clientWidth * 390 / 600" 进行高度计算。这是因为照片的大小是 600 x 390。myCard 对象是从 ion-card 创建的本地对象。这个 myCard 对象将能够访问 ion-card DOM 的所有属性,包括通过 clientWidth 的宽度。您可能已经注意到,这完全是纯 JavaScript,与 Ionic 或 Angular 本身无关。
参见
关于 Angular Http 提供者的更多信息,您可以访问官方文档 angular.io/docs/ts/latest/api/http/index/HttpModule-class.html。
与 Stripe 集成进行在线支付
在本节中,您将学习如何与真实的后端服务集成以进行支付流程。赚取收入是创建应用的重要方面。虽然有许多其他收集支付的方法,但 Stripe 是一个常见的支付系统,并且可以很好地与 Ionic 集成。此外,您不需要提供高级别的安全性和合规性(即 PCI),因为您不会存储信用卡信息。
您的应用不会通过真实支付方式处理,因为您可以使用 Stripe 的公共测试密钥。应用将要求输入一些字段以创建令牌。观察以下应用截图:

如果您点击支付按钮,它将带您进入下一个屏幕,在那里您将获得支付令牌,如下面的截图所示:

实际上,您的后端调用 Stripe 进行授权和处理交易的步骤还有额外的步骤。然而,这不在本节范围内。Stripe 文档在 Node.js 方面有一个很好的教程页面,网址为stripe.com/docs/api/node#authentication。
准备工作
没有必要在物理设备上进行测试,因为 Ionic 和 Stripe 在网页浏览器中运行得很好。
如何操作...
-
如果您没有 Stripe 账户,您需要在
stripe.com.上注册。 -
将以下显示的发布密钥复制到某个地方,因为您稍后需要将其用于 JavaScript 代码:

- 现在,回到终端,使用
blank模板创建一个新的StripePayment应用,如下所示,并进入StripePayment文件夹:
$ ionic start StripePayment blank
$ cd StripePayment
- 打开
./src/index.html文件,并在<body>标签中插入以下所示行:
<script type="text/javascript"
src="img/"></script>
这是为了在您的应用中全局加载Stripe对象。这不是 Angular 推荐的方法,因为组件内部使用的任何内容都必须通过import指令导入。然而,在撰写本书时,angular-stripe 对 Angular 不可用。因此,没有正确执行此操作的方法。前面的方法将完全正常工作。
- 打开
./src/pages/home/home.html文件,并使用以下代码修改内容:
<ion-content class="gray-bg">
<p class="center">
<ion-icon class="icon-large" name="card"></ion-icon>
</p>
<ion-card>
<ion-card-content>
<ion-card-header>
Credit or debit card
</ion-card-header>
<ion-item>
<div #cardElement>
<!-- a Stripe Element will be inserted here. -->
</div>
</ion-item>
<button ion-button full (click)="onSubmit()">Pay</button>
</ion-card-content>
</ion-card>
</ion-content>
Stripe 只需要信用卡号、CVC 和到期日期来创建用于收费的令牌。客户姓名和地址是可选的;因此,您不需要在此处包含它们。
- 然后,将
./src/pages/home/home.ts的内容替换为以下代码:
import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ThankyouPage } from '../thankyou/thankyou'
declare var Stripe: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private token: string = '';
private card:any;
private elements:any;
private stripe:any;
@ViewChild('cardElement') cardElement;
constructor(public nav: NavController) {
this.nav = nav;
this.stripe = Stripe('YOUR STRIPE PUBLIC KEY HERE');
this.elements = this.stripe.elements();
}
ngOnInit() {
const style = {
base: {
fontSize: '16px',
lineHeight: '24px',
marginBottom: '10px'
},
};
this.card = this.elements.create('card', {style});
this.card.mount(this.cardElement.nativeElement);
}
onSubmit() {
this.stripe.createToken(this.card)
.then((data) => {
this.stripeResponseHandler(data);
});
}
stripeResponseHandler(response) {
if (response.error) {
// Show the errors on the form
console.log('error');
console.log(response.error.message);
} else {
// response contains id and card, which contains additional card
//details
this.token = response.token.id;
// Insert the token into the form so it gets submitted to the server
console.log('success');
console.log('Sending token param:');
console.log(this.token);
this.nav.push(ThankyouPage, { token: this.token });
}
}
}
您需要在此处更改您的Test Publishable Key,将Stripe构造函数中的YOUR STRIPE PUBLIC KEY HERE替换为您之前复制的自己的密钥。
- 使用以下代码编辑
./src/pages/home/home.scss:
.page-home {
.center {
text-align: center;
}
.gray-bg {
background-color: #f4f4f7;
}
.icon-large {
font-size: 150px;
color: #387ef5;
opacity: 0.5;
}
}
- 通过创建一个名为
./src/pages/thankyou的新文件夹来创建显示令牌 ID 的thankyou页面,如图所示:
$ mkdir ./src/pages/thankyou
- 在
thankyou文件夹中创建thankyou.html文件并复制以下代码:
<ion-content class="green-bg">
<h4 class="center">
Your token is
</h4>
<p class="center">
<code>
{{ token }}
</code>
</p>
</ion-content>
在现实中,没有必要向用户显示令牌 ID。这只是一个示例,用于获取令牌 ID 以进行收费。
- 在
thankyou文件夹中创建thankyou.ts文件并复制以下代码:
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'thank-you',
templateUrl: 'thankyou.html'
})
export class ThankyouPage {
private token: string = '';
constructor(public nav: NavController, public params:
NavParams) {
this.token = this.params.get('token');
console.log('Getting token param:');
console.log(this.token);
}
}
- 创建
thankyou.scss文件,使用以下代码修改主题:
thank-you {
.green-bg {
color: black;
background-color: #32db64;
}
h4.center {
padding-top: 150px;
}
.center {
text-align: center;
}
}
- 打开并编辑
./src/app/app.module.ts声明ThankyouPage如下:
import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ThankyouPage } from '../pages/thankyou/thankyou'
@NgModule({
declarations: [
MyApp,
HomePage,
ThankyouPage
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage,
ThankyouPage
],
providers: []
})
export class AppModule {}
- 前往你的终端并运行应用:
$ ionic serve
- 为了测试目的,你可以使用
4242424242424242作为信用卡号,123作为cvc,以及12/2017作为到期日期。
它是如何工作的...
这是 Stripe 收费流程:
-
用户填写支付表单并点击提交按钮。
-
前端(你的 Ionic 应用)将使用
Stripe对象调用 API 并发送所有支付信息。 -
Stripe 将返回一个令牌 ID,这基本上是一种确认一切正确并且你现在可以收费的方式。
-
前端将使用令牌 ID 发送到其后端(不包含信用卡信息)以授权收费。
-
后端将调用另一个 Stripe API 来表示“我现在要收费”。此时,Stripe 将向后端返回
success事件。 -
然后,后端将
success事件返回到前端。 -
前端应该渲染一个新页面,例如
thankyou页面。
如前所述,本章不会涵盖此应用的后端部分,因为它不关注 Ionic。你可以使用任何语言构建后端,例如 Node.js、PHP 或 Python。
让我们看看 home.ts,因为 Stripe API 处理的大部分内容都位于此处。首先,你需要进行一个 declare,如图所示,因为 Stripe 是一个全局对象,它被包含在 index.html 中:
declare var Stripe: any;
如果你没有进行 declare,应用仍然可以运行,但你将收到 TypeScript 的错误。
我们正在使用 Stripe Elements,这是一个预构建的支付 UI 元素集。当我们的 home.ts 页面加载时,我们使用 ngOnInit LikeCycle 钩子初始化我们的支付表单。看看以下代码,在 ngOnInit 中:
this.card = this.elements.create('card', {style});
this.card.mount(this.cardElement.nativeElement);
我们创建了一个具有给定样式的 Stripe 元素卡,然后使用 this.card.mount 将该卡挂载到模板内的 HTML 元素上。
如果你查看 home.html,你会看到类似以下的内容:
<div #cardElement>
<!-- a Stripe Element will be inserted here. -->
</div>
我们在 home.html 中创建了一个 div,并使用 #cardElement 本地变量,然后在 home.ts 中使用 @ViewChild 装饰器获取它,并将其上的 Stripe Element UI 挂载。挂载意味着创建用于卡号、到期日期和 CVV 的输入字段。Stripe Element 还自带其自身的验证和错误信息。
当用户提交表单时,将触发以下方法:
onSubmit() {
this.stripe.createToken(this.card)
.then((data) => {
this.stripeResponseHandler(data);
});
}
当你调用Stripe.card.createToken时,Stripe 对象将在后台触发一个 API 调用到stripe.com/,该调用包含用户通过表单填写的信息。这个功能是通过你在home.html中的以下代码实现的:
<button type="button" ion-button bottom block
(click)="onSubmit()">Pay</button>
一旦 Stripe 返回你的令牌 ID,它将调用this.stripeResponseHandler(response)函数:
stripeResponseHandler(response) {
if (response.error) {
// Show the errors on the form
console.log('error');
console.log(response.error.message);
} else {
// response contains id and card, which contains additional card details
this.token = response.token.id;
// Insert the token into the form so it gets submitted to the server
console.log('success');
console.log('Sending token param:');
console.log(this.token);
this.nav.push(ThankyouPage, { token: this.token });
}
}
response.token.id将包含来自 Stripe 的令牌 ID。否则,你可以使用response.error.message来处理错误。在这个例子中,因为它只将令牌 ID 传递到下一页,所以你可以简单地作为参数发送它:{token: this.token}:
this.nav.push(ThankyouPage, {token: this.token});
在你的thankyou.ts文件中,你可以使用以下代码访问参数token:
this.params.get('token');
参见:
-
要了解更多的 Stripe API 信息,你可以查看官方文档
stripe.com/docs/stripe.js。 -
要了解更多关于 Stripe 元素的信息,你可以查看
stripe.com/elements。 -
你可以在
stripe.com/docs/examples找到更多其他语言的示例进行实验。
第五章:添加动画
在本章中,我们将介绍与添加动画和交互相关的以下任务:
-
将全屏内联视频嵌入为背景
-
使用
Dynamics.js创建基于物理的动画 -
通过将手势绑定到动画状态来动画化幻灯片组件
-
为登录页面添加背景 CSS 动画
简介
用户体验对于用户的初始吸引力至关重要。当您的早期采用者第一次使用应用程序时,他们会有更好的印象,这会建立信任并增加留存率。应用程序动画也将为用户提供交互式反馈,以便他们知道该做什么或可以根据非常温和的视觉提示采取行动。
原生的应用程序曾经因为动画性能而优于基于网络的混合应用程序。然而,像 Ionic 和 Angular 这样的框架在近年来在性能上缩小了差距。由于许多前端开发人员熟悉 JavaScript 和 CSS,因此网络动画也更容易学习和编码。
在本章中,您将学习如何使用视频和 CSS 进行基本动画。然后,您将开始利用基于物理的动画来创建有趣的交互。此外,您甚至可以逐帧绑定手势,以便在滑动事件期间动画立即发生。
将全屏内联视频嵌入为背景
现在,有许多应用程序利用视频作为介绍屏幕的动画背景。这使得应用程序更加有趣和富有创意。用户会感觉到他们受到了应用程序的欢迎。这种策略对于给新用户留下深刻印象并鼓励他们回来是非常好的。
本节将教您如何添加在背景中自动播放的视频:

您还将学习如何使用 animate.css 为应用程序标题文本添加自定义动画。
准备工作
此应用程序示例可以在浏览器或物理设备上运行。然而,将您的物理设备连接以验证动画在后台正确播放是可选的。
如何操作...
以下是在背景中添加自动播放视频的说明:
- 使用
blank模板创建一个新的VideoIntro应用程序,如下所示,并导航到VideoIntro文件夹:
$ ionic start VideoIntro blank
$ cd VideoIntro
-
此时,您需要准备好您的视频。然而,对于这个例子,让我们从不需要许可证的公共网站上下载一个免费的视频。导航到
www.coverr.co。 -
您可以下载任何视频。此应用程序的示例使用
Blurry-People.mp4片段。将其下载到您的计算机:

- 将视频保存在
./src/assets/:

- 打开
./src/index.html文件,并将内容替换为以下代码:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic App</title>
<meta name="viewport" content="width=device-width,
initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0,
user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" type="image/x-icon"
href="assets/icon/favicon.ico">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#4e8ef7">
<!-- Google Fonts -->
<link href='https://fonts.googleapis.com/css?family=Lobster'
rel='stylesheet' type='text/css'>
<!-- cordova.js required for cordova apps -->
<script src="img/cordova.js"></script>
<!-- un-comment this code to enable service worker
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.log('Error', err));
}
</script>-->
<link href="build/main.css" rel="stylesheet">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/
animate.css/3.5.2/animate.min.css">
</head>
<body>
<!-- Ionic's root component and where the app will
load -->
<ion-app></ion-app>
<!-- The polyfills js is generated during the build
process -->
<script src="img/polyfills.js"></script>
<!-- The bundle js is generated during the build
process -->
<script src="img/main.js"></script>
</body>
</html>
基本上,与原始 index.html 文件的主要区别在于您想要
以包含标题文本的Google Lobster字体和animate.css
用于动画。
- 对于
main模板,你可以修改./src/pages/home.html文件,并用以下代码替换它:
<ion-content class="home">
<div class="fullscreen-bg">
<video class="fullscreen-bg__video" autoplay loop
muted webkit-playsinline><source src="img/Blurry-People.mp4"
type='video/mp4; codecs="h.264"'><source src="img/> People.webm"
type="video/webm">
</video>
</div>
<div class="center animated zoomIn">
<h1>Beautiful App</h1>
<h2>Fast. Easy. Cheap.</h2>
</div>
</ion-content>
这个页面只有两个重要项目:视频和带有副标题的标题。
- 使用以下代码在同一文件夹中打开并编辑
./src/pages/home/home.scss文件:
page-home {
.home {
font-family: 'Lobster', cursive;
color: white;
text-shadow: 1px 0 0 gray, -1px 0 0 gray, 0 1px 0 gray, 0 -1px 0 gray, 1px 1px gray, -1px -1px 0 gray, 1px -1px 0 gray, -1px 1px 0 gray;
h1 {
font-size: 5rem;
}
}
.fullscreen-bg {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
z-index: -100;
}
.fullscreen-bg__video {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
.center {
top: 50%;
transform: translateY(-50%);
position: absolute !important;
text-align: center;
width: 100%;
}
}
所有动画都是使用 CSS 完成的;因此,你不需要为 JavaScript 文件编写任何代码。
- 打开
config.xml文件,并在<widget>标签内添加以下行:
<preference name="AllowInlineMediaPlayback" value="true"/>
- 前往你的终端,使用以下命令运行应用程序:
$ ionic serve
它是如何工作的...
让我们从home.html文件开始,因为这是唯一添加了动画的页面:
<video class="fullscreen-bg__video" autoplay loop muted
webkit-playsinline> <source src="img/Blurry-People.mp4"
type='video/mp4; codecs="h.264"' >
<source src="img/Blurry-People.webm" type="video/webm">
</video>
前面的标签只是一个典型的 HTML5 <video>标签。然而,有一个新的属性,称为webkit-playsinline。这意味着你希望视频在 HTML 页面上播放,而不是全屏播放并显示播放控制。原因是你想让这个视频在后台播放,同时你可以对上面的文本进行动画处理。这就是为什么你需要通过在config.xml中设置AllowInlineMediaPlayback来启用这个功能的原因。
这个模板中的第二项是你的标题和副标题,如下所示:
<div class="center animated zoomIn">
<h1>Beautiful App</h1>
<h2>Fast. Easy. Cheap.</h2>
</div>
注意,这里包含了animated和zoomIn类。这些是animate.css启动所必需的类。如果你现在运行应用程序,你会看到文本从较小的尺寸开始逐渐放大(即缩放效果)。
home.scss文件很重要,因为它包含大量的动画逻辑。让我们首先看看标题文本:
.home {
font-family: 'Lobster', cursive;
color: white;
text-shadow: 1px 0 0 gray, -1px 0 0 gray, 0 1px 0 gray,
0 -1px 0 gray, 1px 1px gray, -1px -1px 0 gray, 1px -1px
0 gray, -1px 1px 0 gray;
h1 {
font-size: 5rem;
}
}
这里有一个有趣的事情是使用了text-shadow属性。这是因为你想要在文本周围创建一个细的边框线,这样你的白色文本就可以在浅色背景上容易被看到。
要将视频设置为全屏,你需要给它一个负的索引,这样它就会位于其他层之下。此外,高度必须是 100%,如下所示:
.fullscreen-bg {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
z-index: -100;
}
.fullscreen-bg__video {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
最后,为了使文本垂直居中,你必须创建以下类:
.center {
top: 50%;
transform: translateY(-50%);
position: absolute !important;
text-align: center;
width:100%;
}
center类强制元素顶部为50%,然后推 Y 位置-50%以重置中间区域<div>标签的垂直中心点。你很少需要自定义这样的类;因此,保留center类以备将来使用是个好主意。
使用 Dynamics.js 创建基于物理的动画
使用基于物理的动画可以使您的应用程序更加互动和生动,这有助于吸引和保留更多用户。有许多方法可以将物理添加到组件动画中。例如,您甚至可以使用 CSS 的animation-timing函数来添加属性值,如ease-in、ease-out或cubic-bezier。然而,使用基于 JavaScript 的现有物理动画更容易且更好。Dynamics.js是那些带有实用工具和性能的 JavaScript 库之一。实际上,使用原生的 CSS 物理特性并不是一个好的做法,因为它在移动设备上会带来每秒帧数的惩罚。
应用程序将显示一个弹跳按钮,可以显示和隐藏顶部引用框,如下所示;它也使用了物理动画:
准备就绪
此应用程序示例可以在浏览器或物理设备上运行。然而,建议您通过物理设备运行应用程序以测试性能。
如何操作...
以下是指令:
- 打开一个终端窗口,使用
blank模板创建一个新的SpinningButton应用程序,并导航到SpinningButton文件夹:
$ ionic start SpinningButton blank
$ cd SpinningButton
- 使用以下命令使用
npm安装dynamics.js:
npm install dynamics.js --save
- 打开并编辑
./src/pages/home/home.html文件,用以下内容替换内容:
<ion-content class="home">
<div class="my-card" #myCard>
<h1>QUOTE</h1>
<p class="body">Always remember that you are absolutely unique.
Just like everyone else.</p>
<p class="name">Margaret Mead</p>
</div>
</ion-content>
<ion-fab center bottom>
<button ion-fab #thisEl (click)="animateMe(thisEl)">
<ion-icon name="mic"></ion-icon>
</button>
</ion-fab>
在此应用程序中不需要有标题导航,因为它将只是一个
单页。
- 打开
home.ts文件,在步骤 2 所在的文件夹中使用以下代码进行编辑:
import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import * as dynamics from 'dynamics.js';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private isAnimating: Boolean = false;
private isQuoteShown: Boolean = false;
@ViewChild('myCard') myCard;
constructor(public navCtrl: NavController) {
}
animateMe(el) {
if (!this.isAnimating) {
this.isAnimating = true;
dynamics.animate(el._elementRef.nativeElement, {
translateY: -50
}, {
type: dynamics.bounce,
duration: 1300,
complete: () => {
console.log('Done animating button.');
this.isAnimating = false;
}
});
if (!this.isQuoteShown) {
dynamics.animate(this.myCard.nativeElement, {
translateY: 0
}, {
type: dynamics.spring,
duration: 1300,
complete: () => {
console.log('Done animating drop down.');
this.isAnimating = false;
}
});
this.isQuoteShown = true;
} else {
dynamics.animate(this.myCard.nativeElement, {
translateY: -150
}, {
type: dynamics.easeOut,
duration: 900,
friction: 50,
complete: () => {
console.log('Done animating drop down.');
this.isAnimating = false;
}
});
this.isQuoteShown = false;
}
}
}
}
注意,您必须使用 ES6 导入语法导入dynamics。
- 修改
home.scss样式表,如下所示:
page-home {
ion-content.home {
background-color: #ecf0f1;
}
.my-card {
color: white;
transform: translate(0,-150px);
background: #9b59b6;
height: 150px;
padding: 10px;
h1 {
font-size: 4rem;
font-weight: 100;
margin: 0;
}
p {
color: white;
}
p.body {
font-size: 16px;
line-height: 1.5em;
margin-bottom: 0;
margin-top: 5px;
}
p.name {
font-size: 14px;
font-weight: bold;
text-align: right;
margin-top: 5px;
}
}
}
- 导航到您的终端,并使用以下命令运行应用程序:
$ ionic serve
它是如何工作的...
此应用程序中物理动画背后的主要概念是来自Dynamics.js库的dynamics.animate方法。让我们从模板中的按钮开始,如下所示:
<ion-fab center bottom>
<button ion-fab #thisEl (click)="animateMe(thisEl)">
<ion-icon name="mic"></ion-icon>
</button>
</ion-fab>
上述代码中提到的按钮是可以点击以通过调用animateMe()方法创建良好弹跳效果的浮动按钮。
要了解更多关于 Ionic 浮动按钮的信息,您可以参考 Ionic 文档ionicframework.com/docs/components/#floating-action-buttons。
这里的简单逻辑如下:
-
如果按钮有动画,
isAnimating必须为True。一旦为True,任何额外的点击都不会触发动画,因为我们不希望物理效果多次触发。 -
如果顶部引用栏显示,
isQuoteShown必须为True。否则,它将调用不同的动画来隐藏它。
您可以向dynamics.animate方法传递许多选项。例如,按钮将使用dynamics.bounce作为类型;因此,每次点击都会上下弹跳。您还可以指定应用于动画过程的持续时间。动画完成后,它将在complete函数中触发回调,如图所示:
dynamics.animate(el._elementRef.nativeElement, {
translateY: -50
}, {
type: dynamics.bounce,
duration: 1300,
complete: () => {
console.log('Done animating button.');
this.isAnimating = false;
}
});
需要记住的一个重要事项是 Dynamics.js 必须引用 DOM JavaScript 对象本身,而不是 DOM 节点或 Ionic 对象。这就是为什么你必须使用 el._elementRef.nativeElement 来指向原生元素对象。
对于引用框,它会在模板中创建一个名为 myCard 的局部变量,如下所示:
<div class="my-card" #myCard>
<h1>QUOTE</h1>
<p class="body">Always remember that you are absolutely unique.
Just like everyone else.</p>
<p class="name">Margaret Mead</p>
</div>
你必须像下面这样使用 ViewChild 装饰器来引用这个变量,这样 @Page 就知道将其作为依赖项包含:
@ViewChild('myCard') myCard;
参见
如果你想要了解更多关于原生 CSS 基于物理的动画,请访问 developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function。
通过绑定手势到动画状态来动画化幻灯片组件
从用户那里获得 哇 体验的另一种方式是拥有看起来很棒的介绍幻灯片。一个典型的应用会有三到五张幻灯片来描述应用的功能以及它将如何造福用户。今天,许多应用甚至添加了视频或交互式屏幕,以便用户可以感受到应用可能的工作方式。这样的交互式动画需要一些内部开发来将触摸手势绑定到动画状态。基于特定状态的动画非常困难,因为你真的需要获取粒度化的手势数据。另一方面,在状态的开头或结尾进行动画要容易得多。例如,你可以在幻灯片完全显示在屏幕上之后,通过左滑来动画化幻灯片内的对象。然而,这种动画效果并不像在触摸移动过程中绑定动画那样有趣或吸引人。
在本节中你将构建的应用将包含三个幻灯片,当你左右滑动时它们会进行动画:

你将看到幻灯片之间的淡入和淡出动画效果;当从第二张幻灯片向左滑动时,下面的 Angular 标志也会向上移动:

准备工作
由于动画是通过 HTML 和 JavaScript 完成的,因此不需要在物理设备上测试应用。然而,建议你在设备上测试应用以评估动画性能。
如何做到这一点...
以下是指令:
- 使用
blank模板创建一个新的SliderAnimation应用,如下所示,并进入SliderAnimation文件夹:
$ ionic start SliderAnimation blank
$ cd SliderAnimation
- 打开
./src/pages/home/home.html文件,并使用以下代码修改其内容:
<ion-content class="home">
<div class="slides-float">
<div class="slide-float" #slidefloat1>
<ion-icon name="ios-ionic"></ion-icon>
<h1>Ionic</h1>
</div>
<div class="slide-float" #slidefloat2>
<ion-icon name="logo-angular"></ion-icon>
<h1>Angular</h1>
</div>
<div class="slide-float" #slidefloat3>
<ion-icon name="logo-javascript"></ion-icon>
<h1>Both</h1>
</div>
</div>
<ion-slides #myslides pager (ionSlideDrag)="onMove()">
<ion-slide>
<h2>is Beautiful</h2>
</ion-slide>
<ion-slide>
<h2>is Fast</h2>
</ion-slide>
<ion-slide>
<h2>are Awesome</h2>
</ion-slide>
</ion-slides>
</ion-content>
上述模板主要使用 <ion-slides> 标签。然而,有一些层浮在 <ion-slide> 标签之上,以便分别对它们进行动画:
- 然后,将
./src/pages/home/home.ts的内容替换为以下代码:
import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
@ViewChild('myslides') myslides;
@ViewChild('slidefloat1') slidefloat1;
@ViewChild('slidefloat2') slidefloat2;
@ViewChild('slidefloat3') slidefloat3;
private rAf: any;
private bindOnProgress: boolean = false;
constructor(public navCtrl: NavController) {
this.rAf = (function () {
return (window as any).requestAnimationFrame || (window as
any).webkitRequestAnimationFrame || (window as
any).mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
}
onMove() {
if (!this.bindOnProgress) {
this.bindOnProgress = true;
this.myslides.ionSlideProgress
.subscribe(progress => {
// (0, 1) - (0.25, 0) ==> (0-1)/(0.25-0) => -1/0.25 * x + 1
let firstQuarter = () => {
let slidefloat1Opacity = -1 / 0.25 * progress + 1;
console.log('slidefloat1Opacity: ' + slidefloat1Opacity);
this.slidefloat1.nativeElement.style.opacity = slidefloat1Opacity;
this.slidefloat2.nativeElement.style.opacity = 0;
}
// (0.25, 0) - (0.5, 1) ==> (1-0)/(0.5-0.25) => 1 / 0.25 * x - 1 = 4
* x - 1
let secondQuarter = () => {
let slidefloat2Opacity = 4 * progress - 1;
console.log('slidefloat2Opacity: ' + slidefloat2Opacity);
this.slidefloat2.nativeElement.style.opacity = slidefloat2Opacity;
this.slidefloat2.nativeElement.style.transform = 'translateY(0px)';
this.slidefloat1.nativeElement.style.opacity = 0;
}
// (0.5, 0) - (0.75, -250) ==> (-250-0)/(0.75-0.5) = -250 / 0.25 =>
-1000 * x + 500
let thirdQuarter = () => {
let slidefloat2transform = -1000 * progress +500;
console.log('slidefloat2transform: ' + slidefloat2transform);
this.slidefloat2.nativeElement.style.transform = 'translateY(' +
slidefloat2transform + 'px)';
this.slidefloat3.nativeElement.style.opacity = 0;
}
// (0.75, 0) - (1, 1) ==> (1-0)/(1-0.75) => 1/0.25 * x - 0.75 * 4 = 4
* x - 3
let fourthQuarter = () => {
let slidefloat3Opacity = 4 * progress - 3;
console.log('slidefloat3Opacity: ' + slidefloat3Opacity);
this.slidefloat3.nativeElement.style.opacity = slidefloat3Opacity;
this.slidefloat2.nativeElement.style.transform =
'translateY(-250px)';
}
// Animate per quarter of the total 3 slides
if (progress <= 0.25) {
this.rAf(firstQuarter);
} else if ((progress > 0.25) && (progress <= 0.5)) {
this.rAf(secondQuarter);
} else if ((progress > 0.5) && (progress <= 0.75)) {
this.rAf(thirdQuarter);
} else if ((progress > 0.75) && (progress <= 1)) {
this.rAf(fourthQuarter);
}
});
}
}
}
注意,注释用于计算每个对象的动画公式。
- 使用以下代码编辑
./app/pages/home/home.scss:
page-home {
.slides-float {
.slide-float {
top: 0;
position: fixed;
width: 100%;
margin-top: 20px;
opacity: 0;
}
}
.home {
background-color: DarkSlateBlue;
h2 {
font-size: 3rem;
}
ion-slide {
color: white;
background-color: transparent;
}
.slides-float {
color: white;
text-align: center;
:first-child {
opacity: 1;
}
}
.slide-float {
ion-icon {
font-size: 150px;
}
h1 {
font-weight: lighter;
font-size: 60px;
margin-top: 0;
}
}
}
}
- 打开你的终端,并使用以下命令运行应用:
$ ionic serve
它是如何工作的...
以下是对动画的一般过程:
-
由于有三个幻灯片,用户必须滑动两次才能到达末尾。这意味着第一次滑动将在 50% 的进度。
-
当用户滑动到 25% 时,Ionic 标志将淡出。
-
当用户滑动到 50% 时,Angular 标志将淡入以显示第二个幻灯片。
-
当用户滑动到 75% 时,Angular 标志将向上移动以消失,而不是淡出。
-
最后,在最后 75% 到 100% 的过程中,JavaScript 标志将淡入。
您可能已经注意到,淡入或移动的量将取决于进度百分比。因此,如果您稍微左右滑动,您可以直接看到动画对手势的反应。模板中有两层。如上图所示,浮动的静态层必须位于顶部,并且无论当前显示的幻灯片是什么,它都必须保持在同一位置:
<div class="slides-float">
<div class="slide-float" #slidefloat1>
<ion-icon name="ios-ionic"></ion-icon>
<h1>Ionic</h1>
</div>
<div class="slide-float" #slidefloat2>
<ion-icon name="logo-angular"></ion-icon>
<h1>Angular</h1>
</div>
<div class="slide-float" #slidefloat3>
<ion-icon name="logo-javascript"></ion-icon>
<h1>Both</h1>
</div>
</div>
底层是您典型的 <ion-slides>:
<ion-slides #myslides pager (ionSlideDrag)="onMove()">
<ion-slide>
<h2>is Beautiful</h2>
</ion-slide>
<ion-slide>
<h2>is Fast</h2>
</ion-slide>
<ion-slide>
<h2>are Awesome</h2>
</ion-slide>
</ion-slides>
当您滑动时,实际上是在移动 <ion-slide>。然而,它也会触发 onMove() 方法,因为您将其与 move 事件绑定。onMove() 方法将访问浮动 <div> 层中的 #slidefloat1、#slidefloat2 和 #slidefloat3。home.ts 文件是您必须对这些单个浮动幻灯片进行动画的地方。
在 home.ts 文件中,您需要声明几个变量。您将需要能够访问 <ion-slides> 对象,以便调用 原生 的 Swiper 方法:
@ViewChild('myslides') myslides;
根据 Ionic 文档,<ion-slides> 对象是基于 Swiper 库编写的;您可以在 ionicframework.com/docs/v2/api/components/slides/Slides/ 找到更多信息。
您需要原生地将其与滑动事件绑定,以便获取正确的进度数据。
以下三个变量是访问每个浮动幻灯片所必需的:
@ViewChild('slidefloat1') slidefloat1;
@ViewChild('slidefloat2') slidefloat2;
@ViewChild('slidefloat3') slidefloat3;
您需要利用 requestAnimationFrame,如下所示,以获得最佳动画性能:
private rAf: any;
否则,用户在滑动过程中会感觉到 颠簸 的运动,因为您的动画不是 60 FPS。
最后,您只需要绑定一次滑动事件;因此,有必要有一个布尔切换来检测绑定事件:
private bindOnProgress: boolean = false;
以下代码显示了如何创建一个 requestAnimationFrame 对象来调用稍后要渲染的任何函数:
this.rAf = (function(){
return (window as any).requestAnimationFrame || (window as
any).webkitRequestAnimationFrame || (window as
any).mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
onMove() 方法是放置所有动画逻辑的地方,它必须订阅 ionSlideProgress 可观察对象,如下所示:
this.myslides.ionSlideProgress
.subscribe(progress => {
...
});
首先,让我们看一下 onMove() 函数底部的代码,如下所示:
if (progress <= 0.25) {
this.rAf(firstQuarter);
} else if ((progress > 0.25) && (progress <= 0.5 )) {
this.rAf(secondQuarter);
} else if ((progress > 0.5) && (progress <= 0.75 )) {
this.rAf(thirdQuarter);
} else if ((progress > 0.75) && (progress <= 1 )) {
this.rAf(fourthQuarter);
}
基本上,你将想要有四个四分之一(或部分)的动画。当你从幻灯片 1 滑动到幻灯片 2 时,它将触发 firstQuarter 和 secondQuarter 方法。也就是说,你希望在过程结束时淡出第一个浮动幻灯片并淡入第二个浮动幻灯片。这个概念与 thirdQuarter 和 fourthQuarter 方法类似。请注意,你不想直接调用该方法,而是只需将函数引用传递给 this.rAf 中的函数,以便渲染引擎管理帧率。否则,渲染的函数可能会阻塞 UI 中的其他进程,导致运动不流畅。
对于每个四分之一,你只需要更改 style 属性,给定一个已知的 progress 值,如下所示:
let firstQuarter = () => {
let slidefloat1Opacity = -1/0.25 * progress + 1;
console.log('slidefloat1Opacity: ' + slidefloat1Opacity);
this.slidefloat1.nativeElement.style.opacity =
slidefloat1Opacity;
this.slidefloat2.nativeElement.style.opacity = 0;
}
在这里使用箭头函数很重要,这样你才能访问 this 上下文。你必须调用 this.slidefloat2.nativeElement 来获取到 <div> DOM 对象。实际上,编写你自己的 math 函数来计算在滑动过程中使用进度值的位置或透明度完全取决于你。在这个例子中,slidefloat1Opacity 变量仅基于 progress 输入值的线性函数。
secondQuarter 遵循相同的做法。然而,thirdQuarter 使用 transform 属性而不是 opacity,如下所示:
let thirdQuarter = () => {
let slidefloat2transform = -1000 * progress + 500;
console.log('slidefloat2transform: ' + slidefloat2transform);
this.slidefloat2.nativeElement.style.transform = 'translateY(' +
slidefloat2transform + 'px)';
this.slidefloat3.nativeElement.style.opacity = 0;
}
有许多方法可以使 DOM 对象改变其位置。然而,最好利用 transform 属性而不是使用 left 和 top 属性。你希望达到最高的每秒帧数。在 thirdQuarter 方法中,你的 slidefloat2transform 将被计算,并且它将使用 translateY() 更新一个新的 Y 位置。
注意,你必须使用 this.bindOnProgress 来禁用另一个事件绑定到 onProgress,因为对于每次滑动,它都会继续添加更多的事件。
参见
-
要了解更多关于
requestAnimationFrame的信息,你可以查看官方文档developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame。 -
Swiper API 位于
idangero.us/swiper/api/。 -
ionic 在
ionicframework.com/docs/components/#slides提供了官方的使用示例。 -
ionic 还在
ionicframework.com/docs/api/components/slides/Slides/提供了有限的幻灯片 API。
在登录页面添加背景 CSS 动画
动画也可以完全使用 CSS 完成。在许多情况下,你可能会在网上遇到一些有趣的演示,并希望将 CSS-only 代码用于动画。如果动画对用户体验不是那么关键,你只需使用它来为应用添加额外的效果。CSS 动画很棒,因为你不需要编写 JavaScript 代码来管理动画,只需让浏览器处理它即可。
在本节中,你将构建一个应用,在登录页面的背景中显示一些漂浮的方块,如下所示:

准备工作
由于 CSS 动画在 Ionic 应用中运行良好,无需在物理设备上测试。
如何操作...
以下是指令:
- 使用
blank模板创建一个新的BubbleLogin应用,如下所示,并导航到BubbleLogin文件夹:
$ ionic start BubbleLogin blank
$ cd BubbleLogin
- 打开
./src/pages/home/home.html文件,并使用以下代码修改内容:
<ion-content #myContent class="home">
<ul class="bg-bubbles">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<ion-list>
<ion-item>
<ion-label>Username</ion-label>
<ion-input type="text"></ion-input>
</ion-item>
<ion-item class="input-password">
<ion-label>Password</ion-label>
<ion-input type="password"></ion-input>
</ion-item>
</ion-list>
<div padding>
<button ion-button block round color="secondary">LOGIN</ button>
</div>
<p class="logo">
<ion-icon name="ios-chatbubbles"></ion-icon>
</p>
</ion-content>
bg-bubbles类将一个<li>列表转换为漂浮的方块片段。
- 使用以下代码编辑
./src/pages/home/home.scss:
page-home {
.home {
background-color: SeaGreen;
.logo {
margin: 0;
color: white;
font-size: 100px;
text-align: center;
}
scroll-content {
overflow-y: hidden;
}
.item {
background-color: transparent;
}
.item-input ion-label, .item-select ion-label, input.text-
input {
color: white;
}
ion-list > .item:first-child {
border-top: 0;
border-bottom: 1px solid white;
}
ion-list > .item:first-child .item-inner {
margin-right: 8px;
}
ion-list .item-inner {
border-bottom: 0;
}
.input-password {
border-bottom: 1px solid white!important;
item-inner {
border-bottom: 1px solid white;
margin-right: 8px;
}
}
}
.bg-bubbles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
li {
position: absolute;
list-style: none;
display: block;
width: 40px;
height: 40px;
background-color: black;
opacity: 0.2;
bottom: -160px;
-webkit-animation: square 25s infinite;
animation: square 25s infinite;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
&:nth-child(1) {
left: 10%;
}
&:nth-child(2) {
left: 20%;
width: 80px;
height: 80px;
animation-delay: 2s;
animation-duration: 17s;
}
&:nth-child(3) {
left: 25%;
animation-delay: 4s;
}
&:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
animation-duration: 22s;
background-color: black;
}
&:nth-child(5) {
left: 70%;
}
&:nth-child(6) {
left: 80%;
width: 120px;
height: 120px;
animation-delay: 3s;
background-color: black;
}
&:nth-child(7) {
left: 32%;
width: 160px;
height: 160px;
animation-delay: 7s;
}
&:nth-child(8) {
left: 55%;
width: 20px;
height: 20px;
animation-delay: 15s;
animation-duration: 40s;
}
&:nth-child(9) {
left: 25%;
width: 10px;
height: 10px;
animation-delay: 2s;
animation-duration: 40s;
background-color: black;
}
&:nth-child(10) {
left: 90%;
width: 160px;
height: 160px;
animation-delay: 11s;
}
}
}
@-webkit-keyframes square {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-700px) rotate(600deg);
}
}
@keyframes square {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-700px) rotate(600deg);
}
}
}
- 打开终端,使用以下命令运行应用:
$ ionic serve
它是如何工作的...
由于这个应用不使用 JavaScript 进行动画,你不需要在home.ts中做任何修改。
CSS 将通过以下代码无限驱动动画:
animation: square 25s infinite;
transition-timing-function: linear;
你还将使用square关键帧中的两个点:
@keyframes square {
0% { transform: translateY(0); }
100% { transform: translateY(-700px) rotate(600deg); }
}
因此,对于0%到100%的循环,它将在持续时间内垂直移动700像素并旋转600度。
每个方块具有不同的大小和速度的原因是你可以根据<li>标签进一步自定义 CSS。考虑以下示例:
&:nth-child(2) {
left: 20%;
width: 80px;
height: 80px;
animation-delay: 2s;
animation-duration: 17s;
}
由于这个动画不会生成随机数量的方块对象,并且对象数量有限,你可以在 CSS 中为每个<li>标签编写自定义代码。
注意,你必须将动画设置为z-index: 0,因为它将保持在其他层之上,例如表单和按钮。
参见
要了解更多关于 CSS 关键帧的信息,你可以查看 Mozilla 文档,网址为developer.mozilla.org/en-US/docs/Web/CSS/@keyframes。
第六章:用户身份验证和推送通知
在本章中,我们将涵盖与身份验证用户、注册和接收推送通知消息相关的以下任务:
-
使用 Auth0 注册和验证用户
-
构建一个 iOS 应用以接收推送通知
-
构建一个 Android 应用以接收推送通知
简介
跟踪和吸引用户是您应用增长的关键功能。这意味着您应该能够注册和验证用户。一旦用户开始使用应用,您还需要对用户进行细分,以便您可以定制他们的互动。然后,您可以发送推送通知来鼓励用户重新访问应用。
您的项目需要使用以下两个组件:
-
Auth0:Auth0 是一个基于云的身份验证服务。整个想法是将您应用的认证委托给 Auth0。Auth0 支持许多框架,包括 Ionic2+,并且支持许多用于身份验证的社会提供者,如 Google 和 Facebook。更重要的是,他们拥有优秀的文档。
-
OneSignal:OneSignal 是一个服务,允许我们向 iOS 和 Android 发送推送通知。实际上,它支持其他平台,如 Windows、Web 推送通知、Amazon Fire 等。最好的部分是,通常我们为添加推送通知需要不同的代码。但由于 Cordova 和 OneSignal 的抽象 API,我们只需为两个平台编写一次推送通知代码。
使用 Auth0 注册和验证用户
Auth0可以提供开箱即用的用户管理和身份验证功能。Auth0 支持许多提供者。以下是 Auth0 支持的某些著名提供者:
-
电子邮件/密码
-
Facebook
-
Google
-
Twitter
-
Instagram
-
LinkedIn
-
GitHub
根据应用的不同,您可能不需要使用所有这些身份验证方法。例如,对于专注于职业人士的应用,使用 LinkedIn 身份验证可能更有意义,以缩小符合应用用户画像的受众范围。
在本章中,我们将尽可能简化身份验证概念。您将学习如何执行以下操作:
-
注册新用户
-
用户登录和登出
-
显示用户的个人资料信息
准备工作
您需要设备来测试应用,因为我们使用 Cordova 插件进行身份验证,这需要设备或模拟器来运行应用。
如何操作...
我们将有两种方法:
-
在 Auth0 仪表板中创建应用
-
编写我们的 Ionic 应用
在 Auth0 仪表板中创建我们的应用
在本节中,我们将学习如何在 Auth0 仪表板中创建应用。请按照以下步骤操作:
-
前往
auth0.com并创建账户。 -
创建账户后,它将打开 Auth0 仪表板。有一个“新建应用”按钮。点击它,将打开以下对话框
为你的客户端选择一个名称,并将客户端类型选择为 Native。 -
现在,在侧边菜单仪表板中,点击“应用”并选择你创建的应用。你将看到以下页面:
在应用中稍后需要域名和客户端 ID,所以请将它们保存在某个地方。 -
在同一页面上,在“允许的回调 URL”输入中添加
YOUR_PACKAGE_ID://YOUR_DOMAIN/cordova/YOUR_PACKAGE_ID/callback。将YOUR_PACKAGE_ID替换为你的应用包 ID,*YOUR_DOMAIN*是你在上一步中保存的域名。 -
此外,在“允许的来源(CORS)”输入中添加
file://。
使用最新的 Ionic,他们默认使用 WKWebView 插件,该插件从本地 Web 服务器 localhost:8080 为应用提供服务。因此,你也需要在“允许的来源(CORS)”部分中添加它。如果你不打算使用 WKWebView,则无需添加。
- 点击“保存更改”。
让我们开始编码
既然我们已经在 Auth0 仪表板中创建了我们的应用,现在是时候为其编写代码了。请按照以下步骤操作:
- 现在,使用
blank模板创建一个新的MySimpleAuth应用,如下所示,并进入MySimpleAuth文件夹:
$ ionic start MySimpleAuth blank $ cd MySimpleAuth
- 安装
auth0.js和auth0/cordovanpm 包,这些包是 Auth0 认证所必需的:
$ npm install auth0-js @auth0/cordova --save
- 我们还需要安装以下
Cordova插件:
$ ionic cordova plugin add cordova-plugin-safariviewcontroller
$ ionic cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME={YOUR_PACKAGE_ID} --variable ANDROID_SCHEME={YOUR_PACKAGE_ID} --variable ANDROID_HOST={YOUR_DOMAIN} --variable ANDROID_PATHPREFIX=/cordova/{YOUR_PACKAGE_ID}/callback
你应该将 {YOUR_PACKAGE_ID} 替换为你的应用包 ID,并将 {YOUR_DOMAIN} 替换为你在第三步中保存的 Auth0 域名。
- 你还必须在
config.xml中添加以下行:
<preference name="AndroidLaunchMode" value="singleTask" />
- 在
./src/providers/auth/auth.ts创建一个文件,并使用以下代码:
import { Injectable, NgZone } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import Auth0Cordova from '@auth0/cordova';
import Auth0 from 'auth0-js';
const auth0Config = {
// needed for auth0
clientID: 'sIQavE9jev8VXOTQkeb2Cn62m9s9faLN',
// needed for auth0cordova
clientId: 'sIQavE9jev8VXOTQkeb2Cn62m9s9faLN',
domain: 'imtest.auth0.com',
callbackURL: location.href,
packageIdentifier: 'io.ionic.imtest'
};
@Injectable()
export class AuthProvider {
auth0 = new Auth0.WebAuth(auth0Config);
accessToken: string;
idToken: string;
user: any;
constructor(public zone: NgZone) {
this.user = this.getStorageVariable('profile');
this.idToken = this.getStorageVariable('id_token');
}
private getStorageVariable(name) {
return JSON.parse(window.localStorage.getItem(name));
}
private setStorageVariable(name, data) {
window.localStorage.setItem(name, JSON.stringify(data));
}
private setIdToken(token) {
this.idToken = token;
this.setStorageVariable('id_token', token);
}
private setAccessToken(token) {
this.accessToken = token;
this.setStorageVariable('access_token', token);
}
public isAuthenticated() {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return Date.now() < expiresAt;
}
public login() {
const client = new Auth0Cordova(auth0Config);
const options = {
scope: 'openid profile offline_access'
};
client.authorize(options, (err, authResult) => {
if(err) {
throw err;
}
this.setIdToken(authResult.idToken);
this.setAccessToken(authResult.accessToken);
const expiresAt = JSON.stringify((authResult.expiresIn * 1000)
+
new Date().getTime());
this.setStorageVariable('expires_at', expiresAt);
this.auth0.client.userInfo(this.accessToken, (err, profile)=>
{
if(err) {
throw err;
}
profile.user_metadata = profile.user_metadata || {};
this.setStorageVariable('profile', profile);
this.zone.run(() => {
this.user = profile;
});
});
});
}
public logout() {
window.localStorage.removeItem('profile');
window.localStorage.removeItem('access_token');
window.localStorage.removeItem('id_token');
window.localStorage.removeItem('expires_at');
this.idToken = null;
this.accessToken = null;
this.user = null;
}
}
确保在 auth.ts 中的 auth0Config 使用你自己的 clientID、domain 和 packageIdentifier 值。
- 打开并编辑
./src/app/app.module.ts,使用以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AuthProvider } from '../providers/auth/auth';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler},
AuthProvider
]
})
export class AppModule {}
- 编辑并替换
./src/pages/home/home.html为以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Home Page
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<div *ngIf="!auth.isAuthenticated()">
<button ion-button block color="primary" (click)="auth.login()">Log In</button>
</div>
<div *ngIf="auth.isAuthenticated()">
<ion-card>
<img [src]="auth.user.picture" />
<ion-card-content>
<ion-card-title>{{ auth.user.name }}</ion-card-title>
</ion-card-content>
</ion-card>
<button ion-button block color="primary"
(click)="auth.logout()">Logout</button>
</div>
</ion-content>
这些只是基本的 login 和 logout 模板。所有内容都在一个页面上,以保持简单。
- 打开并编辑
./src/pages/home/home.ts,使用以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { AuthProvider } from '../../providers/auth/auth';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public auth: AuthProvider) {
}
}
- 然后在设备上使用以下 CLI 命令运行此命令:
$ ionic cordova run android
工作原理
在深入代码之前,有一些事情我们需要了解。
Auth0 使用JWT(JSON Web Token),这是一种通过 JSON 在双方之间共享信息的紧凑方式。简单来说,当用户认证成功后,Auth0 会发送 JWT 给我们,其中包含有关用户的信息,并允许用户访问受保护的路线/URL。Auth0 会发送一个access_token,这是访问受保护路线所必需的,它还会发送一个id_token,其中包含用户的个人资料信息,如用户名、个人资料图片等。这两个令牌都有短暂的生存期,然后它们会过期。但是,除了这些,Auth0 还会发送一个refresh_token,它有一个较长的有效期,可以用来获取新的id_token和access_token。
我们在我们的应用中配置了回调 URL。这是 Auth0 在认证后重定向用户的 URL。回调 URL 包括应用的包 ID,这就是为什么在安装插件时我们需要提到它。我们还需要在CORS(跨源资源共享)中添加file://,因为 Ionic 应用会从file://源发起 HTTP 请求。
如果您正在使用 Ionic 的 WKWebView。它会在应用内部运行一个本地 web 服务器。因此,您必须在 Auth0 仪表板中将 http://localhost:8080 列入 CORS 白名单。
大部分工作都在AuthService中。AuthService 允许我们登录/登出。它使用auth0.js和auth0/cordova库进行认证。首先,我们创建了一个auth0Config对象。这个对象看起来如下所示:
const auth0Config = {
clientID: ''
clientId: '',
domain: '',
callbackURL: location.href,
packageIdentifier: ''
};
-
在前面的代码中,您可以看到
clientID和clientId具有相同的值。这就是我们之前保存的值。前者由auth0.js使用,后者由auth0/cordova使用。 -
domain也是我们用于认证的Auth0 域。我们在创建 Auth0 仪表板上的应用时已经保存了它。 -
callbackURL将始终是location.href。 -
packageIdentifier是您应用的包 ID,与您的config.xml中相同。
然后,我们将这个配置传递给Auth0.WebAuth构造函数和Auth0Cordova构造函数。
在login函数中,我们通过调用 Auth0Cordova 的authorize函数来启动认证。需要注意的是,我们将带有scope键的option对象作为参数传递给authorize函数。这个scope键告诉 Auth0 在认证完成后返回某些数据,如电子邮件和配置文件。我们还传递了一个回调函数作为第二个参数,该函数在认证完成后触发。当启动认证时,Auth0Cordova 打开操作系统浏览器并将我们重定向到我们的 Auth0 域。在这里,用户可以登录和注册。默认情况下,应用已配置为电子邮件/密码认证。但您也可以启用 Google、Facebook 和 GitHub 认证,它们也会与电子邮件/密码认证一起出现。当用户认证成功后,浏览器通过自定义 URL 方案将我们重定向到我们的应用。然后,我们在localStorage中存储idToken和accessToken,并使用auth0.client.userInfo函数获取配置文件信息。然后在登录方法中,我们也将这些信息保存到localStorage中。
在logout函数中,我们只是从 localStorage 中删除idToken、accessToken、令牌过期信息和配置文件数据,并重置 AuthService 类。
在home.html中,当用户未认证时,我们显示一个登录按钮;当用户认证后,我们显示用户的配置文件图片和用户名,以及 LOGOUT 按钮。这些在home.ts中的login和logout方法分别调用AuthService的登录和注销函数。
这是用户未认证时我们的应用看起来是这样的:

当用户点击时,它打开一个用于认证用户的网页。它看起来如下:

这是用户认证后我们的应用看起来是这样的:

还有更多...
您可以使用 Auth0 来保护您的自定义后端,并使用angular2-jwt库在 Ionic 应用中访问后端。请参阅github.com/auth0/angular2-jwt。
构建 iOS 应用以接收推送通知
推送通知是一个重要的功能,可以频繁地吸引用户,尤其是在用户没有使用应用的时候。许多人下载了应用,但只打开了几次。如果您向他们发送推送通知消息,这将鼓励他们打开应用参与新的活动。如果您必须从头开始构建一切,实现推送通知非常复杂;然而,OneSignal 使它变得非常简单。推送通知提供者是一个可以与苹果推送通知服务(APNs)或谷歌的Firebase 云消息(FCM)通信的服务器。您可以使用现有的开源软件设置自己的提供者服务器,但您必须单独维护此服务器并跟上 APN API 的潜在变化。
在本节中,您将学习以下内容:
-
为 iOS 推送通知设置 OneSignal
-
配置 iOS 应用、证书(应用和推送)以及配置文件
-
编写代码以接收推送通知
准备工作
为了测试通知消息,需要有一个可用的物理 iOS 设备。
您还必须注册苹果开发者计划(ADP),以便访问developer.apple.com和itunesconnect.apple.com,因为这些网站将需要经过批准的账户。
此外,您还需要拥有苹果 Mac 和已安装的 Xcode。
如何操作
您需要设备来查看推送通知。我们将分多步进行:
-
创建苹果签名证书
-
添加设备和创建配置文件
-
为 OneSignal 仪表板创建推送证书
-
在 OneSignal 仪表板中配置应用
-
编写应用程序代码
让我们创建一个苹果签名证书
按照说明创建苹果签名证书:
-
访问苹果开发者网站
developer.apple.com并使用您的凭据登录。 -
点击证书、标识符和配置文件,如图所示:
![]()
-
选择您要针对的正确设备平台。在本例中,它将是 iOS、tvOS、watchOS,如下面的截图所示:
![]()
-
导航到标识符 | 应用 ID以创建应用 ID,如图所示:

- 点击屏幕右上角的加号(+),如图所示:

- 填写表格以注册您的应用 ID。
名称字段可以是任何内容。您可以为您的项目(即MyiOSPush)提供名称以保持简单,如图所示:

- 在这里您需要正确完成的重要部分是包标识符,因为它必须与
./config.xml文件或 Xcode 中的包标识符匹配,如图所示:

- 要启用推送通知,您需要在以下页面上的推送通知服务上勾选:

- 选择注册,如图所示:

- 选择完成以完成创建应用 ID 的步骤,如下所示:

- 要开始创建证书,您需要在您的 Mac OSX 上使用钥匙串访问生成证书签名请求文件。在左上角菜单中导航到钥匙串访问,然后导航到证书助手 | 从证书颁发机构请求证书...,如图所示:

-
输入您的用户电子邮件地址和通用名称。留空 CA 电子邮件地址字段并勾选保存到磁盘,如图所示:
![输入信息]()
-
保存您的
CertificateSigningRequest.certSigningRequest文件,如下所示:![保存文件]()
-
导航到苹果开发者网站,然后导航到证书 | 所有,如图所示:
![苹果开发者网站]()
-
在右上角点击加号(+)按钮以开始创建证书,
如下所示:
![示例]()
-
现在,您只需按照网站上的步骤填写必要的信息。在此示例中,您将选择开发版本而不是生产版本,如图所示:
![选择开发版本]()
-
点击继续按钮,操作如下:
![继续按钮]()
-
点击如图所示的“选择文件...”按钮,上传您之前保存的签名请求文件:
![选择文件按钮]()
-
点击如图所示的继续按钮,以继续操作:
![继续按钮]()
-
点击下载按钮以下载您的 iOS 开发证书文件:
![下载按钮]()
-
点击您下载的
.cer文件,如图所示,以便将其导入钥匙串访问:![导入证书]()
非常重要的是要记住,您必须在 Mac 上安装签名证书,因为当您构建应用时,它将用于签名应用。只需双击下载的.cer文件即可安装它。
添加设备和创建配置文件
-
如果您需要将应用推送到特定设备,您必须注册该设备。转到设备 | 所有设备:
![设备注册]()
-
点击加号(+)按钮:
![加号按钮]()
-
为设备提供一个 UDID 并保存以注册设备。观察以下截图:
![]()
-
你需要一个配置文件。导航到配置文件 | 所有:
![]()
-
点击加号(+)按钮:
![]()
-
选择 iOS 应用开发作为你的配置文件,因为此示例仅针对开发版本:
-
点击继续按钮:

- 在下拉菜单中选择正确的应用程序标识符,并保存以完成配置文件创建:

- 点击继续按钮:

- 选择你之前创建的 iOS 开发证书,如图所示:

- 如上图所示,选择至少一个你想要安装应用程序进行测试的设备:

-
为你的配置文件提供一个配置文件名称,如图所示:
![]()
-
点击下载按钮以下载配置文件文件(即
MyiOSPush_Provisioning_Profile.mobileprovision):![]()
-
点击你刚刚下载的
MyiOSPush_Provisioning_Profile.mobileprovision,以便将其导入到 Xcode 中:![]()
此步骤非常重要,因为如果你没有将其导入到 Xcode 中,你的应用程序将无法成功构建。如果你的应用程序因为无效的配置文件而无法构建,最好在开发者控制台中检查配置文件的状态。
创建推送证书
按照步骤为 iOS 应用程序创建推送证书:
-
要启用推送通知功能,你必须请求推送证书,这与应用程序证书不同。选择你之前创建的应用程序标识符(即
MyiOSPush):![]()
-
点击页面底部的编辑按钮:
![]()
推送通知必须显示可配置状态。否则,你的应用程序将无法使用推送通知。
- 在 推送通知 | 开发 SSL 证书部分下点击创建证书...按钮:

- 你将被带到新页面以创建你的 CSR 文件。点击继续按钮:

- 点击选择文件...按钮:

- 定位你之前创建的
CertificateSigningRequest.certSigningRequest文件:

您必须上传与应用证书相同的 .certSigningRequest 文件。否则,您的应用将无法接收推送通知,调试起来非常困难。
- 点击继续按钮:

- 点击下载按钮下载证书文件。您可以将其命名为
aps_certificate.cer以避免覆盖之前的.cer文件:

- 下载完
.cer文件后,您需要点击它将其导入到 钥匙串访问:

- 在钥匙串访问中找到新的推送服务证书并选择它,如图所示:

-
右键单击证书并选择导出:
![]()
-
给它一个新的名称以避免覆盖应用证书。这个过程基本上是将
.cer文件转换为.p12文件用于 OneSignal:

- 为此
.p12文件提供密码以保护它:![]()
对于 OneSignal,.p12 文件的密码不是必需的,但出于最佳安全考虑,最好保护它。
现在,让我们配置 OneSignal
按照以下步骤配置 OneSignal 以发送推送通知:
-
访问
onesignal.com并创建一个账户。 -
在仪表板中,点击“添加新应用”。您将看到以下对话框:

- 填入您想要的名称并点击创建。它将打开以下对话框:

- 选择 Apple iOS 并点击下一步。您将看到以下内容:

-
选择上传可选沙盒证书并上传您之前创建的 推送证书 的
.p12文件。同时,填写.p12文件的密码。然后点击保存。 -
现在在顶部主菜单中,点击“密钥与 ID”。您将看到 OneSignal 应用 ID,如下所示:
![]()
将 OneSignal 应用 ID 记录下来;我们需要这个信息来配置我们的应用。
让我们开始编码
按照以下步骤创建示例应用:
- 现在,使用
blank模板创建一个新的MyiOSPush应用,如图所示,并转到MyiOSPush文件夹:
$ ionic start MyiOSPush blank
$ cd MyiOSPush
- 安装 Cordova 插件和 Ionic Native 包装器以支持 OneSignal:
$ ionic cordova plugin add onesignal-cordova-plugin
$ npm install --save @ionic-native/onesignal
我们需要将此 ionic-native 插件添加到 app.module.ts。
- 我们还需要安装
cocoapods。转到 终端,并按照以下方式安装:
sudo gem install cocoapods
pod repo update
- 使用以下内容打开并编辑
./src/app/app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { OneSignal } from '@ionic-native/onesignal';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
OneSignal,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 您需要修改主页代码以接收通知消息。打开并编辑
./src/pages/home/home.html并粘贴给定的代码:
<ion-header>
<ion-navbar>
<ion-title>
Push Notification
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2 class="big-square" *ngIf="!this.messages.length">
You have no message
</h2>
<h3 class="sub-title" *ngIf="!!this.messages.length">
Your messages
</h3>
<ion-card *ngFor="let msg of messages">
<ion-card-header>
{{ msg.title }}
</ion-card-header>
<ion-card-content>
{{ msg.text }}
</ion-card-content>
</ion-card>
</ion-content>
- 将同一文件夹中的
home.ts文件的内容替换为以下代码:
import { Component, ChangeDetectorRef } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { OneSignal } from '@ionic-native/onesignal';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
public messages = [];
public clicked: Boolean = false;
constructor(public navCtrl: NavController, public oneSignal: OneSignal, platform: Platform, private changeDetector: ChangeDetectorRef) {
platform.ready().then(() => {
this.oneSignalConfig();
});
}
oneSignalConfig() {
this.oneSignal.startInit("94218e7a-2307-41fa-9bc3-20783b4cde9a");
this.oneSignal.handleNotificationReceived().subscribe((value:any) => {
let msg = value.payload;
this.messages.push({
title: msg.title,
text: msg.body
});
this.changeDetector.detectChanges();
});
this.oneSignal.endInit();
}
}
在 startInit 函数中,您必须传递您自己的 OneSignal 应用 ID,这是您之前创建的。
- 将
/home文件夹中的home.scss替换为给定的代码:
page-home {
.center {
text-align: center;
}
h2.big-square {
text-align: center;
padding: 50px;
color: #D91E18;
background: #F9BF3B;
}
h3.sub-title {
text-align: center;
padding: 10px;
color: #446CB3;
background: #E4F1FE;
}
ion-card ion-card-header {
padding: 10px 16px;
background: #F9690E;
color: white;
}
ion-card ion-card-header + ion-card-content,
ion-card .item + ion-card-content {
padding-top: 16px;
}
}
-
通过 USB 连接将您的物理 iPhone 连接到 Mac。
-
确保您位于应用文件夹中,并按照以下步骤为 iOS 平台构建应用:
$ ionic cordova run ios
- 操作系统将提示允许 codesign 使用 iOS 开发者证书进行签名。您必须接受此提示以允许访问,以便构建应用并将其上传到设备:

-
验证应用是否在设备上成功运行。在此阶段,您已经完成了推送通知的设置和编码。下一步是通过 OneSignal 控制台发送推送通知。以下是说明:
-
在 OneSignal 中选择您的应用后,您将看到以下侧边菜单:

- 点击 新建消息。您将看到一个如下所示的屏幕:

-
选择 发送给所有人,因为在这个阶段只有您会使用这个应用。然后点击 下一步。将出现以下页面:
![]()
-
输入您的标题和消息。
-
在顶部菜单中,您将看到以下链接。此时,我们处于 消息:
![]()
-
如果您想进行一些配置和调度,您可以在以下部分进行。否则,您可以跳转到 确认 链接,您将在页面右上角看到以下按钮:
![]()
-
只需点击 发送消息,OneSignal 就会将您的推送通知发送出去。
它是如何工作的
为了理解整个流程是如何工作的,让我们总结一下您所做的工作,如下所示:
通过以下步骤设置您的 Apple 开发者账户:
-
创建应用 ID
-
创建应用证书(在通过 Keychain Access 本地创建签名请求之后)
-
创建配置文件
-
创建推送证书
设置您的 OneSignal 应用
现在,让我们专注于编码部分本身,以了解它是如何工作的。
在 NgModul 中,我们在 providers 数组中添加了一个 Ionic Native 包装器用于 OneSignal。
然后,在 home.ts 中,我们在构造函数中通过调用 oneSignalConfig() 函数初始化 OneSignal。在 oneSignalConfig() 函数中,我们调用了 startInit 函数,该函数启动了推送通知注册过程。我们必须向此函数传递一个 OneSignal 应用 ID。然后,我们订阅了 handleNotificationReceived 可观察对象。每当用户收到通知时,它都会被触发。在其中,我们将每个接收到的推送通知推入 this.messages 数组,然后使用 endInit 函数停止初始化过程,如下面的代码所示:
oneSignalConfig() {
this.oneSignal.startInit("94218e7a-2307-41fa-9bc3-20783b4cde9a");
this.oneSignal.handleNotificationReceived().subscribe((value:any) => {
let msg = value.payload;
this.messages.push({
title: msg.title,
text: msg.body
});
this.changeDetector.detectChanges();
});
this.oneSignal.endInit();
}
调用变更检测器的detectChanges函数非常重要,否则由于这个过程超出了 Angular 的变更检测范围,UI 将不会更新。
在home.html模板中,消息将通过messages对象显示,如下面的代码所示:
<ion-card *ngFor="let msg of messages">
<ion-card-header>
{{ msg.title }}
</ion-card-header>
<ion-card-content>
{{ msg.text }}
</ion-card-content>
</ion-card>
在这里,每个message项都有title和text字段。
如果用户没有打开应用,你将看到通知出现在通知区域。
这就是应用在 iPhone 上的样子:

还有更多...
关于 APNs 的更多信息,您可以访问官方文档developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html。
关于 OneSignal 设置的更多信息,请查看onesignal.com/ionic。
构建一个用于接收推送通知的 Android 应用
推送通知在 Google 上与 iOS 的工作方式相同;然而,您将不会使用 Apple 通知服务,而是通过 FCM 服务器进行操作,这是Google Cloud Messaging(GCM)的新替代品。但是 OneSignal 抽象了这个过程,因此您不需要使用不同的 API 进行编码。您将使用与 iOS 应用相同的推送对象。
关于 FCM 和 GCM 之间差异的更多信息,请访问firebase.google.com/support/faq的常见问题解答。
在本节中,您将学习以下内容:
-
设置 OneSignal 以接收 Android 推送通知
-
配置 Firebase 项目以使用推送 API
-
编写代码以在 Android 上接收推送通知
您将使用与 iOS 推送通知示例相同的代码库。主要区别在于在您的 Firebase 和 OneSignal 账户中设置的过程。
准备工作
您可以使用 Android 模拟器测试 Android 推送通知。因此,您不需要有物理 Android 设备。
为了获得访问权限,您还必须在console.firebase.google.com注册 Firebase。
除了您当前的设置外,您还需要安装Android Studio。
- 观察以下截图:
- Android SDK 工具、构建工具、平台工具和英特尔硬件加速执行管理器(HAXM)(
software.intel.com/en-us/android/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows)。观察以下截图:

- 至少已创建一个Android 虚拟设备(AVD)(使用
$ android avd命令行打开 AVD 管理器)。观察以下截图:

如何操作
首先,我们将在 Firebase 控制台中配置推送通知,然后我们将编写示例应用。
配置 Firebase 以推送通知
这里是配置 Firebase 控制台的说明:
-
您需要一个 Firebase 项目编号和一个 Firebase 服务器 ID 以接收推送通知。首先,让我们登录到 Firebase 控制台
console.firebase.google.com。 -
点击创建新项目按钮,并填写项目名称(即
MyAndroidPush):

- 在左侧导航菜单中导航到增长 | 通知:

- 选择 Android 图标:

FCM 服务也支持 iOS 应用。因此,您可能可以在 iOS 和 Android 项目中都使用 FCM。
- 在表格中提供包名。您可以从应用项目的
./config.xml中复制并粘贴包名:

- 选择继续,并将 JSON 文件保存到某个位置。您不需要在 Ionic 项目中使用此 JSON 文件:

-
点击完成按钮以完成通知服务的设置:
![图片]()
-
现在,您将需要服务器密钥和发送者 ID。导航到左上角的齿轮图标,并选择项目设置菜单项:
![图片]()
-
选择云消息选项卡:
.
![图片]()
-
复制服务器密钥和发送者 ID(如果使用 GCM,则与项目 ID 相同):
![图片]()
配置 OneSignal
这里是配置 OneSignal 的说明:
- 在您的OneSignal 仪表板中,打开之前创建的 iOS 应用,然后点击应用设置。您将看到以下页面:

- 点击与Google Android 平台平行的配置按钮。您将看到以下对话框:

- 将服务器 API 密钥和项目编号(也称为发送者 ID)输入到相应的字段中,然后点击保存。
让我们开始编码
下面是创建示例应用的说明:
- 使用
blank模板创建一个新的MyAndroidPush应用,如下所示,然后进入MyAndroidPush文件夹:
$ ionic start MyAndroidPush blank
$ cd MyAndroidPush
- 安装 Cordova 插件和 OneSignal 的 Ionic 原生包装器:
$ ionic cordova plugin add onesignal-cordova-plugin
$ npm install --save @ionic-native/onesignal
- 使用以下内容打开并编辑
./src/app/app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { OneSignal } from '@ionic-native/onesignal';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
OneSignal,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 你的主页代码与 iOS 推送示例非常相似。打开并编辑
./src/pages/home/home.html,并粘贴以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Push Notification
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2 class="big-square" *ngIf="!this.messages.length">
You have no message
</h2>
<h3 class="sub-title" *ngIf="!!this.messages.length">
Your messages
</h3>
<ion-card *ngFor="let msg of messages">
<ion-card-header>
{{ msg.title }}
</ion-card-header>
<ion-card-content>
{{ msg.text }}
</ion-card-content>
</ion-card>
</ion-content>
- 将同一文件夹中的
home.ts文件的内容替换为以下代码:
import { Component, ChangeDetectorRef } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { OneSignal } from '@ionic-native/onesignal';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
public messages = [];
public clicked: Boolean = false;
constructor(public navCtrl: NavController, public oneSignal: OneSignal, platform: Platform, private changeDetector: ChangeDetectorRef) {
platform.ready().then(() => {
this.oneSignalConfig();
});
}
oneSignalConfig() {
this.oneSignal.startInit("94218e7a-2307-41fa-9bc3-20783b4cde9a", "539293856976");
this.oneSignal.handleNotificationReceived().subscribe((value:any) => {
// do something when notification is received
let msg = value.payload;
this.messages.push({
title: msg.title,
text: msg.body
});
this.changeDetector.detectChanges();
});
this.oneSignal.endInit();
}
}
如果你注意到了,对于 Android,startInit的调用有两个参数而不是一个参数。第一个参数是OneSignal App ID,第二个参数是Google 项目编号/发送者 ID。
- 将
home.scss替换为以下代码,同样在/home文件夹中:
page-home {
.center {
text-align: center;
}
h2.big-square {
text-align: center;
padding: 50px;
color: #D91E18;
background: #F9BF3B;
}
h3.sub-title {
text-align: center;
padding: 10px;
color: #446CB3;
background: #E4F1FE;
}
ion-card ion-card-header {
padding: 10px 16px;
background: #F9690E;
color: white;
}
ion-card ion-card-header + ion-card-content,
ion-card .item + ion-card-content {
padding-top: 16px;
}
}
- 确保你处于应用文件夹中,并按照以下方式为 Android 平台构建:
$ ionic cordova run android
发送推送通知的过程与 iOS 完全相同。
它是如何工作的
这个过程几乎与 iOS 相同。原因是 OneSignal 为我们抽象了很多东西。不同之处在于,它现在不是向 APNS 发送推送通知请求,而是向 Google 的 FCM 服务器发送推送通知请求。然后,它将推送通知转发到实际设备。
你将在 Android 手机上看到如下通知:

默认情况下,它将使用应用图标作为通知图标,但你可以为 Android 自定义它。查看documentation.onesignal.com/docs/customize-notification-icons。在 iOS 上,你不能自定义图标。
当你打开应用时,你将看到如下通知:

还有更多...
关于Firebase 通知服务的更多信息,你可以访问官方文档firebase.google.com/docs/cloud-messaging/。
第七章:使用 Ionic Native 支持设备功能
在本章中,我们将介绍与原生设备功能支持相关的以下任务:
-
使用相机插件拍照
-
使用社交分享插件分享内容
-
使用本地通知插件显示本地通知
-
使用指纹 AIO 插件进行指纹认证。
-
创建一个媒体播放器和添加媒体播放器通知控件
-
使用 Google Maps 插件和地理编码支持创建出租车应用程序
简介
在本章中,你将学习如何访问设备的一些常见功能,例如相机、联系名单、电子邮件和地图。其中一些功能可以仅使用 JavaScript 环境编写,但性能并不与原生支持相媲美。
Cordova 拥有一个非常支持的社区,拥有许多插件。你可能想查看 plugins.cordova.io/ 以了解有哪些插件。幸运的是,你不需要直接处理这些插件。你将使用在 Cordova 和 Angular 之上的 Ionic Native (ionicframework.com/docs/v2/native/) 服务。请注意,由于兼容性问题,你必须使用 Ionic Native 而不是 ngCordova 来支持 Ionic 2+。你只能为 Ionic 1.x 使用 ngCordova。
使用相机插件拍照
在本节中,你将制作一个应用程序,使用设备相机拍照或从设备相册加载现有图片。图片可以是 Base64 格式,也可以保存在与你的应用程序相关的本地文件系统中。以下是该应用程序的截图:

这里是高级流程:
-
访问 Cordova 相机插件以触发相机捕获,并以 Base64 或 URI 格式获取图像
-
在
<img>DOM 对象上解析 Base64 数据或 URI -
如果 URI 格式,则显示 URI
-
捕获一个切换组件的事件
-
使用水平滚动显示长数据(例如,URI)
准备工作
为了测试相机功能,你应该准备一个物理设备。虽然可以通过模拟器运行代码,但不同平台上的文件系统支持可能会有所不同。
如何操作...
以下是为添加相机支持的说明:
- 开始一个空白项目(例如,
MyCamera)并进入该文件夹:
$ ionic start MyCamera blank
$ cd MyCamera
- 使用以下代码添加 Cordova 相机插件和该插件的 Ionic Native 封装:
$ ionic plugin add cordova-plugin-camera
$ npm install --save @ionic-native/camera
你不应该直接使用 cordova add 命令行;而是使用 ionic cordova plugin add
你应该能够看到一个新文件夹,cordova-plugin-camera,被添加到 /plugins 文件夹中。
- 将
./src/pages/home/home.html替换为以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Camera
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-row class="center">
<ion-col width-50>
<button ion-button (click)="getPicture(1)">Show Camera</ button>
</ion-col>
<ion-col width-50>
<button ion-button (click)="getPicture(0)">Show Album</ button>
</ion-col>
</ion-row>
<ion-item class="no-border">
<ion-label>Return image file URI</ion-label>
<ion-toggle energized [(ngModel)]="useURI">
</ion-toggle>
</ion-item>
<ion-card>
<img [src]="imageData" *ngIf="imageData" />
<ion-card-content>
<ion-card-title>
<div *ngIf="useURI">
Using URI
</div>
<div *ngIf="!useURI">
Using Base64
</div>
</ion-card-title>
<p *ngIf="useURI">
Your URI is {{ imageData }}
</p>
<p *ngIf="!useURI">
Your Base64 image has {{ (imageData + '').length }} bytes
</p>
</ion-card-content>
</ion-card>
</ion-content>
由于你只有一个页面,这个模板将显示两个按钮和一个用于显示图片的区域。
- 将
./src/pages/home/home.ts替换为以下代码:
import { Component, Input } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Camera } from '@ionic-native/camera';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
public imageData: string;
@Input('useURI') useURI: Boolean = true;
constructor(public navCtrl: NavController, public camera:Camera) {
}
getPicture(sourceType) {
this.camera.getPicture({
quality: 50,
allowEdit: true,
encodingType: this.camera.EncodingType.JPEG,
saveToPhotoAlbum: false,
destinationType: this.useURI ? this.camera.DestinationType.
FILE_URI : this.camera.DestinationType.DATA_URL,
targetWidth: 800,
targetHeight: 800,
sourceType: sourceType
}).then((imageData) => {
if (this.useURI) {
this.imageData = imageData;
} else {
this.imageData = "data:image/jpeg;base64," + imageData;
}
}, (err) => {
console.log(err);
});
}
}
只有一种方法:getPicture()。此方法将返回照片数据,以便模板可以渲染。
- 将
.src/pages/home/home.scss替换为以下代码:
page-home {
center {
text-align: center;
}
.no-border .item-inner {
border-bottom: 0;
}
}
在样式上只有一些小的改动,所以你可以保持它们简单。
- 将
./src/app/app.module.ts替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { Camera } from '@ionic-native/camera';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
Camera,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
-
将你的设备连接到电脑。
-
前往终端并执行以下命令行以进行 iOS 操作:
$ ionic cordova run ios
如果你无法使用前面的命令行将应用推送到你的物理设备,你可以使用 ionic cordova run ios --device 来指定 CLI 使用物理设备而不是模拟器。
如果你想在 Android 设备上运行应用,请使用以下代码:
$ ionic cordova run android
当你运行应用并拍照时,你应该能看到应用,如下面的截图所示:

它是如何工作的...
Camera.getPicture() 只是 Cordova 相机插件的 navigator.camera.getPicture() 的抽象。如果你已经熟悉来自 Ionic 1 的 Cordova 或 ngCordova,这应该非常熟悉。让我们从模板开始。你有以下两个按钮,它们将触发相同的 getPicture() 方法:
<button ion-buton (click)="getPicture(1)">Show Camera</button>
<button ion-buton (click)="getPicture(1)">Show Camera</button>
这些只是访问照片的不同方式:要么从相机本身,要么从手机相册中的现有照片。为了使照片渲染,你需要将照片数据传递到 src 属性,如下所示:
<img [src]="imageData" *ngIf="imageData" />
注意,你只想在 imageData 存在且包含数据时显示此 <img> 标签。imageData 变量可以是 Base64 或照片的内部 URL。为了指定此选项,有一个切换按钮,如图所示:
<ion-toggle energized [(ngModel)]="useURI"></ion-toggle>
你将在类中使用 useURI 变量,如图所示,以确定返回照片数据的格式:
@Input('useURI') useURI: Boolean = true;
useURI 和 sourceType 都将在 getPicture() 函数中使用,如下所示:
Camera.getPicture({
quality: 50,
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
saveToPhotoAlbum: false,
destinationType: this.useURI ? Camera.DestinationType.FILE_URI
: Camera.DestinationType.DATA_URL,
targetWidth: 800,
targetHeight: 800,
sourceType: sourceType
}).then((imageData) => {
if (this.useURI) {
this.imageData = imageData;
} else {
this.imageData = "data:image/jpeg;base64," + imageData;
}
}, (err) => {
console.log(err);
});
调整质量、targetWidth 和 targetHeight 至低非常重要,这样照片就不会太大,否则可能会使设备崩溃,尤其是在内存不足的情况下。当你返回 Base64 数据时,必须在字符串前加上 data:image/jpeg;base64。
还需要注意,在 app.module.ts 中,我们正在将 Camera 插件添加到 NgModule 的提供者数组中。这非常重要,因为它允许我们通过 Angular 的依赖注入系统使用插件。在本章中,我们必须为每个插件都这样做。
这里没有讨论的一项功能是将图像数据发布到服务器。常见场景是从文件系统中上传文件。由于数据大小是原始二进制大小的两倍,因此将数据作为 Base64 发送不是一个好主意。
还有更多...
只使用 JavaScript 就可以创建类似 Instagram 的滤镜效果。你可以利用现有的库,例如 Filterous (github.com/girliemac/Filterous),直接修改图像画布。
在 GitHub 上有一个针对 Cordova 的 Instagram 插件(github.com/vstirbu/InstagramPlugin)。您可以为传递图片到 Instagram 编写一些额外的代码。不过,用户必须首先在手机上安装 Instagram。当您计划在 Instagram 执行照片滤镜操作之前做一些酷炫的图像处理(例如,添加有趣的文字)时,这个想法很不错。
您甚至可以添加 Cordova 的社交网络插件并将生成的图片发布到 Twitter 或 Facebook。
使用社交分享插件分享内容
如果您开发了一个具有可分享内容的应用,您可能希望利用设备的原生功能通过设备的授权社交媒体账户进行分享。使用这种方法有几个好处。首先,用户每次想要分享时,不需要打开单独的浏览器来登录他们的社交媒体账户。其次,所有信息都可以通过编程方式填写,例如标题、正文、链接或图片。最后,由于这是设备的原生功能,菜单选择允许用户看到他们已经熟悉的多个账户,从而进行选择。社交分享插件可以极大地提升用户体验。
这就是您将要构建的应用:

当用户点击分享按钮时,应用将显示以下用于社交媒体账户选择的原生按钮菜单:

如果用户选择了 Twitter,将会弹出一个包含所有预先填写信息的窗口,如下面的截图所示:

在 Twitter 上发布后,用户可以直接返回应用,而无需离开。
准备工作
为了测试社交分享功能,您应该准备好一个物理设备或模拟器。
如何操作...
以下是指示:
- 开始一个空白项目(例如,
LinkSocialShare),如下所示,并进入该文件夹:
$ ionic start LinkSocialShare blank
$ cd LinkSocialShare
- 使用以下命令行添加社交分享插件和插件的 Ionic Native 包装器:
$ ionic plugin add cordova-plugin-x-socialsharing
$ npm install --save @ionic-native/social-sharing
- 打开
./src/pages/home/home.html并将其内容替换为以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Home
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card>
<ion-item>
<h2 #messageSubject>Ionic Developer</h2>
<p>May 5, 2016</p>
</ion-item>
<img src="img/600x390">
<ion-card-content>
<p #messageBody>Wow Ionic is so awesome. I gotta share this to other people.</p>
</ion-card-content>
<ion-row>
<ion-col>
<button ion-button color="primary" clear small icon-left>
<ion-icon name="thumbs-up"></ion-icon>
<div>12 Likes</div>
</button>
</ion-col>
<ion-col>
<button ion-button color="primary" clear small icon-left
(click)="sendShare(messageBody.innerText,
messageSubject.innerText,'http://ionicframework.com/docs/v2/')">
<ion-icon name="ios-share"></ion-icon>
<div>Share</div>
</button>
</ion-col>
<ion-col center text-center>
<ion-note>
11h ago
</ion-note>
</ion-col>
</ion-row>
</ion-card>
</ion-content>
这是一个非常简单的页面,包含卡片元素。点赞按钮只是出于美观原因而存在,没有代码实现。然而,所有的 JavaScript 逻辑都将集中在
分享按钮。
- 打开
./src/pages/home/home.ts,如下所示:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { SocialSharing } from '@ionic-native/social-sharing';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public socialSharing: SocialSharing) {
}
sendShare(message, subject, url) {
this.socialSharing.share(message, subject, null, url);
}
}
- 将
./src/app/app.module.ts替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { SocialSharing } from '@ionic-native/social-sharing';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
SocialSharing,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 前往终端并执行以下任一命令行:
$ ionic run ios
$ ionic run android
它是如何工作的...
您可以开始查看模板,因为社交媒体内容是从那里提取的。主题值来自#messageSubject局部变量,如下所示:
<ion-item>
<h2 #messageSubject>Ionic Developer</h2>
<p>May 5, 2016</p>
</ion-item>
在前面的例子中,主题是Ionic Developer,因为您稍后将会访问messageSubject.innerText。messageSubject只是引用您的 H2 DOM 节点。
类似地,正文来自#messageBody,如图所示:
<ion-card-content>
<p #messageBody>Wow Ionic 2 is so awesome. I gotta share this to other people.</p>
</ion-card-content>
当用户点击“分享”按钮时,将触发sendShare()方法,如下所示:
<button ion-button color="primary" clear small icon-left
(click)="sendShare(messageBody.innerText,
messageSubject.innerText,
'http://ionicframework.com/docs/v2/')">
让我们看看您的home.ts,了解sendShare()是如何工作的。
首先,您需要从 Ionic Native 导入SocialSharing模块,如图所示:
import { SocialSharing } from '@ionic-native/social-sharing';
要分享内容并触发社交媒体菜单,逻辑,如图所示,非常简单:
sendShare(message, subject, url) {
SocialSharing.share(message, subject, null, url);
}
如果您想分享一个文件,可以将第三个参数(其中为null)替换为用户本地文件系统的 URL。当您想让人通过电子邮件发送 PDF 或 JPG 或将其发布在 Facebook 上时,这很有用。
更多内容...
要查看社交分享插件的最新更新,您可以访问ionicframework.com/docs/v2/native/social-sharing/的文档页面。
使用本地通知插件显示本地通知
在开发移动应用程序时,您的应用程序可能需要通知用户某些信息。如果信息来自后端,我们使用推送通知。但是,如果信息是由应用程序在设备本地生成的,我们可以使用本地通知来实现这一点。
这个菜谱旨在帮助您了解本地通知是如何工作的。
当您打开应用程序时,您将看到以下页面。

您可以在输入框中输入任何内容,然后点击“显示通知”。您还可以安排稍后显示的通知。这是通知区域中通知的外观:

准备工作
您应该准备一个物理设备以便测试本地通知。
如何操作...
这里是说明:
- 创建一个空的 Ionic 应用程序(例如,
Notifications)并cd到该文件夹,如图所示:
$ ionic start Notifications blank
$ cd Notifications
- 使用以下命令安装本地通知插件和插件的 Ionic 本地包装器:
$ ionic cordova plugin add cordova-plugin-local-notification
$ npm install --save @ionic-native/local-notifications
- 打开
./src/pages/home/home.html并替换以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Local Notifications
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item padding>
<ion-label color="primary" stacked>Notification Message</ion-label>
<ion-input placeholder="Enter Notification Text here" [(ngModel)]="message"></ion-input>
</ion-item>
<button full ion-button color="primary" (click)="showNotification('now')">Show Notification</button>
<ion-item padding>
<ion-label>Time in seconds</ion-label>
<ion-datetime displayFormat="ss Second" placeholder="" [(ngModel)]="time"></ion-datetime>
</ion-item>
<button full ion-button color="primary" (click)="showNotification('future')">Schedule a Notification</button>
<button full ion-button color="primary" (click)="clearNotifications()">Clear all Notifications</button>
</ion-content>
- 打开
./src/pages/home/home.ts并替换以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { LocalNotifications } from '@ionic-native/local-notifications';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
notifications:Array<any> = [];
id: number = 1;
message:string;
time:number;
constructor(public navCtrl: NavController,
private localNotifications: LocalNotifications) {
}
showNotification(type) {
if(type === 'now') {
this.localNotifications.schedule({
id: this.id,
text: this.message,
});
} else {
this.localNotifications.schedule({
id: this.id,
text: this.message,
at: new Date(new Date().getTime() + this.time * 100),
});
}
this.id++;
}
clearNotifications() {
this.localNotifications.clearAll();
}
}
- 将
./src/app/app.module.ts替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { LocalNotifications } from '@ionic-native/local-notifications';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
LocalNotifications,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 在终端中使用以下命令行运行应用程序:
$ ionic run ios
$ ionic run android
它是如何工作的...
首先,让我们看一下模板home.html:
<ion-content padding>
<ion-item padding>
<ion-label color="primary" stacked>Notification Message</ion-label>
<ion-input placeholder="Enter Notification Text here" [(ngModel)]="message"></ion-input>
</ion-item>
<button full ion-button color="primary" (click)="showNotification('now')">Show Notification</button>
<ion-item padding>
<ion-label>Time in seconds</ion-label>
<ion-datetime displayFormat="ss Second" placeholder="" [(ngModel)]="time"></ion-datetime>
</ion-item>
<button full ion-button color="primary" (click)="showNotification('future')">Schedule a Notification</button>
<button full ion-button color="primary" (click)="clearNotifications()">Clear all Notifications</button>
</ion-content>
我们有一个输入字段,用于添加通知文本。然后我们有一个按钮用于立即显示通知。然后我们有一个ion-datetime输入,用于指定秒数。它将用于通过“安排通知”按钮安排未来的通知。最后,我们有一个清除所有通知按钮,用于清除设备通知区域中的通知。
在您的home.ts中,您必须首先导入LocalNotifications模块,如图所示:
import { LocalNotifications } from '@ionic-native/local-notifications';
当有人点击“显示通知”或“安排通知”按钮时,会触发“显示通知”按钮。以下是showNotification方法:
showNotification(type) {
if(type === 'now') {
this.localNotifications.schedule({
id: this.id,
text: this.message,
});
} else {
this.localNotifications.schedule({
id: this.id,
text: this.message,
at: new Date(new Date().getTime() + this.time * 100),
});
}
this.id++;
}
The showNotifications 方法接受一个参数 type。type 用于确定我们是否希望立即显示通知或在未来的某个时间显示。如果 type 等于 now,我们立即显示它;否则,我们安排稍后显示通知。为了显示通知,我们使用插件的 schedule 方法。我们必须指定 id、text 和 at 值。at 值用于安排将来显示的通知。
The clearNotifications 方法清除所有通知。
还有更多...
查看本地通知插件的 GitHub 页面以获取更多信息,github.com/katzer/cordova-plugin-local-notifications。
使用指纹 AIO 插件进行指纹验证
那些只想使用密码验证系统的日子已经过去了。验证正在变得更强和更直观。如今,大多数 iOS 设备都有指纹传感器,这种趋势现在也开始在 Android 设备中盛行。用户可以使用指纹密码来保护他们的设备。幸运的是,对于像我们这样的开发者来说,我们可以使用相同的技术来保护我们应用程序内的内容,用户在验证后可以访问这些内容。
在这个菜谱中,我们正在创建一个解锁秘密应用程序。它基本上是一个玩笑应用程序。主页看起来如下。

当用户点击 揭示秘密 按钮时,它会显示指纹验证对话框:

当用户进行验证时,我们在屏幕上显示一个笑话。就这么简单:

准备工作
为了测试这个应用程序,你应该有一个带有指纹传感器的物理设备。
如何操作...
这里是说明:
- 创建一个空白 Ionic 应用程序(例如,
FingerAuth)并cd到该文件夹,如下所示:
$ ionic start FingerAuth blank
$ cd FingerAuth
- 使用以下命令安装指纹
aio插件和 Ionic Native 包装器:
$ ionic cordova plugin add cordova-plugin-fingerprint-aio
$ npm install --save @ionic-native/fingerprint-aio
- 打开
./src/pages/home/home.html并替换为以下代码:
<ion-content padding>
<h1 padding>Secrets</h1>
<p *ngIf="!isAvailable">This Device doesn't have Fingerprint Sensor</p>
<p *ngIf="isAvailable && quote">{{quote.joke}}</p>
<button id="reveal-button" color="primary" *ngIf="!!isAvailable" ion-button round (click)="authenticate()">Reveal a Secret</button>
</ion-content>
- 打开
./src/pages/home/home.ts并替换为以下代码:
import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { HttpClient } from '@angular/common/http';
import { FingerprintAIO } from '@ionic-native/fingerprint-aio';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
quote:any = {};
isAvailable: Boolean;
constructor(public navCtrl: NavController,
private faio: FingerprintAIO,
private http: HttpClient,
private platform: Platform) {
}
ionViewDidLoad() {
this.checkAvailablity();
}
checkAvailablity() {
this.platform.ready()
.then(()=> {
this.faio.isAvailable().then((value)=> {
console.log(value);
this.isAvailable = true
}).catch(() => {
this.isAvailable = false;
});
});
}
authenticate() {
this.faio.show({
clientId: 'Ionic Fingerprint Auth',
clientSecret: 'password', //Only necessary for Android
localizedFallbackTitle: 'Use Pin', //Only for iOS
localizedReason: 'Please authenticate' //Only for iOS
})
.then((result: any) => {
this.reveal();
});
}
reveal() {
const url = "http://api.icndb.com/jokes/random/";
this.http.get(url)
.subscribe((data:any) => {
this.quote = data.value;
});
}
}
- 将
./src/pages/home/home.scss替换为以下代码:
page-home {
ion-content {
.scroll-content {
text-align:center;
background-color: black;
color:white;
#reveal-button {
height:200px;
width:200px;
border-radius:100%;
}
}
}
}
- 将
./src/app/app.module.ts替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { HttpClientModule } from '@angular/common/http';
import { FingerprintAIO } from '@ionic-native/fingerprint-aio';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
HttpClientModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
FingerprintAIO,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 在终端中运行应用程序,使用以下命令行:
$ ionic cordova run ios
$ ionic cordova run android
它是如何工作的...
首先,让我们看看模板 home.html:
<ion-content padding>
<h1 padding>Secrets</h1>
<p *ngIf="!isAvailable">This Device doesn't have Fingerprint Sensor</p>
<p *ngIf="isAvailable && quote">{{quote.joke}}</p>
<button id="reveal-button" color="primary" *ngIf="!!isAvailable" ion-button round (click)="authenticate()">Reveal a Secret</button>
</ion-content>
如果设备没有指纹传感器,我们会在屏幕上显示一条通知说 This Device doesn't have Fingerprint Sensor。否则,我们显示一个揭示秘密按钮。当用户点击它时,它会打开指纹验证对话框。
在你的 home.ts 中,你必须首先导入 FingerprintAIO 模块,如下所示:
import { FingerprintAIO } from '@ionic-native/fingerprint-aio';
当页面加载时,我们通过插件中的isAvailable方法在ionViewDidLoad钩子中检查传感器的可用性。它返回一个承诺。如果已解析,则表示传感器可用。当用户在 UI 中点击“揭示秘密”按钮时,会触发authenticate方法。
这是authenticate()方法:
authenticate() {
this.faio.show({
clientId: 'Ionic Fingerprint Auth',
clientSecret: 'password', //Only necessary for Android
localizedFallbackTitle: 'Use Pin', //Only for iOS
localizedReason: 'Please authenticate' //Only for iOS
})
.then((result: any) => {
this.reveal();
});
}
在此方法中,我们调用插件的show方法,它返回一个承诺。如果承诺已解析,则表示用户已认证,我们将揭示笑话。否则,我们不做任何事情。
更多内容...
您可以在github.com/NiklasMerz/cordova-plugin-fingerprint-aio了解更多关于指纹 AIO 插件的信息。
使用媒体播放器通知控件创建媒体播放器
媒体播放器是用户手机中的一个重要应用。现在几乎每个媒体应用都在通知区域有一个控件。在这个菜谱中,我们将创建一个非常简单的媒体播放器,带有播放和暂停按钮,并将媒体播放器控件添加到通知区域。
这就是我们的应用主页将看起来像:

这就是通知区域将看起来像:

当用户在应用内点击播放按钮时,它会在通知区域显示控件。当用户在应用内点击暂停按钮时,它也会更新音乐控件。这也适用于相反的情况。您可以从通知区域的音乐控件播放和暂停媒体。
准备工作
由于该应用使用了 Cordova 插件,因此您应该准备一个物理设备来测试此应用。
如何操作...
这里是说明:
- 创建一个空的 Ionic 应用(例如,
MediaPlayer),然后cd到该文件夹,如下所示:
$ ionic start MediaPlayer blank
$ cd MediaPlayer
- 使用以下命令安装音乐控件插件及其 Ionic Native 包装器:
$ ionic cordova plugin add cordova-plugin-music-controls
$ npm install --save @ionic-native/music-controls
- 打开
./src/pages/home/home.html并替换为以下代码:
<ion-header>
<ion-navbar>
<ion-title>
Music Player
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item>
<ion-thumbnail item-left>
<img src="img/{{track.art}}">
</ion-thumbnail>
<div item-content style="width:100%">
<p><strong>{{track.title}}</strong> ⚬
<em>{{track.artist}}</em></p>
</div>
</ion-item>
<ion-row id="music-controls">
<ion-col (click)="play()"><ion-icon name="play"></ion-icon></ion-col>
<ion-col (click)="pause()"><ion-icon name="pause"></ion-icon></ion-col>
</ion-row>
</ion-content>
- 打开
./src/pages/home/home.ts并替换为以下代码:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { MusicControls } from '@ionic-native/music-controls';
import { AudioProvider} from '../../services/audio-service';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
track = {
src: 'https://ia801609.us.archive.org/16/items/nusratcollection_20170414_0953/Man%20Atkiya%20Beparwah%20De%20Naal%20Nusrat%20Fateh%20Ali%20Khan.mp3',
artist: 'Nusrat Fateh Ali Khan',
title: 'Man Atkiya Beparwah De Naal',
art: 'https://ia801307.us.archive.org/31/items/mbid-42764450-04e5-459e-b022-00847fc8fb94/mbid-42764450-04e5-459e-b022-00847fc8fb94-12391862253_thumb250.jpg',
preload: 'metadata' // tell the plugin to preload metadata such as duration for this track, set to 'none' to turn off
};
constructor(public navCtrl: NavController, public musicControls:MusicControls,
public audioProvider: AudioProvider) {}
play() {
this.audioProvider.play(this.track.src);
this.createControls();
}
pause() {
this.audioProvider.pause();
this.musicControls.updateIsPlaying(false);
}
createControls() {
this.musicControls.create({
track : this.track.title,
artist : this.track.artist,
cover : this.track.art,
isPlaying : true,
hasPrev : false,
hasNext : false,
dismissable : true,
});
this.musicControls.subscribe().subscribe(action => {
const message = JSON.parse(action).message;
switch(message) {
case 'music-controls-play':
this.play();
break;
case 'music-controls-pause':
this.pause();
break;
}
});
this.musicControls.listen();
}
}
- 将
./src/pages/home/home.scss替换为以下代码:
page-home {
#music-controls {
text-align:center;
font-size: 2.5rem;
}
}
- 在
./src/services/audio-service.ts中创建一个文件并添加以下内容:
import { Injectable } from '@angular/core';
@Injectable()
export class AudioProvider {
track:any;
isPaused: Boolean = false;
url;
play(url) {
if(this.url !== url) {
this.url = url;
this.track = new Audio(url);
this.track.load();
}
this.track.play();
}
pause() {
this.track.pause();
this.isPaused = true;
}
}
- 将
./src/app/app.module.ts替换为以下代码:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MusicControls } from '@ionic-native/music-controls';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AudioProvider} from '../services/audio-service';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
MusicControls,
AudioProvider,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
- 使用以下命令行在终端中运行应用:
$ ionic cordova run ios
$ ionic cordova run android
工作原理...
首先,让我们看一下模板home.html:
<ion-content padding>
<ion-item>
<ion-thumbnail item-left>
<img src="img/{{track.art}}">
</ion-thumbnail>
<div item-content style="width:100%">
<p><strong>{{track.title}}</strong> ⚬ <em>{{track.artist}}</em>
</p>
</div>
</ion-item>
<ion-row id="music-controls">
<ion-col (click)="play()"><ion-icon name="play"></ion-icon></ion-col>
<ion-col (click)="pause()"><ion-icon name="pause"></ion-icon></ion-col>
</ion-row>
</ion-content>
非常简单。我们在ion-item中显示了轨道的缩略图和轨道标题,以及轨道艺术家。然后我们有play和pause按钮,它们在ion-row内部显示。
在home.ts中,我们有媒体播放器的音频轨道列表。为了简化,我只添加了一个轨道,这样我们就不添加太多额外的功能,并保持对插件部分的关注。然后我们有play和pause方法。这些方法与 UI 中的play和pause按钮相关联,并且当用户点击它们时会被触发。
在play按钮内部,我们使用轨道的源url触发 AudioProvider 的play方法,并调用createControls方法。这个createControls方法在设备的通知区域创建音乐控制。
在pause按钮内部,我们触发 AudioProvider 的pause方法,并更新音乐控制,告诉它我们已经暂停了音频,并且它相应地更改了 UI。
在createControls()方法中:
createControls() {
this.musicControls.create({
track : this.track.title,
artist : this.track.artist,
cover : this.track.art,
isPlaying : true,
hasPrev : false,
hasNext : false,
dismissable : true,
});
this.musicControls.subscribe().subscribe(action => {
const message = JSON.parse(action).message;
switch(message) {
case 'music-controls-play':
this.play();
break;
case 'music-controls-pause':
this.pause();
break;
}
});
this.musicControls.listen();
}
此外,看看我们的微型AudioProvider:
export class AudioProvider {
track:any;
isPaused: Boolean = false;
url;
play(url) {
if(this.url !== url) {
this.url = url;
this.track = new Audio(url);
this.track.load();
}
this.track.play();
this.isPaused = false;
}
pause() {
this.track.pause();
this.isPaused = true;
}
}
在play方法中,我们获取url作为参数。如果新的url与之前的不同,我们使用该url创建Audio对象,并加载媒体,然后播放媒体并将this.isPaused设置为true。
在pause方法中,我们暂停轨道并将this.isPaused布尔值设置为true,默认设置为false。
还有更多...
为了简化,我们只使用了播放和暂停按钮。但是,很容易在通知区域以及应用中实现即将到来的下一首和上一首功能。查看插件的 GitHub 页面github.com/homerours/cordova-music-controls-plugin。
使用 Google Maps 插件和地理编码支持创建出租车应用
今天,许多移动应用利用不同的地图功能,例如显示当前位置、创建路线和提供建议性的商业搜索。本节将向您展示如何使用 Ionic Native 的 Google Maps 插件提供地图支持。
你将创建一个可以执行以下操作的出租车应用:
-
以全屏显示 Google Maps
-
在地图上添加一个按钮覆盖层
-
检测当前设备位置
-
添加带有任何文本的标记
这是出租车应用的截图:

当用户点击 PICK ME UP 按钮时,它会跳转到当前设备位置并显示经纬度信息:

可以使用 HTML5 和 JavaScript 版本的地理位置和地图,而不是 Cordova 插件的。然而,你将看到性能上的负面影响。很明显,如果你使用 SDK,地图渲染和优化通常会更快。此外,HTML5 地理位置有时会有一些奇怪的 bug,需要用户接受两次权限——一次为应用,一次为内部浏览器对象。
准备工作
Google Maps 插件需要为你的项目提供一个 Google Maps API 密钥。你需要一个 Google 账户并登录才能开始:
-
导航到 Google APIs 控制台
console.developers.google.com/cloud-resource-manager。 -
如果你还没有创建项目,请创建一个。只需填写所需的字段:

- 您需要启用 Google Maps SDK for iOS、Google Maps Android API 或两者都启用。这取决于您计划支持多少平台。在这个例子中,让我们选择 Google Maps SDK for iOS:

- 点击启用按钮:

- 前往凭证以创建您自己的密钥:

- 点击创建凭证 | API 密钥选项:

- 选择 RESTRICT KEY 选项。在以下示例中,您将选择 iOS 应用程序单选按钮:

- 填写您应用程序的 Bundle ID。您可能还不知道确切是什么,因为 Ionic 将创建一个随机的 ID。所以只需输入
com.ionicframework.starter并稍后更改它:

-
点击保存按钮。
-
现在,您应该会看到如下所示的 iOS 应用程序部分的关键字:

- 复制 API 密钥,以便您可以使用它来添加 Cordova Google Maps 插件。
如何操作...
让我们从零开始创建一个 Ionic 项目并添加 Google Maps 功能,如下所示:
- 按照所示创建一个空的 Ionic 项目,并进入该文件夹:
$ ionic start TaxiApp blank
$ cd TaxiApp
- 使用以下命令行将 iOS 平台替换为版本 3.9.0:
$ ionic platform remove ios
$ ionic platform add ios@3.9.0
$ ionic platform add android
您必须特别选择 ios@3.9.0,因为当前版本的 Cordova Google Maps 插件仅与此版本兼容。否则,您的构建将失败。如果可能的话,您应该尝试最新的版本。
- 使用您复制的密钥替换
`YOUR_IOS_API_KEY_IS_HERE,如下所示安装 Google Maps 插件:
$ cordova plugin add cordova-plugin-googlemaps --variable
API_KEY_FOR_IOS="YOUR_IOS_API_KEY_IS_HERE"`
如果您为 iOS 和 Android 都这样做,请使用以下命令行:
$ cordova plugin add cordova-plugin-googlemaps --variable
API_KEY_FOR_ANDROID="key" --variable API_KEY_FOR_IOS="key"
您必须在这里使用 Cordova CLI,因为使用 Ionic CLI 添加带有 API 密钥的 Google Maps 不会起作用
- 打开
./src/pages/home/home.html以修改您的模板,如下所示:
<ion-content [ngClass]="{'no-scroll': mapRendered}">
<div id="map">
<button ion-button color="dark" (click)="getMyLocation()">PICK ME UP</button>
</div>
</ion-content>
这里的主要元素是您的具有 map ID 的 div,因为您必须在这里注入 Google Maps 对象。
- 在同一文件夹中编辑您的
./src/pages/home/home.ts:
import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import {
GoogleMaps,GoogleMap,GoogleMapsEvent,GoogleMapOptions,
CameraPosition,LatLng,MarkerOptions,Marker
} from '@ionic-native/google-maps';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
public map: GoogleMap;
public mapRendered: Boolean = false;
constructor(public navCtrl: NavController, public platform: Platform) {
this.platform.ready().then(() => {
this.showMap();
});
}
showMap() {
let mapOptions: GoogleMapOptions = {
camera: {
target: {
lat: 43.0741904,
lng: -89.3809802
},
zoom: 18,
tilt: 30
}
};
this.map = GoogleMaps.create('map', mapOptions);
this.map.one(GoogleMapsEvent.MAP_READY)
.then(() => {
console.log('Map is ready!');
this.mapRendered = true;
});
}
getMyLocation() {
this.map.getMyLocation().then((location) => {
var msg = ["I am here:\n",
"latitude:" + location.latLng.lat,
"longitude:" + location.latLng.lng].join("\n");
let position = {
target: location.latLng,
zoom: 15
};
this.map.moveCamera(position);
let markerOptions: MarkerOptions = {
'position': location.latLng,
'title': msg
};
this.map.addMarker(markerOptions).then((marker:Marker) => {
marker.showInfoWindow();
});
});
}
}
- 对样式表进行一些小的调整,以便地图可以占据整个屏幕。编辑
./src/pages/home/home.scss,如下所示:
ion-app._gmaps_cdv_ .nav-decor{
background-color: transparent !important;
}
page-home {
text-align: center;
#map {
height: 100%;
z-index: 9999;
}
.no-scroll {
.scroll-content {
overflow-y: hidden;
}
}
}
- 前往终端并运行应用程序:
$ ionic cordova run ios
$ ionic cordova run android
您可以使用上述任意一条命令行,具体取决于平台。
它是如何工作的...
此应用程序的核心主要在 JavaScript 代码 home.ts 中。为了使用插件对象,您应该在顶部声明它,如下所示:
import {
GoogleMaps,GoogleMap,GoogleMapsEvent,GoogleMapOptions,
CameraPosition,LatLng,MarkerOptions,Marker
} from '@ionic-native/google-maps';
虽然看起来有很多部分在移动,但基本流程非常简单,如下所示:
-
当 Ionic 和 Cordova 准备就绪时,通过在
HomePage构造函数中调用showMap()来初始化地图,触发platform.ready().then。 -
当用户点击按钮时,应用程序将调用
getMyLocation来获取位置数据。 -
这些数据将被用来创建标记并将地图的摄像头移动到该位置的中心。
重要的是要知道 GoogleMaps.create 需要一些时间来处理,并且一旦成功创建地图,它将触发一个 ready 事件。这就是为什么你需要添加一个事件监听器来监听 GoogleMapsEvent.MAP_READY。这个示例在地图准备好后并没有立即做任何事情,但稍后你可以添加更多的处理函数,例如自动跳转到当前位置或向地图上添加更多标记。
当用户点击 PICK ME UP 按钮时,它将触发 getMyLocation() 方法。返回的位置对象将包含纬度 (location.latLng.lat) 和经度 (location.latLng.lng)。要移动摄像头到任何地方,只需通过传递位置坐标 (location.latLng) 调用 map.moveCamera。要添加标记,使用位置和标题作为 HTML 调用 map.addMarker。
更多内容...
Cordova Google Maps 插件还有许多其他功能,例如以下内容:
-
显示
InfoWindow -
添加多行标记
-
修改图标
-
文本样式
-
Base64 编码的图标
-
点击标记
-
点击信息窗口
-
创建可拖动的标记
-
拖动事件
-
创建平面标记
由于你无法在原生 Google Maps 之上弹出 div,标记功能非常实用。一些额外的场景如下:
-
触摸标记并跳转到页面:你只需要监听
GoogleMapsEvent.MARKER_CLICK事件并执行任何操作在回调函数中需要的。
-
将头像/个人资料图片作为标记显示:
addMarker接受 Base64 图像字符串。因此,你可以在参数标题中传递类似这样的内容-canvas.toDataURL()。
注意,Google 对免费 API 使用量有限额。例如,每个用户每秒只能请求一次,每天只能请求几千次。这个限额会不断变化,但了解这一点很重要。无论如何,如果你遇到密钥问题,你必须回到凭证页面并重新生成密钥。为了在应用中手动更改密钥,你必须编辑 /plugins/ios.json。查找以下两个位置:
"*-Info.plist": {
"parents": {
"Google Maps API Key": [
{
"xml": "<string>YOUR_IOS_API_KEY_IS_HERE</string>",
"count": 1
}
]
}
}
以下代码:
"plugin.google.maps": {
"API_KEY_FOR_IOS": "YOUR_IOS_API_KEY_IS_HERE",
"PACKAGE_NAME": "com.ionicframework.starter"
}
你只需要编辑 YOUR_IOS_API_KEY_IS_HERE 这一行,并将其替换为你的新密钥。
有很多种方法可以与 Google Maps 一起工作。你可以访问 Google Maps 插件的 GitHub 页面了解更多信息,网址为 github.com/mapsplugin/cordova-plugin-googlemaps。
第八章:应用主题
在本章中,我们将介绍与应用主题自定义相关的以下任务:
-
查看和调试特定平台的主题
-
根据平台自定义主题
简介
虽然 Ionic 自带一些默认主题,但你可能还想进一步自定义应用的外观和感觉。有以下几种方法:
-
在 Sass 文件中更改样式表
-
在 JavaScript 中检测特定平台的数据类型(iOS、Android、Windows)并应用自定义类或 AngularJS 条件
上述两种方法中的任何一种都应该有效,但强烈建议在构建应用之前,在 Sass 文件中应用自定义,以实现最佳渲染性能。
查看和调试特定平台的主题
开发应用时最大的挑战之一是确保每个平台都有期望的外观和感觉。具体来说,你希望编写一次代码和主题,就能在所有平台上正常工作。另一个挑战是每天都要确定工作流程,从编写代码并在浏览器中预览到将应用部署到设备进行测试。你希望尽量减少不必要的步骤。如果你必须为每个移动平台独立重建应用并测试,这无疑是非常困难的。
Ionic CLI 提供了无缝集成,以改善你的工作流程,确保你可以提前 捕捉 到每个平台的所有问题。你可以在同一个浏览器窗口中快速查看各种平台的应用。这个功能非常强大,因为现在你可以对每个屏幕进行并排比较,并具有特定的交互。如果你想调试 JavaScript 代码,你可以使用在浏览器中一直使用的相同网页开发者工具。这种能力将为你节省大量时间,而不是等待将应用推送到物理设备,如果你的应用变得更大,这可能需要几分钟。
在这个示例中,你将学习如何使用 Sass 变量快速修改主题。然后,你将运行应用并检查不同平台的 UI 一致性。
准备工作
没有必要在物理设备上测试主题,因为 Ionic 可以在浏览器中渲染 iOS、Android 和 Windows Phone。
如何操作...
下面是操作步骤:
- 使用如图所示的
tutorial模板创建一个新的应用,然后进入文件夹:
$ ionic start ThemeApp tutorial
$ cd ThemeApp
在 Ionic 1 中,你需要设置 Sass 依赖项,因为 Ionic 使用了大量的外部库。然而,Ionic 没有这样的要求,因为所有依赖项都是在创建项目时添加的。
- 打开
.../src/theme/variable.scss文件,并用以下命令替换$colors变量:
$colors: (
primary: #2C3E50, // #387ef5,
clear: white,
secondary: #446CB3, // #32db64,
danger: #96281B, // #f53d3d,
light: #BDC3C7, // #f4f4f4,
dark: #6C7A89, // #222,
favorite: #16A085 // #69BB7B
);
默认颜色代码可以像前面代码中那样注释掉。
- 打开
app.html文件,并将clear属性添加到以下代码块中:
<ion-toolbar clear>
<ion-title>Pages</ion-title>
</ion-toolbar>
- 打开
./src/pages/hello-ionic/hello-ionic.html文件,并用给定的代码替换其内容:
<ion-header>
<ion-navbar color="primary">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Hello Ionic</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding class="getting-started">
<h3>Welcome to your first Ionic app!</h3>
<p>
This starter project is our way of helping you get a functional app running in record time.
</p>
<p>
Follow along on the tutorial section of the Ionic docs!
</p>
<p>
<button ion-button color="secondary" menuToggle>Toggle Menu</button>
</p>
</ion-content>
- 在浏览器中测试运行应用,你应该能看到一个屏幕,如下所示:
$ ionic serve -l
-l(lima)命令表示为所有三个平台渲染应用。

它是如何工作的...
Ionic 使得为不同平台开发和使用测试主题变得非常容易。你的典型流程是首先修改 variables.scss 中的主题变量。你不应该直接修改任何 .css 文件。此外,Ionic 项目现在确保你不会意外地编辑错误的核心理念文件,因为这些核心文件不再位于应用文件夹位置。
要更新默认颜色,你只需修改 variables.scss 中的颜色代码即可。你甚至可以添加更多颜色名称,例如 clear: white,Ionic 会自动处理其余部分。这意味着 clear 关键字可以作为值到颜色的属性在任何接受颜色属性的 Ionic 元素上使用。以下是一些示例:
<ion-navbar color="primary">
<button ion-button color="secondary" menuToggle>
<ion-toolbar color="clear">
Ionic CLI 是一个非常有用的工具,可以帮助你在不同平台上调试主题。要获取有关如何使用 Ionic CLI 的帮助,你可以在控制台中输入以下命令行:
$ ionic -h
这将列出所有可用的选项供你选择。在 serve 选项下,你应该熟悉一些重要功能,如下所示:
| 参数 | 描述 |
|---|---|
| `--consolelogs | -c` |
| `--serverlogs | -s` |
| `--browser | -w` |
| `--browseroption | -o` |
| `--lab | -l` |
还有更多...
你可以通过访问 Matheus Cruz Rocha 的克隆存储库来获取更多调色板:github.com/innovieco/ionic-flat-colors。
根据平台定制主题
每个移动平台供应商都有自己的设计指南。本节将介绍一个典型的开发、查看、调试和针对 iOS、Android 和 Windows 手机应用主题的不同工作流程的示例。在传统开发(使用本地语言或其他混合应用解决方案)中,你必须为每个平台维护单独的存储库以定制主题。从长远来看,这可能会非常低效。
Ionic 有许多内置功能来支持基于检测到的平台进行主题更改。通过为每个平台分离 Sass 变量,它使得操作非常方便。这将消除许多不必要的定制。作为开发者,你更愿意专注于应用体验而不是花时间管理平台。
本节中的示例涵盖了使用 Sass 和 JavaScript 的两种可能的定制。以下截图显示了具有不同标题栏颜色和文本的 iOS、Android 和 Windows 应用:

准备工作
没有必要在物理设备上测试主题,因为 Ionic 可以在浏览器中渲染所有三个平台。
如何做到这一点...
这里是说明:
- 使用
blank模板创建一个新应用,并进入项目文件夹:
$ ionic start PlatformStylesApp blank
$ cd PlatformStylesApp
-
打开
./src/app/app.module.ts文件,将整个主体替换为以下:
import { NgModule } from '@angular/core';
import { IonicApp, IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
IonicModule.forRoot(MyApp, {
backButtonText: 'Go Back',
iconMode: 'md',
modalEnter: 'modal-slide-in',
modalLeave: 'modal-slide-out',
tabbarPlacement: 'bottom',
pageTransition: 'ios',
})
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: []
})
export class AppModule {}
这个例子扩展了 Ionic Bootstrap 的使用,现在将进行讨论。
- 打开
./src/pages/home/home.ts并将代码替换为以下:
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
platform: any;
isIOS: Boolean;
isAndroid: Boolean;
isWP: Boolean;
constructor(private navController: NavController, platform:
Platform) {
this.platform = platform;
this.isIOS = this.platform.is('ios');
this.isAndroid = this.platform.is('android');
this.isWP = this.platform.is('windows');
console.log(this.platform);
}
}
- 打开
./src/pages/home/home.html文件,将模板更改为:
<ion-header>
<ion-navbar primary [ngClass]="{'large-center-title': isWP}">
<ion-title>
My Theme
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
Did you see this bulb? It's the same across all platforms.
<p class="center">
<ion-icon class="large-icon" name="bulb"></ion-icon>
</p>
<p *ngIf="isIOS">
Hey iPhone user, can you review this app in App Store?
</p>
<p *ngIf="isAndroid">
Hey Android user, can you review this app in Google Play?
</p>
<p *ngIf="isWP">
Hey Windows Phone user, can you review this app in Marketplace?
</p>
</ion-content>
这是应用唯一的模板,但它的 UI 将根据检测到的平台而有所不同。
- 将
./src/pages/home/home.scss替换为以下样式表:
page-home {
.large-icon {
font-size: 60px;
}
.center {
text-align: center;
}
.header .toolbar[primary] .toolbar-background {
background: #1A2980;
background: -webkit-linear-gradient(right, #1A2980,
#26D0CE);
background: -o-linear-gradient(right, #1A2980, #26D0CE);
background: linear-gradient(to left, #1A2980, #26D0CE);
}
.large-center-title {
text-align: center;
.toolbar-title {
font-size: 25px;
}
}
}
没有必要更改全局变量。因此,你只需修改一个页面的样式。目的是展示为每个平台定制的功能。
- 使用以下命令在浏览器中测试运行应用:
$ ionic serve -l
它是如何工作的...
Ionic 自动创建了平台特定的父类,并将它们放在 <body> 标签中。iOS 应用将包含 .ios 类。Android 应用将包含 .md 类。因此,对于样式表定制,你可以利用这些现有的类来改变应用的外观和感觉。
ionic 文档在 ionicframework.com/docs/v2/theming/platform-specific-styles/ 列出了所有平台模式和配置属性。
| 平台 | 模式 | 详情 |
|---|---|---|
| iPhone/iPad/iPad | ios | iOS 样式用于所有苹果产品 |
| Android | md | md 代表 Material Design,因为这是 Android 设备的默认设计 |
| Windows Phone | wp | 在 Cordova 或 Electron 内的任何 Windows 设备上查看都使用 Windows 样式 |
| 核心 | md | 对于所有其他平台,默认为 Material Design |
首先,让我们看看来自 Ionic Angular 的 Ionic Bootstrap 类。你在 app.module.ts 文件中声明了它:
IonicModule.forRoot(MyApp, {
backButtonText: 'Go Back',
iconMode: 'md',
modalEnter: 'modal-slide-in',
modalLeave: 'modal-slide-out',
tabbarPlacement: 'bottom',
pageTransition: 'ios',
})
这条语句基本上指示应用使用 MyApp 对象进行引导。第二个参数是你可以注入自定义配置属性的地方。所有配置属性列表在 ionicframework.com/docs/v2/api/config/Config/。
这里要指出的一点是 iconMode。在 Ionic 中,每个平台的图标都大不相同。整个 Ionicons 集现在都根据平台名称分开。根据 Ionic 的文档页面,有三个平台,分别是 ionicframework.com/docs/v2/ionicons/。
你甚至可以使用搜索 Ionicons 按钮,如下所示搜索图标名称:

注意,你不需要担心为哪个平台选择哪个图标。尽管在这个例子中,代码强制你为所有三个平台选择 iOS 图标,但你只需使用图标名称,让 Ionic 决定使用哪个图标:

例如,当你指定图标名称为 "add" 时,如果用户正在使用 Android,Ionic 将使用 "md-add",如下所示:
<ion-icon name="add">
</ion-icon>
有几种方式可以根据平台为主题化你的应用。首先,你可以在 HomePage 类中添加变量来检测当前的平台,如下所示:
export class HomePage {
platform: any;
isIOS: Boolean;
isAndroid: Boolean;
isWP: Boolean;
constructor(private navController: NavController, platform: Platform) {
this.platform = platform;
this.isIOS = this.platform.is('ios');
this.isAndroid = this.platform.is('android');
this.isWP = this.platform.is('windows');
console.log(this.platform);
}
}
this.platform = platform 是 Ionic 提供的。如果你在运行应用时打开浏览器控制台,你可以检查 platform 对象:

这个 platform 对象包含大量的信息。这类似于 Ionic 1 中的 ionic.platform。然而,它已经进行了重大重构。
通过将平台变量提供给视图,你可以使用它来通过 ngIf 隐藏或显示特定的 DOM。建议使用 ngIf 而不是 ngShow,因为 ngShow 可能会立即显示和隐藏元素,从而产生 闪烁 效果。以下是与使用这些平台变量相关的模板中的代码:
<p *ngIf="isIOS">
Hey iPhone user, can you review this app in App Store?
</p>
<p *ngIf="isAndroid">
Hey Android user, can you review this app in Google Play?
</p>
<p *ngIf="isWP">
Hey Windows Phone user, can you review this app in Marketplace?
</p>
最后,你可以直接使用平台类来更改主题。考虑以下示例:
.header-md .toolbar[primary] .toolbar-background {
background: #1A2980;
background: -webkit-linear-gradient(right, #1A2980, #26D0CE);
background: -o-linear-gradient(right, #1A2980, #26D0CE);
background: linear-gradient(to left, #1A2980, #26D0CE);
}
这意味着,每当它是材料设计模式(.md 类)时,你将用自己的样式覆盖这些类。前面的例子展示了一个有趣的 CSS 渐变,它在移动设备上工作得非常好。
还有更多...
进一步的设备信息可以通过 Platform 类获得。你甚至可以在 ionicframework.com/docs/v2/api/platform/Platform/ 检测到 iPad 设备。
第九章:高级主题
在本章中,我们将涵盖一些高级主题,如下所示:
-
在 Ionic 中懒加载页面
-
使用
ngx-translate进行国际化(i18n) -
为 Ionic 应用程序创建文档
简介
在本章中,我们将探讨一些与 Ionic 应用程序相关的高级主题,例如为了性能而懒加载页面、深度链接 Ionic 页面,以及添加多语言功能和为 Ionic 应用程序创建文档。
Ionic 中的懒加载
随着时间的推移,Web 应用程序变得越来越庞大和复杂。现在我们有了 Photoshop 的 Web 版本,甚至还有更多复杂的 Web 应用程序。Web 并不是为了这些类型的应用程序而设计的。如果你的应用程序非常大,并且在第一次加载时加载了整个应用程序,那么你的应用程序可能性能不佳。如果你只能加载特定页面/视图所需的 JavaScript 部分,那会怎么样?这就是懒加载的用武之地。懒加载是仅加载用户想要看到的应用程序部分的过程。你可以懒加载的不仅仅是 JavaScript,还可以是 CSS。
在 Angular 中,你可以通过路由配置进行懒加载。然而,Ionic 没有 Angular 路由或路由配置。在本教程中,你将学习如何在 Ionic 中进行懒加载。
准备中
你可以在浏览器中测试你的应用程序,因为懒加载不依赖于设备。
如何实现...
以下是为懒加载的说明:
- 使用
blank模板创建一个新的应用程序LazyLoading并导航到懒加载文件夹,如下所示:
$ ionic start LazyLoading blank
$ cd LazyLoading
- 在
/src/pages/home文件夹内创建一个名为home.module.ts的新文件,并将以下内容添加到其中:
import { NgModule } from "@angular/core";
import { IonicPageModule } from "ionic-angular";
import { HomePage } from "./home";
@NgModule({
declarations: [HomePage],
imports: [IonicPageModule.forChild(HomePage)]
})
export class HomePageModule {}
- 将
IonicPage装饰器添加到 HomePage 组件中,如下所示:
import { Component } from "@angular/core";
import { NavController, IonicPage } from "ionic-angular";
@IonicPage()
@Component({
selector: "page-home",
templateUrl: "home.html"
})
export class HomePage {
constructor(public navCtrl: NavController) {}
}
- 创建一个
/src/pages/second/second.ts文件,并添加以下内容:
import { Component } from "@angular/core";
import { IonicPage, NavController, NavParams } from "ionic-angular";
@IonicPage()
@Component({
selector: "page-second",
templateUrl: "second.html"
})
export class SecondPage {
constructor(public navCtrl: NavController, public navParams: NavParams) {}
}
- 创建一个
/src/pages/second/second.html文件,并添加以下内容:
<ion-header>
<ion-navbar>
<ion-title>Second</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
</ion-content>
- 创建一个
/src/pages/second/second.module.ts文件,并添加以下内容:
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { SecondPage } from './second';
@NgModule({
declarations: [
SecondPage,
],
imports: [
IonicPageModule.forChild(SecondPage),
],
})
export class SecondPageModule {}
- 打开
/src/app/app.component.ts并按照以下方式更新rootPage的值:
rootPage: any = "HomePage";
- 打开
/src/app/app.module.ts并按照以下方式更新它:
import { BrowserModule } from "@angular/platform-browser";
import { ErrorHandler, NgModule } from "@angular/core";
import { IonicApp, IonicErrorHandler, IonicModule } from "ionic-angular";
import { SplashScreen } from "@ionic-native/splash-screen";
import { StatusBar } from "@ionic-native/status-bar";
import { MyApp } from "./app.component";
@NgModule({
declarations: [MyApp],
imports: [BrowserModule, IonicModule.forRoot(MyApp)],
bootstrap: [IonicApp],
entryComponents: [MyApp],
providers: [
StatusBar,
SplashScreen,
{ provide: ErrorHandler, useClass: IonicErrorHandler }
]
})
export class AppModule {}
- 现在,使用以下命令运行应用程序:
$ ionic serve
它是如何工作的...
懒加载的想法与其他任何类型的技术相同。然而,Ionic 的实现与 Angular 的懒加载相比非常不同。
首先,你需要为想要懒加载的 Ionic 页面创建一个功能模块。在我们的例子中,我们创建了 home.module.ts。它看起来如下所示:
import { NgModule } from "@angular/core";
import { IonicPageModule } from "ionic-angular";
import { HomePage } from "./home";
@NgModule({
declarations: [HomePage],
imports: [IonicPageModule.forChild(HomePage)]
})
export class HomePageModule {}
这是一个功能模块,其中声明数组包含 HomePage,我们在导入数组中使用 IonicPageModule.forChild 方法,并将 HomePage 作为输入,以便我们可以在 HomePage 内部访问 Ionic。
其次,我们需要在 home.ts 中使用 IonicPage 装饰器装饰我们的 HomePage 类,如下所示:
...
import { IonicPage } from 'ionic-angular';
@IonicPage()
@Component({..})
export class HomePage {}
最后,我们需要将 HomePage 替换为引号中的 'HomePage' 以实现懒加载。例如,每次我们想要将 HomePage 推送到导航堆栈时,我们会调用 push 函数,如下所示:
navCtrl.push('HomePage'); 而不是:navCtrl.push(HomePage);
此外,我们还需要删除对懒加载页面的任何导入。因此,我们不得不从 app.module.ts 中删除对 HomePage 的引用。
更多...
当你为 Ionic 应用程序配置懒加载时,你也在添加一个额外的功能,即通过 URL 访问页面。结合深度链接 Cordova 插件和这个功能,你可以轻松地为 Ionic 应用程序实现深度链接——把这看作是一个练习。
你可以在 github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking 找到深度链接插件的链接。
参考以下内容
在此博客文章中了解更多关于懒加载的信息:webpack.js.org/guides/lazy-loading/。
使用 ngx-translate 进行国际化 (i18n)
将英语作为应用程序的主要语言是好的。然而,很可能有不知道英语的人会使用你的应用程序。为应用程序提供多种语言是好的。这被称为应用程序的国际化。在本部分中,我们将使用 Angular 的 ngx-translate 库来实现 Ionic 应用程序的多语言功能。
这就是应用程序的外观:

准备工作
你可以在浏览器中运行此应用程序。
如何操作...
以下是如何操作的说明:
- 使用
blank模板创建一个新的TranslateApp并进入文件夹,如下所示:
$ ionic start TranslateApp blank
$ cd TranslateApp
- 按照以下方式安装
ngx-translate/core和ngx-translate/http-loader:
npm install @ngx-translate/core @ngx-translate/http-loader --save
- 在
/src/assets/i18n文件夹内创建en.json文件并添加以下内容:
{
"Hello": "Hello",
"Good Morning": "Good Morning"
}
- 在
/src/assets/i18n文件夹内创建de.json文件并添加以下内容:
{
"Hello": "Hallo",
"Good Morning": "Guten Morgen"
}
- 打开
/src/pages/home/home.html并按照以下方式更新:
<ion-header>
<ion-navbar>
<ion-title>
Ionic Language
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item>
{{'Hello' | translate }}, {{ 'Good Morning' | translate }}
</ion-item>
<ion-item>
<ion-label>Language</ion-label>
<ion-select [(ngModel)]="language" (ionChange)="setLang()">
<ion-option value="en">English</ion-option>
<ion-option value="de">Deutsch</ion-option>
</ion-select>
</ion-item>
</ion-content>
- 打开
/src/pages/home/home.ts并按照以下方式更新:
import { Component } from "@angular/core";
import { NavController } from "ionic-angular";
import { TranslateService } from "@ngx-translate/core";
@Component({
selector: "page-home",
templateUrl: "home.html"
})
export class HomePage {
language: string = "en";
constructor(
public navCtrl: NavController,
private translate: TranslateService
) {
translate.setDefaultLang("en");
translate.use("en");
}
setLang() {
console.log(this.language);
this.translate.use(this.language);
}
}
- 在
/src/app文件夹内打开app.module.ts并按照以下方式更新:
import { BrowserModule } from "@angular/platform-browser";
import { ErrorHandler, NgModule } from "@angular/core";
import { IonicApp, IonicErrorHandler, IonicModule } from "ionic-angular";
import { SplashScreen } from "@ionic-native/splash-screen";
import { StatusBar } from "@ionic-native/status-bar";
import { MyApp } from "./app.component";
import { HomePage } from "../pages/home/home";
import { TranslateModule, TranslateLoader } from "@ngx-translate/core";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { HttpClientModule, HttpClient } from "@angular/common/http";
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
}
@NgModule({
declarations: [MyApp, HomePage],
imports: [
BrowserModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [MyApp, HomePage],
providers: [
StatusBar,
SplashScreen,
{ provide: ErrorHandler, useClass: IonicErrorHandler }
]
})
export class AppModule {}
- 按照以下方式运行应用程序:
$ ionic serve
它是如何工作的...
在此示例中,我们使用 ngx-translate 进行国际化。这是一个相当简单的过程。基本思想是我们为想要在应用程序中支持的所有语言创建一个 JSON 文件。例如,en.json 用于英语,de.json 用于德语。在 JSON 文件中,我们有键和值。值是我们想要在应用程序中显示的翻译文本,而键是我们将要放入应用程序中的内容。
同样重要的是,我们需要为多种语言使用相同的键,并根据翻译使用不同的值。看看以下两个 JSON 文件:
en.json:
{
"Hello": "Hello",
"Good Morning": "Good Morning"
}
de.json:
{
"Hello": "Hallo",
"Good Morning": "Guten Morgen"
}
这两个文件都有两个键值对,键相同但值不同。
现在,在我们的模板中,我们使用这两个键:
{{'Hello' | translate }}, {{ 'Good Morning' | translate }}
我们使用 Angular 插值并在这里添加带有引号的键,以及来自 ngx-translate 库的 translate 管道。基本上,它根据所选语言将 key 转换为正确的 value。
要更改语言,我们使用来自 ngx-translate 的 TranslateService。它有 setDefault 方法来设置默认语言,以及 use 方法来切换语言。所以,在我们的 home.html 中,当用户使用 ion-select 切换语言时,我们调用我们的 setLang 函数,该函数底层调用 TranslateService 的 use 方法,并传入适当的语言代码,如 en 或 de。
最后,我们还需要在 app.module.ts 中配置 ngx-translate。我们需要在应用的根 NgModule 中导入 TranslateModule.forRoot()。我们还得配置 TranslateModule 的加载器。在这里,我们使用 TranslateHttpLoader 从 /assets/i18n/[lang].json 加载翻译,其中 lang code 是字母语言代码,例如 en 代表英语。
重要的是要注意,为了使用 AOT(提前编译),我们需要使用工厂函数,如下所示:
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
}
因此,配置如下所示:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
参考信息
查看关于 ngx-translate 的文档,请访问 github.com/ngx-translate/core。
创建 Ionic 应用的文档
到目前为止,我们已经添加了一些针对应用用户的功能和特性。在这个配方中,我们将添加从源代码生成文档的能力,使用 TSDocs、Gulp 和 Ionic CLI Hooks。文档是开发者体验的一个重要方面。我个人认为,它应该是 Ionic 的默认部分。
这就是文档将呈现的样子:

准备工作
你可以在浏览器中运行此操作。
如何操作...
以下是如何创建示例应用的说明:
- 使用
blank模板创建一个新的DocApp并在文件夹内导航,如下所示:
$ ionic start DocApp blank
$ cd DocApp
- 安装以下
npm开发依赖以使用typedoc:
$ npm install --save-dev gulp gulp-connect gulp-typedoc typedoc
- 在应用的
root目录中创建gulpfile.js并添加以下内容:
var gulp = require("gulp");
var connect = require("gulp-connect");
var typedoc = require("gulp-typedoc");
var config = {
root: "docs/"
};
gulp.task("typedoc", function() {
return gulp.src(["src/**/*.ts"]).pipe(
typedoc({
module: "commonjs",
target: "es6",
experimentalDecorators: true,
out: config.root,
name: "DocApp",
readme: "./README.md"
})
);
});
gulp.task("serve:docs", ["typedoc"], function() {
connect.server({
root: config.root,
livereload: true
});
});
- 打开
/src/pages/home/home.ts并按照以下方式更新:
import { Component } from "@angular/core";
import { NavController } from "ionic-angular";
@Component({
selector: "page-home",
templateUrl: "home.html"
})
export class HomePage {
constructor(public navCtrl: NavController) {}
/**
* Following is the way you write documentation
*
* @param username Username of the user
* @returns It returns a string value
*/
dummyFunction(username: string) {
return username;
}
}
- 运行应用,如下所示:
$ gulp serve:docs
它是如何工作的...
我们正在使用 TypeDoc,这是一个用于 TypeScript 应用的文档生成器。它使用 TSDoc 注释,与流行的 JSDoc 非常相似。所有的魔法工作都是在 gulpfile.js 中完成的。
首先,我们有一个 typedoc 任务。基本上,它使用 gulp.src(["src/**/*.ts"]) 选取所有 TypeScript 源文件并将其管道传输到 typedoc。然后,typedoc 根据文件中的类结构和 TSDoc 注释生成文档。你可以从他们的网站 typedoc.org 了解有关 TypeDoc 的配置信息。基本上,这里我们正在配置站点标题、文档位置以及其他一些事情。
如果你查看 home.ts 文件内部,我们有一个 dummyFunction,其看起来如下:
/**
* Following is the way you write documentation
*
* @param username Username of the user
* @returns It returns a string value
*/
dummyFunction(username: string) {
return username;
}
函数之前的注释是 TSDoc 注释。它们具有特定的结构,我们需要学习和掌握,借助这些注释我们可以生成有意义的文档。
最后,我们在终端中运行一个 gulp 任务,gulp serve:docs,它使用浏览器中的网络服务器生成并提供服务文档。
参见
-
你也可以让它与 Ionic CLI 一起工作,在用 Ionic serve 提供应用服务的同时生成文档。查看
ionicframework.com/docs/cli/configuring.html#hooks中的 Ionic CLI Hooks。 -
在
github.com/Microsoft/tsdoc了解更多关于 TSDoc 的信息。
第十章:为不同平台发布 App
在本章中,我们将涵盖与发布和确保 app 未来兼容性相关的以下任务:
-
为确保应用未来兼容性添加版本控制
-
为 iOS 构建和发布 app
-
为 Android 构建和发布 app
简介
在过去,构建和成功发布一个 app 非常繁琐。然而,如今互联网上有大量的文档和非官方指南,几乎可以解决你可能遇到的所有问题。此外,Ionic 还自带 CLI 来协助这个过程。本章将指导你以高级别了解 app 构建和发布步骤。你将学习以下内容:
-
为确保应用未来兼容性添加版本控制
-
将你的 app 发布到 App Store 或 Google Play
本章的目的是提供一些查找思路和一些注意事项。苹果和谷歌不断更新他们的平台和流程;因此,步骤可能随着时间的推移而有所不同。
为确保应用未来兼容性添加版本控制
通常情况下,你可能不会考虑跟踪特定用户的 app 版本。然而,随着用户数量和发布版本的增多,你很快就会面临更新问题和兼容性问题。例如,一个用户可能运行的是你 app 的旧版本,但你现在所有的后端 API 都期望从新版本中获取新的参数。因此,你可能需要考虑一种策略来本地检测 app 版本,以便通知用户更新需求。这在你后端针对特定 app 版本进行不同处理时也非常有用。
你将要构建的 app 非常简单。它将检测当前版本并将信息存储在服务中。这是 app 的截图:

准备工作
以下 app 示例必须在物理设备或模拟器上运行。
如何操作...
查看以下说明:
- 使用
blank模板创建一个新的MyAppVersionapp,如下所示,并导航到MyAppVersion文件夹:
$ ionic start MyAppVersion blank
$ cd MyAppVersion
- 安装
app-version插件:
$ ionic cordova plugin add cordova-plugin-app-version
$ npm install --save @ionic-native/app-version
- 通过更改版本号来编辑
./config.xml,如下所示:
<widget id="com.ionicframework.myappversion637242" version="0.0.123" >
注意,你的widget id可能与这里提到的不同。你只需要更改版本号。在这种情况下,是0.0.123版本。
- 在
app文件夹内创建services文件夹,如下所示:
$ mkdir ./src/services
- 在
services文件夹中创建myenv.ts文件,并包含以下代码:
import { Injectable } from "@angular/core";
import { AppVersion } from "@ionic-native/app-version";
@Injectable()
export class MyEnv {
constructor(private appVersion: AppVersion) {}
getAppVersion() {
return this.appVersion.getVersionCode();
}
}
这是此 app 的唯一服务。在实际项目中,你可能需要多个服务,因为其中一些将需要直接与你的后端通信。
- 打开并编辑你的
/src/app/app.module.ts,如下所示:
import { BrowserModule } from "@angular/platform-browser";
import { ErrorHandler, NgModule } from "@angular/core";
import { IonicApp, IonicErrorHandler, IonicModule } from "ionic-angular";
import { SplashScreen } from "@ionic-native/splash-screen";
import { StatusBar } from "@ionic-native/status-bar";
import { AppVersion } from "@ionic-native/app-version";
import { MyEnv } from "../services/myenv";
import { MyApp } from "./app.component";
import { HomePage } from "../pages/home/home";
@NgModule({
declarations: [MyApp, HomePage],
imports: [BrowserModule, IonicModule.forRoot(MyApp)],
bootstrap: [IonicApp],
entryComponents: [MyApp, HomePage],
providers: [
StatusBar,
SplashScreen,
AppVersion,
MyEnv,
{ provide: ErrorHandler, useClass: IonicErrorHandler }
]
})
export class AppModule {}
在此文件中的主要修改是注入AppVersion和MyEnv提供者以供整个 app 使用。
- 打开并替换
./src/pages/home/home.html,使用以下代码:
<ion-header>
<ion-navbar>
<ion-title>
MyAppVersion
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding class="center home">
<button ion-button (click)="getVersion()">Get App Version</button>
<p class="large" *ngIf="ver">
MyAppVersion {{ ver }}
</p>
</ion-content>
- 打开并替换
./src/pages/home/home.ts,使用以下代码:
import { Component } from "@angular/core";
import { NavController } from "ionic-angular";
import { MyEnv } from "../../services/myenv";
@Component({
selector: "page-home",
templateUrl: "home.html"
})
export class HomePage {
public ver: string;
constructor(private navCtrl: NavController, public myEnv: MyEnv) {}
getVersion() {
console.log(this.myEnv.getAppVersion());
this.myEnv.getAppVersion().then(data => (this.ver = data));
}
}
- 在同一文件夹中打开并编辑
home.scss:
page-home {
.home {
p.large {
font-size: 16px;
}
}
ion-content {
&.center {
text-align: center;
}
}
}
- 前往你的终端并运行应用。如果你想在物理设备上运行应用,请输入以下命令:
$ ionic cordova run ios
对于 Android,请输入以下命令:
$ ionic cordova run android
它是如何工作的...
简而言之,AppVersion 插件负责所有 繁重的工作。使用 JavaScript,Ionic 应用无法找出其当前代码的版本。你可能认为使用本地存储或 cookie 是一种替代方案,但用户也可以手动删除该存储。为了有一个永久的解决方案,应该使用 AppVersion 插件,因为它可以读取你的 config.xml 文件并为你获取版本。
最佳实践是为所有环境变量创建一个单独的服务。这就是为什么你应该有一个名为 MyEnv 的服务。此外,你应该在 应用级别 注入 MyEnv 作为提供者,因为你只想实例化它一次,而不是每次创建新组件时都进行实例化。查看以下代码:
providers: [MyEnv]
由于所有 AppVersion 方法都是基于 promise 的,你应该将整个对象作为 promise 返回。让我们看看你的 myenv.ts 文件中的 getAppVersion() 方法:
getAppVersion() {
return this.appVersion.getVersionCode();
}
然后,在你的页面文件中,例如 home.ts,你应该按照以下示例调用 getAppVersion 方法,并使用 .then() 方法获取以下结果:
getVersion() {
console.log(this.myEnv.getAppVersion());
this.myEnv.getAppVersion().then((data) => this.ver = data);
}
如果你打开控制台来检查 promise 对象,你会看到它包含你的应用版本值和 .then() 方法。查看以下截图:

关于 AppVersion 插件的更多信息,你可能想参考官方 AppVersion 文档,网址为 github.com/whiteoctober/cordova-plugin-app-version。
构建 iOS 应用并发布
如果你没有提前做好准备,发布到 App Store 可能会是一个令人沮丧的过程。在本节中,我们将逐步介绍在 Apple 开发者中心、iTunes Connect 和你的本地 Xcode 项目中正确配置所有内容的步骤。
准备工作
为了访问 developer.apple.com/macos/touch-bar/ 和 itunesconnect.apple.com,你必须注册 Apple 开发者计划,因为那些网站将需要经过批准的账户。
此外,你还应该有以下内容:
-
macOS
-
Xcode
如何操作...
以下是为确保一切配置正确的说明:
- 确保你处于应用文件夹中,然后构建 iOS 平台:
$ ionic cordova build ios
前往 /platforms/ios 文件夹,并在 Xcode 中打开 .xcodeproj 文件。查看以下截图:

- 导航到“通用”选项卡,如以下截图所示,以确保你拥有所有正确的信息,特别是包标识符和版本。如有需要,进行更改并保存:

- 访问苹果开发者网站,点击“证书、标识符和配置文件”,如图所示:

- 选择您要针对的正确设备平台;在这种情况下,将是 iOS、tvOS、watchOS:

- 对于 iOS 应用,您需要证书、应用 ID、测试设备和配置文件。要从证书开始,导航到“证书 | 所有”,如下所示:

- 点击以下截图所示的加号(+)按钮:

-
您必须按照网站上的步骤填写必要的信息,如图所示:
![图片]()
-
完成表单后,您可以保存 CSR 文件并将其导入到 Mac 的密钥链访问中。
-
按照以下步骤导航到“标识符 | 应用 ID”以创建应用 ID:

- 点击屏幕右上角的加号按钮,如下所示:

- 填写以下截图所示的应用 ID 注册表单:

- 在这里,您需要正确完成的重要部分是包标识符,如图所示,因为它必须与 Xcode 中的包标识符匹配:

- 如果您的应用需要推送通知或其他应用服务,您需要在以下页面上检查这些服务:

- 如果您需要将应用推送到特定设备,您必须注册该设备。导航到“设备 | 所有”,如图所示:

- 点击加号按钮,如图所示:

- 提供以下设备的 UDID 并保存,以注册设备:

-
最后,如果您还没有配置文件,您将需要一个配置文件。通常,Xcode 会自动创建一个。但是,您可以按照以下步骤创建自己的配置文件:导航到“配置文件 | 所有”,如图所示:
![图片]()
-
点击以下截图所示的加号按钮:

- 选择“应用商店”作为您的配置文件,如图所示:

- 从下拉菜单中选择正确的应用 ID 并保存,以完成配置文件创建,如下所示:

- 访问
itunesconnect.apple.com上的 iTunes Connect,并点击“我的应用”按钮,如下所示:

- 选择加号 (+) 图标,然后选择新建应用,如下所示:

- 填写表格,并确保您选择了您应用的正确 Bundle ID:

-
提供有关应用的额外步骤,例如截图、图标和地址。如果您只想测试应用,您可以最初提供一些占位符信息,稍后再回来编辑。
准备您的开发者账号和 iTunes Connect 账号就到这里。
现在,打开 Xcode 并选择 iOS 设备作为存档目标,否则存档功能将不会开启。在您可以将应用提交到 App Store 之前,您需要存档您的应用!
![图片]()
-
如上图所示,在顶部菜单导航到产品 | 存档:
![图片]()
-
存档过程完成后,选择提交到 App Store 以完成发布过程。
-
要发布,请选择提交 Beta 应用审核。您可能还想浏览其他标签,例如定价和内购,以配置您自己的要求。
它是如何工作的...
显然,本节并没有涵盖发布过程中的所有细节。一般来说,您只需确保在提交到 App Store 之前,在物理设备(通过 USB 或 TestFlight)上彻底测试您的应用。
如果由于某种原因,存档功能无法构建,您可以手动前往您本地的 Xcode 文件夹,删除该特定临时存档应用以清除缓存,如下所示:
~/Library/Developer/Xcode/Archives
还有更多...
-
TestFlight 是一个独立的话题。TestFlight 的好处是您不需要苹果批准您的应用就可以在物理设备上进行测试和开发。更多关于 TestFlight 的信息请见
developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/BetaTestingTheApp.html. -
测试 Ionic 应用还有另一种方法,那就是使用 Ionic 的 DevApp。它允许您在无需编译的情况下在 iOS 和 Android 上测试 Ionic 应用。您已在手机上安装了 Ionic Devapp,并在工作站中在终端运行
ionic serve -c。更多详情请见ionicframework.com/docs/pro/devapp/。
构建 Android 应用并进行发布
构建 Android 应用并进行发布比 iOS 更直接,因为您只需与命令行接口来构建 .apk 文件并将其上传到 Google Play 开发者控制台。
Ionic 框架文档也为此提供了很好的说明页面,请见 ionicframework.com/docs/guide/publishing.html。
准备工作
要求是你必须准备好你的 Google 开发者账户,然后登录到play.google.com/apps/publish.
你的本地环境也应该有正确的 SDK 和keytool、jarsigner和zipalign命令行,针对该特定版本。
如何操作...
以下是指令:
- 进入你的应用文件夹,使用以下命令为 Android 构建:
$ ionic cordova build --release android
- 你会在
/platforms/android/build/outputs/apk文件夹中注意到android-release-unsigned.apk。在终端中转到该文件夹:

- 如果这是你第一次创建此应用,你必须有一个
keystore文件。此文件用于在发布时识别你的应用。如果你丢失了它,你以后将无法更新你的应用。要创建keystore文件,请输入以下命令行,并确保它与 SDK 的keytool版本相同:
$ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
-
一旦你在命令行中填写了信息,请将此文件复制到某个安全的地方,因为你稍后还需要它。
-
下一步是使用该文件来签名你的应用,以便它将创建一个 Google Play 允许用户安装的新
.apk文件:
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore HelloWorld-release-unsigned.apk alias_name
- 在上传最终的
.apk之前,你必须使用zipalign打包它,如下所示:
$ zipalign -v 4 HelloWorld-release-unsigned.apk HelloWorld.ap
你需要确保zipalign在PATH中,或者你必须指定绝对路径。应用名称可以是任何你喜欢的,或者你可以使用本章中创建的相同名称。
- 登录到 Google 开发者控制台,然后点击创建应用程序按钮,如下所示:

- 在打开的弹出窗口中填写应用程序的标题,然后点击下面的创建按钮,如图所示:

- 使用左侧菜单填写你的应用所需的商店列表和其他信息:

- 现在,你已经准备好上传你的
.apk文件。你需要做的第一件事是进行 Beta 测试:

- 一旦你完成了 Beta 测试,你可以按照开发者控制台的说明将应用推送到生产环境。
如果你在发布应用时遇到任何问题,查看仪表板右上角的“为什么我无法发布?”链接可能会有所帮助。Google 将指导你完成必须完成或修复的特定步骤。
它是如何工作的...
本节不涵盖其他 Android 市场,如 Amazon 应用商店,因为每个市场都有不同的流程。然而,共同的想法是,你需要完全构建.apk的未签名版本,使用现有的或新的keystore文件进行签名,最后使用zipalign准备上传。
在support.google.com/googleplay/android-developer/answer/113469?hl=en了解更多关于上传应用程序以发布的信息


















为你的客户端选择一个名称,并将客户端类型选择为 Native。
在应用中稍后需要域名和客户端 ID,所以请将它们保存在某个地方。




































浙公网安备 33010602011771号