Ionic2-秘籍-全-

Ionic2 秘籍(全)

原文:zh.annas-archive.org/md5/f77b685168acd54f420f83ed2bc1f6a7

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

移动开发的世界是碎片化的,有许多平台、框架和技术。Ionic 旨在填补这一空白,它是一个开源的 HTML5 移动应用框架,允许开发者使用 HTML、CSS 和 AngularJS 等 Web 技术构建具有原生感觉的应用。Ionic 使前端开发者能够轻松成为应用开发者。该框架通过深度 Cordova 集成和一套全面的工具(用于原型设计、后端支持和部署)提供了卓越的性能。

这本书将带你通过使用基于 Ionic 2 的 HTML5 和 JavaScript 开发跨平台移动应用的过程,Ionic 2 是使用 Angular 2 框架的最新版本。你将从熟悉 CLI 和了解如何构建和运行应用开始。你将接触到现实世界移动应用的常见功能,例如使用 Firebase 或本地存储进行用户认证、获取数据和保存数据。接下来,本书将解释 Ionic 如何通过 ngCordova 与 Cordova 集成以支持原生设备功能,并利用其生态系统中的现有模块。你还将探索扩展 Ionic 以创建新组件的高级主题。最后,本书将展示如何自定义 Ionic 主题并为所有平台构建应用。

本书涵盖的内容

第一章,使用 Ionic 2 创建我们的第一个应用,介绍了 Ionic 2 框架,并提供了如何设置开发环境以及快速创建和运行第一个应用的说明。

第二章,添加 Ionic 2 组件,通过一些示例指导你如何管理应用中的页面、状态以及整体导航。

第三章,使用 Angular 2 构建块扩展 Ionic 2,深入探讨了 AngularJS 组件、指令以及管道的自定义。你将学习如何利用 Ionic 2 模块架构创建共享服务。

第四章,验证表单和发送 HTTP 请求,解释了如何创建一个具有输入验证的复杂表单,通过 REST API 调用检索数据,并与 Stripe 集成进行在线支付。

第五章,添加动画,提供了如何将视频嵌入为背景、创建基于物理的 CSS 动画以及将手势绑定到动画状态的说明。

第六章,使用 Ionic Cloud 进行用户认证和推送通知,深入探讨了使用 Ionic Cloud 进行用户注册和认证以及发送和接收推送通知。

第七章,使用 Ionic Native 支持设备功能,解释了如何使用 Ionic Native 访问原生设备功能,例如相机、社交分享、InAppBrowser 和地图。

第八章,为应用主题化,提供了使用 Sass 变量定制不同平台应用的说明。

第九章,为不同平台发布应用,探讨了执行应用发布的最终步骤的过程。

您需要为本本书准备的内容

  • 需要配备 Mac OS X El Capitan 和 root 权限的 Mac 电脑

  • iPhone 5 或更高版本

  • 任何运行 Android 5.x 或更高版本的 Android 设备(可选)

  • 任何 Windows Phone 设备(可选)

本书面向的对象

Ionic 2 食谱旨在帮助前端开发者利用现有技能开发跨平台移动应用。本书将帮助您通过深入探讨 AngularJS、Cordova 和 Sass,成为中级或高级 Ionic Framework 开发者。由于 Ionic 是开源的,因此有一个庞大的社区支持这个框架,帮助您继续学习之旅。

部分

在本书中,您将找到一些频繁出现的标题(准备就绪、如何操作、工作原理、更多信息、相关内容)。

为了清楚地说明如何完成食谱,我们使用以下这些部分:

准备就绪

本节将向您介绍在食谱中可以期待的内容,并描述如何设置任何软件或任何为食谱所需的初步设置。

如何操作...

本节包含遵循食谱所需的步骤。

工作原理...

本节通常包含对前一个章节发生情况的详细解释。

更多信息…

本节包含有关食谱的附加信息,以便使读者对食谱有更深入的了解。

相关内容

本节提供了对其他有用信息的链接,以帮助读者了解食谱。

习惯用法

在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名如下所示:"除非需要进行故障排除,否则无需手动修改/platforms/plugins文件夹。"

代码块设置如下:

<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>

任何命令行输入或输出如下所示:

$ sudo npm install -g cordova ionic ios-sim

新术语重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,以这种方式显示:“在本节中,您将学习如何创建一个属性指令,该指令可以防止某些字符在用户名中输入,并通过切换其可见性来显示另一个 DOM 节点(显示为您正在输入用户名)。”

注意

警告或重要注意事项以这种方式出现在框中。

小贴士

小贴士和技巧看起来像这样。

读者反馈

我们欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。

要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书籍标题。

如果你在某个领域有专业知识,并且对撰写或参与一本书籍感兴趣,请参阅我们的作者指南,网址为www.packtpub.com/authors

客户支持

现在你已经是 Packt 图书的骄傲拥有者了,我们有一些事情可以帮助你从购买中获得最大收益。

下载示例代码

您可以从您的账户中下载本书的示例代码文件,网址为www.packtpub.com。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的支持标签上。

  3. 点击代码下载与勘误

  4. 搜索框中输入书籍名称。

  5. 选择您想要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买此书的来源。

  7. 点击代码下载

你也可以通过点击 Packt Publishing 网站上书籍网页上的代码文件按钮来下载代码文件。您可以通过在搜索框中输入书籍名称来访问此页面。请注意,您需要登录您的 Packt 账户。

文件下载完成后,请确保您使用最新版本的软件解压缩或提取文件夹:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Ionic-2-Cookbook。我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。去看看吧!

下载本书的颜色图像

我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。这些彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/Ionic2Cookbook_ColorImages.pdf下载此文件。

勘误

尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。

要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。

侵权

互联网上对版权材料的侵权是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过链接mailto:copyright@packtpub.com与我们联系,并提供涉嫌侵权材料的链接。

我们感谢您的帮助,保护我们的作者以及我们为您提供有价值内容的能力。

问题

如果您对本书的任何方面有问题,您可以通过链接mailto:questions@packtpub.com与我们联系,我们将尽力解决问题。

第一章. 使用 Ionic 2 创建我们的第一个应用

在本章中,我们将涵盖以下主题:

  • 设置开发环境

  • 通过 CLI 创建 HelloWorld 应用

  • 通过 Ionic Creator 创建 HelloWorld 应用

  • 使用您的网络浏览器查看应用

  • 使用 Ionic CLI 查看应用

  • 使用 Xcode for iOS 查看应用

  • 使用 Genymotion for Android 查看应用

  • 使用 Ionic View 查看应用

简介

现在开发移动应用程序有很多选择。原生应用程序需要对每个平台(如 iOS、Android 和 Windows phone)进行独特的实现。在一些情况下,如高性能 CPU 和 GPU 处理以及大量内存消耗,这是必需的。任何不需要过度图形和密集 CPU 处理的应用程序都可以从成本效益、一次编写到处运行 HTML5 移动实现中受益。

对于选择 HTML5 路线的开发者来说,在这个活跃的市场中有许多不错的选择。一些选项可能很容易开始,但它们可能很难扩展或可能面临性能问题。商业选项通常对小开发者来说成本很高,难以发现产品和市场匹配。最佳实践是首先考虑用户。有些情况下,一个简单的响应式设计网站是一个更好的选择;例如,当业务主要包含固定内容且更新需求最小或内容更适合用于 SEO 时。

如下所示,Ionic 框架相对于其竞争对手具有几个优势:

  • 它是基于 AngularJS 编写的。Ionic 1.x 基于 AngularJS 1.x,而 Ionic 2.0 则基于 AngularJS 2.0。

  • 由于使用了 requestAnimationFrame() 技术,UI 性能强大。

  • 它提供了一套美丽且全面的默认样式,类似于以移动设备为中心的 Twitter Bootstrap。

  • Sass 可用于快速、简单、有效的主题定制。

AngularJS 1.x 和 2.0 的发布之间发生了许多重大变化。所有这些变化都适用于 Ionic 2。考虑以下示例:

  • AngularJS 2.0 使用 TypeScript,它是 ECMAScript 6ES6)标准的超集,将您的代码编译成 JavaScript。这允许开发者在编译步骤中利用 TypeScript 的功能,如类型检查。

  • 在 AngularJS 中将不再有控制器和指令。以前,控制器被分配给 DOM 节点,而指令将模板转换为类似组件的架构。然而,由于控制器和/或指令冲突的问题,大型 AngularJS 1.x 应用程序的扩展和调试非常困难。迁移到 AngularJS 2.0 后,只有一个组件的概念,它最终有一个与 HTML 模板对应的选择器和一个包含函数的类。

  • 在 AngularJS 2.0 中,$scope对象将不再存在,因为所有属性现在都在组件内部定义。这实际上是个好消息,因为在 AngularJS 1.x 中调试$scope中的错误(尤其是嵌套场景)非常困难。

  • 最后,AngularJS 2.0 承诺将拥有更好的性能,并支持 ES5 和 ES6 标准。你可以使用 TypeScript、Dart 或纯 JavaScript 来编写 AngularJS 2.0。

在本章中,你将学习几个 HelloWorld 示例来 Bootstrap 你的 Ionic 应用。这个过程将为你提供一个快速骨架,以便开始构建更全面的应用。大多数应用都有类似的用户体验流程,例如标签和侧边菜单。

设置开发环境

在你创建第一个应用之前,你的环境必须准备好所需的组件。这些组件确保开发、构建和测试过程顺利。默认的 Ionic 项目文件夹基于 Cordova 的。因此,你需要 Ionic CLI 来自动添加正确的平台(即 iOS、Android 或 Windows 手机)并构建项目。这将确保所有 Cordova 插件都正确包含。该工具有许多选项,可以在浏览器或模拟器中运行你的应用并实现实时刷新。

准备工作

你需要安装 Ionic 及其依赖项以开始。Ionic 本身只是一个 CSS 样式、AngularJS 组件和标准 Cordova 插件的集合。它也是一个命令行工具,用于帮助管理所有技术,如 Cordova 和 Bower。安装过程将为你提供一个命令行来生成初始代码并构建应用。

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 用于 iOS

  • Android Studio 用于 Android

  • 微软 Visual Studio Code (VS Code)

  • Sublime Text (www.sublimetext.com/)用于 Web 开发

所有这些都有免费许可。您可以直接在 Xcode 或 Android Studio 中编码,但它们对于 Web 应用来说有些重量级,尤其是在您有很多窗口打开且只需要简单编码时。Sublime Text 对非商业开发者免费,但如果您是商业开发者,则必须购买许可证。大多数前端开发者更喜欢使用 Sublime Text 进行 HTML 和 JavaScript 编码,因为它非常轻量级,并拥有一个支持良好的开发者社区。Sublime Text 已经存在很长时间,并且非常用户友好。然而,Ionic 2 中有许多功能使 Visual Studio Code 非常有吸引力。例如,它具有完整 IDE 的外观和感觉,但又不笨重。您可以直接在 VS Code 中调试 JavaScript,以及获取自动完成(例如,IntelliSense)。以下说明涵盖了 Sublime Text 和 VS Code,尽管本书的其余部分将使用 VS Code。

如何操作…

VS Code 在 Mac、Windows 和 Linux 上运行。以下是说明

  1. 访问code.visualstudio.com

  2. 下载并为您特定的操作系统安装。

  3. 解压下载的文件。

  4. .app文件拖入Applications文件夹,并将其拖到 Mac 的 Dock 上。

  5. 打开 Microsoft Visual Studio code。

  6. Ctrl + Shift + p 打开命令面板。

  7. 在命令面板中输入shell command

  8. 点击Shell Command: 在 PATH 中安装'code'命令命令,将脚本安装到您的终端$PATH中。

  9. 重新启动 Visual Studio Code 以生效。

  10. 之后,您可以直接从 Ionic 项目文件夹中执行code.(包括点)直接打开该文件夹作为项目:

    注意

    注意,以下截图是通过 Mac 完成的。

    如何操作…

  11. 如果您决定使用 Sublime Text,您将需要 Package Control (packagecontrol.io/installation),它类似于插件管理器。由于 Ionic 使用 Sass,安装 Sass 语法高亮包是可选的。

  12. 导航到Sublime Text | 首选项 | Package Control如何操作…

  13. 前往Package Control: 安装包。您也可以只输入命令的部分内容(即,inst),它将自动选择正确的选项:如何操作…

  14. 输入Sass,搜索结果将显示一个针对TextMate & Sublime Text的选项。选择该选项进行安装:如何操作…

还有更多…

有许多 Sublime Text 包您可能想要使用,例如 HTML、JSHint、JSLint、Tag 和 ColorPicker。您可以通过sublime.wbond.net/browse/popular获取更多需求。

通过 CLI 创建 HelloWorld 应用

使用现有的模板启动应用是最快的。Ionic 通过命令行提供了以下三个标准模板:

  • 空白:这是一个带有最小 JavaScript 代码的简单页面。

  • 标签页:这些是带有路由的多页。一个路由 URL 会跳转到标签页。

  • 侧边菜单:这是一个带有左右菜单和中心内容区域的模板。

如何做到这一点…

  1. 要使用 Ionic 从空白模板设置应用,使用以下命令:

     $ ionic start HelloWorld_Blank blank --v2
    
    

    注意

    如果你没有在ionic.io上的账户,命令行会要求你创建。你可以按yn继续。在这个步骤中拥有账户不是强制性的。

  2. 如果你用制表符替换空白,它将创建一个标签模板,如下所示:

     $ ionic start HelloWorld_Tabs tabs
    
    
  3. 类似地,以下命令将创建一个带有侧边菜单的应用:

     $ ionic start HelloWorld_Sidemenu sidemenu --v2
    
    

侧边菜单模板是最常见的模板,因为它提供了一个非常好的路由示例,其中包含/app/pages文件夹中的不同页面。

在 GitHub 页面github.com/driftyco/ionic-cli上提供了关于 Ionic CLI 的额外指导。

它是如何工作的…

本章将向你展示如何快速启动你的代码库并可视化结果。关于 AngularJS 2.0 及其模板语法的更多细节将在本书的各个章节中讨论。然而,核心概念如下:

  • 组件:AngularJS 2.0 非常模块化,因为你可以将代码写在文件中,并使用导出类将其转换为组件。如果你熟悉 AngularJS 1.x,这类似于控制器以及它与 DOM 节点的绑定。组件将拥有自己的私有和公共属性和方法(即函数)。要判断一个类是否是 AngularJS 组件,你必须使用@Component装饰器。这是 TypeScript 中的另一个新概念,因为你可以对任何类施加特性(元数据),使它们以某种方式行为。

  • 模板:模板是一个 HTML 字符串或一个单独的.html文件,它告诉 AngularJS 如何渲染组件。这个概念与其他任何前端和后端框架非常相似。然而,AngularJS 2.0 有自己的语法,允许在 DOM 上执行简单的逻辑,例如重复渲染(*ngFor)、事件绑定(点击)或自定义标签(<my-tag>)。

  • 指令:这允许你操作 DOM,因为指令绑定到了一个 DOM 对象上。所以,*ngFor*ngIf就是指令的例子,因为它们会改变该 DOM 的行为。

  • 服务:这指的是管理模型或复杂逻辑集合的抽象,除了 get/set 所需之外。没有像组件那样的服务装饰器。所以,任何类都可以是服务。

  • 管道:这主要用于在模板中处理表达式并返回一些数据(即四舍五入数字和添加货币),使用{{ expression | filter }}格式。例如,{{amount | currency}}如果amount变量是100,将返回$100

Ionic 会自动创建一个项目文件夹结构,其结构如下所示:

如何工作…

你将大部分时间花在/app文件夹中,因为你的应用组件将放置在这里。这与 Ionic 1.x 非常不同,因为这里的/www文件夹实际上是由 TypeScript 编译的。如果你为 iOS 构建应用,Ionic 构建命令行也会在/platforms/ios/www创建另一个副本,这是专门为 Cordova 指向的。AngularJS 2.0 中的另一个有趣的变化是,所有自定义 JS 和 CSS 文件都放置在同一个子文件夹或/app/pages中。由于 AngularJS 2.0 是基于组件的,每个组件都会包含 HTML、CSS 和 JS。如果你添加更多的 JavaScript 模块,你可以将它们放在/app文件夹中,或者更好的做法是使用npm install,这样它就会自动添加到/node_modules文件夹中。Ionic 2 完全摒弃了 Grunt 和 Bower。所有内容都简化为package.json,其中将列出你的第三方依赖项。

没有必要手动修改/platforms/plugins文件夹,除非需要进行故障排除。否则,Ionic 或 Cordova CLI 将自动化这些文件夹中的内容。

默认情况下,从 Ionic 模板中,AngularJS 应用名称被称作MyApp。你会在app.js中看到类似的内容,这是整个应用的启动文件:

如何工作…

这充当着你的应用目录,所有内容都将注入到index.html中的<ion-app></ion-app>内。

注意,如果你双击index.html文件在浏览器中打开它,会显示一个空白页面。这并不意味着应用没有工作。原因是 Ionic 的 Angular 组件会动态加载所有的.js文件,这种行为需要通过http://协议进行服务器访问。如果你在本地打开文件,浏览器会自动将其视为文件协议(file://),因此 Angular 将无法加载额外的.js模块以正确运行应用。有几种运行应用的方法,稍后将会讨论。

通过 Ionic Creator 创建 HelloWorld 应用

另一种开始你的应用代码库的方法是使用Ionic Creator。这是一个优秀的界面构建器,可以通过拖放风格加速你的应用开发。你可以快速将现有的组件拖放到基于 Web 的界面中,以可视化它们在应用中的样子。最常见的组件,如按钮、图片和复选框,都是可用的。

Ionic Creator 允许用户将所有.html.css.js文件作为一个项目导出。你应该能够编辑/app文件夹中的内容,以构建在界面之上。

准备工作

使用 Ionic Creator 开始之前,需要在creator.ionic.io/注册一个免费账户。

如何操作…

  1. 创建一个名为myApp的新项目:如何操作…

  2. 验证以确保你看到以下屏幕:如何操作…

    中心区域是你的应用界面。左侧为你提供了一个页面列表。每个页面是一个单独的路由。你还可以访问许多 UI 组件,你通常需要在 html 文件中手动编码。右侧面板显示了任何选中组件的属性。

    你可以在这里自由地做任何需要的事情,只需将组件拖放到中心屏幕即可。如果你需要创建一个新页面,你必须点击页面面板中的加号。每个页面都表示为链接,这基本上是 Angular UI Router 定义中的一个路由。要导航到另一个页面(例如,点击按钮后),你只需更改链接属性并指向该页面。

    顶部有一个编辑按钮,你可以在此之间切换编辑模式和预览模式。看到你的应用将如何看起来和表现是非常有用的。

  3. 完成后,点击导航栏顶部的导出按钮。你有以下四个选项:

    • 使用 Ionic CLI 工具获取代码

    • 将项目作为 ZIP 文件下载

    • 将其导出到原生代码(类似于 PhoneGap Build),如图所示:如何操作…

    • 使用 Creator 应用将其导出到预览模式,如下所示:

    如何操作…

学习 Ionic Creator 的最佳方式是亲自尝试。你可以添加一个新页面并选择任何现有的模板。以下示例显示了登录页面模板:

如何操作…

这是它应该看起来的样子(导出或下载后):

如何操作…

还有更多…

要切换到预览模式,在那里你可以看到设备模拟器中的 UI,请点击右上角的切换按钮以启用测试,如图所示:

还有更多…

在这种模式下,你应该能够像它们实际部署在设备上一样,在网页浏览器中与组件进行交互。

如果你弄坏了某些东西,重新开始一个新项目非常简单。这是一个用于原型设计和获取初始模板或项目框架的出色工具。你应该继续在常规 IDE 中编码其余的应用程序。Ionic Creator 目前还不能为你做所有事情。例如,如果你想访问特定的 Cordova 插件功能,你必须单独编写那段代码。

此外,如果你想在 Ionic Creator 允许的范围之外调整界面,也需要对.html.css文件进行特定的修改。

使用你的网页浏览器查看应用

为了运行网络应用,您需要将您的 /www 文件夹变成一个网络服务器。再次强调,有很多人方法可以做到这一点,人们倾向于坚持一两种方法以保持简单。一些其他选项可能不可靠,例如 Sublime Text 的实时观察包或静态页面生成器(例如,Jekyll 和 Middleman 应用)。它们检测更改的速度很慢,可能会冻结您的 IDE。因此,这里不会提及这些。

准备工作

推荐的方法是使用 ionic serve 命令行。它基本上启动了一个 HTTP 服务器,这样您就可以在桌面浏览器中打开您的应用。

如何操作…

  1. 首先,您需要处于 project 文件夹中。假设它是 Side Menu HelloWorld:

     $ cd HelloWorld_Sidemenu
    
    
  2. 从那里,只需发出简单的命令行,如下所示:

     $ ionic serve
    
    

就这样!您不需要进入 /www 文件夹或找出要使用的端口号。当网络服务器运行时,命令行将提供以下选项:

如何操作…

这里最常用的选项是 r 用于重启或 q 用于完成工作后退出。

查看具有正确设备分辨率的应用的额外步骤:

  1. 如果您的电脑上还没有安装 Google Chrome,请安装它。

  2. 在 Google Chrome 中打开 ionic serve 中的链接(例如,http://localhost:8100/)。

  3. 打开开发者工具。例如,在 Mac 的 Google Chrome 中,导航到 查看 | 开发者 | 开发者工具如何操作…

  4. 在 Chrome 开发者工具区域单击小型的移动图标,如图所示:如何操作…

  5. 将会显示一个长长的设备列表供您选择,如下所示:如何操作…

  6. 选择设备后,您需要刷新页面以确保 UI 已更新。Chrome 应该会给出设备的精确视图分辨率。如何操作…

大多数开发者更喜欢使用这种方法进行编码,因为您可以使用 Chrome 开发者工具调试应用。它的工作方式与任何其他网络应用完全相同。您可以在控制台创建断点或输出变量。

它是如何工作的…

注意,ionic serve 实际上正在监视 /app 文件夹下的所有内容,并在 /www 下即时将 TypeScript 代码编译成 JavaScript。这很有道理,因为系统不需要扫描每个文件,因为其更改的概率非常小。

当网络服务器运行时,您可以返回到 IDE 并继续编码。例如,让我们打开 page1.html 或任何其他模板文件,并将其第一行更改为以下内容:

<ion-view view-title="Updated Playlists">

返回到 Ionic 打开新页面的网页浏览器;应用界面会立即更改标题栏,无需您刷新浏览器。当需要在代码更改和检查应用的实际工作或外观之间来回切换时,这是一个非常棒的功能。

使用 Ionic CLI 查看应用

到目前为止,你已经测试了 Ionic 的 Web 应用程序部分。大多数时候,你需要实际上在物理设备或至少在模拟器上运行应用程序,以查看应用程序的行为以及所有原生功能是否正常工作。

准备工作

你需要安装模拟器。iOS 模拟器在执行npm install -g ios-sim时安装,Android 模拟器随 Android Studio 一起安装。要在物理设备上测试应用程序,你必须通过 USB 连接将设备连接到你的计算机。

如何做到这一点...

  1. 使用以下命令行添加特定的平台(例如 iOS)并构建应用程序:

     $ ionic platform add ios
     $ ionic build ios
    
    

    注意

    注意,在构建应用程序之前,你需要执行platform add。然而,如果你使用 Ionic CLI 的标准模板,它应该已经包含了 iOS 平台。要为 Android 构建和运行,你可以将 iOS 替换为 Android。

  2. 要使用 iOS 模拟器模拟应用程序,请使用以下命令行:

     $ ionic emulate ios
    
    
  3. 要在真实的物理 iPhone 设备上运行应用程序,请使用以下命令行:

     $ ionic run ios --device
    
    

使用 Xcode 为 iOS 查看应用程序

你也可以使用 Mac 上的 Xcode 运行应用程序。

如何做到这一点...

  1. 前往/platforms/ios文件夹。

  2. 查找包含.xcodeproj的文件夹,并在 Xcode 中打开它。

  3. 点击 iOS 设备图标,并选择你选择的 iOS 模拟器:如何做到这一点…

  4. 点击运行按钮,你应该能在模拟器中看到应用程序正在运行。

还有更多…

你可以通过 USB 端口连接一个物理设备,它将出现在 iOS 设备列表中供你选择。然后,你可以在你的设备上直接部署应用程序。请注意,此方法需要 iOS 开发者会员资格。这种方法比仅通过网页浏览器查看应用程序更复杂。

然而,当你想要测试与设备功能相关的代码时,例如相机或地图,这是必须的。如果你在/app文件夹中更改代码并想在 Xcode 中再次运行它,你必须首先执行ionic build ios,因为正在运行的代码位于你的 Xcode 项目的Staging文件夹中,如图所示:

还有更多…

对于调试,Xcode 控制台也可以输出 JavaScript 日志。然而,你可以使用 Safari 的Web Inspector(类似于 Google Chrome 的开发者工具)的高级功能来调试你的应用程序。请注意,只有 Safari 可以调试在连接的物理 iOS 设备上运行的 Web 应用程序,因为 Chrome 在 Mac 上不支持这一点。启用此功能很容易,可以通过以下步骤完成:

  1. 通过导航到设置 | Safari | 高级并启用Web Inspector来允许 iOS 设备进行远程调试:还有更多…

  2. 通过 USB 将物理 iOS 设备连接到你的 Mac 并运行应用程序。

  3. 打开 Safari 浏览器。

  4. 选择开发 | 你的设备名称(或 iOS 模拟器) | index.html,如图所示:还有更多…

    注意

    如果你没有在 Safari 中看到 Develop 菜单,你需要导航到 Preferences > Advanced 并勾选 Show Develop menu in menu bar

Safari 将为该特定设备打开一个新的控制台,就像它在计算机的 Safari 中运行时一样。

使用 Genymotion for Android 查看应用

虽然安装 Google Android 模拟器是可能的,但许多开发者在 Mac 上有不一致的体验。有许多商业和免费的替代品提供了更多便利和广泛的设备支持。Genymotion 提供了一些独特的优势,例如允许用户切换 Android 模型和版本,支持从应用内部进行网络连接,并允许模拟 SD 卡。

在本节中,你将学习如何首先设置 Android 开发环境(在本例中为 Mac)。然后,你将安装和配置 Genymotion 以进行移动应用开发。

如何操作…

  1. 第一步是正确设置 Android 开发环境。从 developer.android.com/studio/index.html 下载并安装 Android Studio。

    注意

    如果你的机器没有正确的依赖项,可能会要求你安装其他库。如果是这种情况,你应该在命令行中运行 sudo apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0 lib32stdc++6 来安装。

  2. 运行 Android Studio。

  3. 你需要安装所有必需的包,例如 Android SDK。只需在设置向导屏幕上点击 Next 两次,然后点击 Finish 按钮开始安装包。如何操作…

  4. 安装完成后,你需要安装额外的包和其他 SDK 版本。在 Quick Start 屏幕上,选择 Configure,如图所示:如何操作…

  5. 然后,选择 SDK Manager,如图所示:如何操作…

  6. 安装上一个版本,例如 Android 5.0.15.1.1 是一个好习惯。你可能还希望安装所有 ToolsExtras 以供以后使用:如何操作…

  7. 点击 Install packages… 按钮。

  8. 勾选 Accept License 复选框并选择 Install

  9. SDK Manager 将在顶部显示一个 SDK 路径。复制此路径,因为你需要修改环境路径。

  10. 前往终端并输入以下命令:

     $ touch ~/.bash_profile; open ~/.bash_profile.
    
    
  11. 这将打开一个文本编辑器以编辑你的 bash profile 文件。在 /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
    
    
  12. 保存并关闭该文本编辑器。

  13. 返回终端并输入。

     $ source ~/.bash_profile
     $ echo $ANDROID_HOME
    
    
  14. 你应该看到输出你的 SDK 路径。这验证了你已经正确配置了 Android 开发环境。

  15. 下一步是安装和配置 Genymotion。从 genymotion.com 下载并安装 Genymotion 和 Genymotion Shell。

  16. 运行 Genymotion。

  17. 点击添加按钮以开始添加新的 Android 设备,如图所示:如何操作…

  18. 选择您想要模拟的设备。在本例中,让我们选择三星 Galaxy S5,如下所示:如何操作…

  19. 您将看到设备正在添加到您的虚拟设备中。点击该设备。如何操作…

  20. 然后点击开始如何操作…

  21. 模拟器将花费几秒钟时间启动,并显示另一个窗口。这是一个还没有运行您的应用的空白模拟器:如何操作…

  22. 运行 Genymotion Shell。

  23. 从 Genymotion Shell 中,您需要获取设备列表并保留连接的设备的 IP 地址,即三星 Galaxy S5。输入devices list:如何操作…

  24. 输入adb connect 192.168.56.101(或从设备列表命令行中看到的任何 IP 地址)。

  25. 输入adb devices以确认它已连接。

  26. 输入ionic platform add android以将 Android 添加为您的应用平台。

  27. 最后,输入ionic run android

  28. 您应该能够看到显示您的应用的 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。如果您测试自己的应用,请按照以下步骤操作:

  1. 从 App Store 或 Google Play 下载 Ionic View。

  2. 确保在ionic.io上注册一个账户。

  3. 前往您应用的项目文件夹。

  4. 搜索 ionic upload。

  5. 输入您的凭证。

  6. CLI 会上传整个应用并给你一个应用 ID,在本例中为152909f7。你可能想保留这个应用 ID 以便稍后与其他测试人员分享。如何操作…

  7. 在移动设备上打开 Ionic View 应用,如果你还没有登录,请登录。

  8. 现在,你应该能在你的我的应用页面上看到应用名称。请继续选择应用名称(在本例中为myApp),如图所示:如何操作…

  9. 选择查看应用来运行应用,如图所示:如何操作…

  10. 你会看到应用界面出现初始说明,说明如何退出应用。由于你的应用将覆盖 Ionic View 的全屏,你需要像图示那样用三根手指向下滑动,才能返回到 Ionic View:如何操作…

如果没有代码更新,过程相同,只是你需要从菜单中选择同步到最新

还有更多…

总结来说,使用 Ionic View 有几个好处,以下是一些:

  • 这很方便,因为只有一个命令行可以推送应用

  • 任何人都可以通过输入应用 ID 来访问你的应用

  • 即使没有 iOS 开发者会员资格,你也可以开始使用 Ionic 进行开发。苹果有自己的TestFlight应用,其用例非常相似

  • 你可以通过在开发过程中让测试人员测试你的应用来保持开发过程的敏捷性

  • Ionic View 支持广泛的设备功能,并且还在不断增长

第二章。添加 Ionic 2 组件

在本章中,我们将介绍与使用 Ionic 2 组件相关的以下任务:

  • 使用标签添加多个页面

  • 添加左右菜单导航

  • 使用状态参数导航多个页面

简介

可以用几页简单编写一个应用。Ionic 提供了许多开箱即用的组件,允许简单的即插即用操作。当应用增长时,管理不同视图及其在特定时间或触发事件时的自定义数据可能会非常复杂。Ionic 2 在处理状态和导航方面有一些变化。在 Ionic 1 中,您可以使用 UI-Router 进行高级路由管理机制。在 Ionic 2 中,NavController将启用导航的 push/pop 实现。

由于 Ionic 2 引入了许多新的组件,您必须了解这些组件如何影响您的应用状态层次结构以及何时触发每个状态。

使用标签添加多个页面

本节将解释如何使用 Ionic 标签界面,并扩展它以适应其他情况。所使用的示例非常基础,包含三个标签和每个标签中的一些示例 Ionic 组件。这是您将在许多应用中找到的一个非常常见的结构。您将学习 Ionic 2 如何构建标签界面,以及它如何转换为单独的文件夹和文件。

在此示例中,您将构建三个标签,如下所示:

  • 展示一个仅包含文本的简单页面,以说明组件应放置的位置

  • 展示注册表单

  • 展示水平滑块框

虽然应用非常直接,但它将教会您许多 Angular 2 和 Ionic 2 的关键概念。其中一些是组件装饰器、主题以及 TypeScript 编译器过程。

这是应用的一个截图,其中选中了中间标签:

使用标签添加多个页面

准备工作

由于这是您从头开始构建的第一个应用,您需要确保您已经遵循了第一章,使用 Ionic 2 创建我们的第一个应用,以设置环境和 Ionic CLI。如果您已经有了 Ionic 1,则必须更新它。为此,您可以使用与安装相同的命令行,如下所示:

$ sudo npm install -g cordova ionic ios-sim

如何操作…

以下是指示:

  1. 使用tabs模板创建一个新的PagesAndTabs应用,并进入PagesAndTabs文件夹以启动 Visual Studio Code,如图所示:

    $ ionic start PagesAndTabs tabs --v2 
    $ cd PagesAndTabs
    $ code .
    
    
  2. 空模板仅提供基本页面。在 Mac 上打开Finder应用或在 Windows 上打开 Windows 资源管理器,以查看以下文件夹结构:如何操作…

    小贴士

    您只需修改/src文件夹内的内容,而不是/www,正如在 Ionic 1 中那样。/src文件夹中的所有内容都将被构建,而/www文件夹将自动创建。我们还将尽可能保留文件夹名称和文件名,因为这里的主要目标是了解标签模板的工作原理以及您可以修改的区域。

  3. 使用以下代码打开并编辑/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>添加更多标签。

  4. 要添加一个页面,你需要确保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 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>
    
  5. 同样,在同一个/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) {
    
      }
    
      ionViewWillEnter() {
        console.log('Enter Page 1');
      }
    
    }
    
  6. 对于第二个页面,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>
      </ion-list>
      <div padding>
        <button ion-button primary block>Create Account</button>
      </div>
    </ion-content>
    
  7. 编辑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) {
    
      }
    
      ionViewWillEnter() {
        console.log('Enter Page 2');
      }
    }
    
  8. 最后,对于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 (ionDidChange)="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>
    
  9. /contact文件夹中,你需要使用以下代码编辑contact.ts

    import { Component, ViewChild } from '@angular/core';
    import { Slides, NavController } from 'ionic-angular';
    
    @Component({
      selector: 'page-contact',
      templateUrl: 'contact.html'
    })
    export class ContactPage {
      @ViewChild('mySlider') slider: Slides;
    
      constructor(public navCtrl: NavController) {
    
      }
    
      ionViewWillEnter() {
        console.log('Enter Page 3');
      }
    
      onSlideChanged(e) {
        let currentIndex = this.slider.getActiveIndex();
        console.log("You are on Slide ", (currentIndex + 1));
      }
    
    }
    
  10. 前往你的终端,并输入以下命令行来运行应用:

    $ ionic serve
    
    

它是如何工作的...

实际上,在这个简单的应用中有很多新的信息和概念。在更高层次上,这是应用的架构:

  • 当你运行应用时,Cordova 首先加载/www/index.html文件以打开。所有代码和模板都合并到一个文件中,即/www/build/main.js

  • /app文件夹是大多数逻辑所在的地方。它以app.component.ts作为引导文件。

  • /pages文件夹下的每个子文件夹将代表一个页面,这是 Ionic 2 中的新概念。一个页面由一个 HTML 模板、TypeScript 代码和一个.scss文件组成,用于仅自定义该特定模板。

  • /theme文件夹将包含全局级别的变量和自定义设置,以覆盖 Ionic 2 的默认主题。

现在,让我们从/app文件夹中的所有内容开始。

app.component.ts文件仅导入启动应用所需的所有页面和组件。此示例默认需要以下四个导入:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from 'ionic-native';
import { TabsPage } from '../pages/tabs/tabs';

你必须始终从 Ionic 导入ComponentPlatformStatusBar,因为这将为你提供@Component装饰器来引导你的应用。装饰器放置在其类之前,为类提供元数据。以下示例说明MyApp类具有组件的特性,具有template属性:

@Component({
  template: `<ion-nav [root]="rootPage"></ion-nav>`
})
export class MyApp {
  rootPage = TabsPage;

  constructor(platform: Platform) {
    platform.ready().then(() => {
      StatusBar.styleDefault();
    });
  }
}

由于这是一个简单的示例,你不需要声明很多除了模板信息之外的内容。类似于 Ionic 1,你可以使用templatetemplateUrl来指向本地文件。

类是 ES6 中的另一个新概念。然而,开发者已经在各种编程语言中声明了类,例如 Java 和 C#。在 ES6 中,你可以使用类来更有效地重用代码,并具有更好的抽象。类可以仅存在于该文件上下文中。考虑以下示例:

class Example {}

然而,如果你想在其他地方使用那个类,你必须导出:

export class Example {}

在一个类中,你可以有如下所示的内容:

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 2 的一个新概念,因为它试图摆脱使用 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 中使用),并告诉构造函数使用 tab1Roottab2Roottab3Root 作为根,如下所示,用于其他页面的标签导航:

export class TabsPage {
  tab1Root: any = HomePage;
  tab2Root: any = AboutPage;
  tab3Root: any = ContactPage;

  constructor() {
  }
}

Ionic 2 的标签声明与 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>

tab2Roottab3Root在结构上非常相似。Ionic 2 为你提供了在page类中直接绑定事件的便利性,如下所示:

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 2 中不再存在。

对于可扩展的应用程序,最好将模板分离到不同的文件中,并避免在 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 (ionDidChange)="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 2(或 Ionic 2)中获取事件,你必须使用括号(),因为ng-click或类似的概念不再可用。在这种情况下,如果滑动基于ionDidChange而改变,ion-slides指令将在ContactPage类中触发onSlideChanged()函数。

你实际上不能在没有 TypeScript 将代码转换为 JavaScript 的情况下直接运行 TypeScript。当你运行ionic serve时,这个过程会在幕后自动发生。此外,当你更改项目中的某些代码时,Ionic 会检测到这些更改并在更新浏览器之前重新构建文件。你不需要每次都点击刷新按钮。

参见

添加左右菜单导航

菜单导航是许多移动应用中非常常见的组件。你可以使用菜单让用户在应用中切换到不同的页面,包括登录和登出。菜单可以放置在应用的左侧或右侧。Ionic 2 还允许你检测事件并进一步自定义菜单的外观和感觉。

这是您将要开发的应用的截图:

添加左右菜单导航

应用将有两个页面和两个菜单。你可以切换左侧或右侧菜单(但不能同时切换)。实际上,你同时拥有两个菜单的可能性很小,但为了演示目的,这个应用将包括两个菜单,以展示你可以设置的菜单的不同属性。左侧菜单将改变页面,而右侧菜单将允许你捕获点击的确切项目。

准备工作

这个应用可以在你的网页浏览器上运行,因此不需要有物理设备。再次强调,你只需要在电脑上安装 Ionic 2。

如何操作…

这里是说明:

  1. 使用sidemenu模板创建一个新的LeftRightMenu应用,如下所示,并进入LeftRightMenu文件夹:

    $ ionic start LeftRightMenu sidemenu --v2
    $ cd LeftRightMenu
    
    
  2. 验证你的应用文件夹结构,确保它与以下结构相似:如何操作…

  3. 编辑./src/app/app.component.ts并替换为以下代码:

    import { Component, ViewChild } from '@angular/core';
    import { Nav, Platform } from 'ionic-angular';
    import { StatusBar, Splashscreen } from 'ionic-native';
    
    import { Page1 } from '../pages/page1/page1';
    import { Page2 } from '../pages/page2/page2';
    
    @Component({
      templateUrl: 'app.html'
    })
    export class MyApp {
      @ViewChild(Nav) nav: Nav;
      text: string = '';
      rootPage: any = Page1;
      pages: Array<{title: string, component: any}>;
    
      constructor(public platform: Platform) {
        this.initializeApp();
    
        // used for an example of ngFor and navigation
        this.pages = [
          { title: 'Page One', component: Page1 },
          { title: 'Page Two', component: Page2 }
        ];
    
      }
    
      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.
          StatusBar.styleDefault();
          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;
      }  
    }
    
  4. 使用以下代码创建./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处于同一级别,而不是作为父或子元素。这种结构对于菜单导航的正常工作非常重要。

  5. 现在,让我们创建两个页面,你只需要修改来自“sidemenu”模板的标准页面。打开并编辑./src/app/pages/page1/page1.html模板:

    <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>
    
    <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>
    
  6. 在同一文件夹中,通过page1.scss打开并编辑css类,如下所示:

    .getting-started {
    
      p {
        margin: 20px 0;
        line-height: 22px;
        font-size: 16px;
      }
    
    }
    
    .bar-button-menutoggle {
      display: inline-flex;
    }
    

    小贴士

    注意,由于你使用的是 sidemenu 模板,它已经包含了一个第二页(例如,page2)。在这个特定示例中,不需要修改该页面。

  7. ./src/pages/page2/page2.html中打开并编辑第二页的模板,如下所示:

    <ion-header>
      <ion-navbar>
        <ion-title>Page Two</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>
    
    <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>
    
  8. 最后,由于你在page1文件夹中添加了一个scss文件,你需要确保它在/src/app/app中导入,你可以按照以下方式操作:Scss

    @import "../pages/page1/page1";
    
  9. 进入你的终端并运行应用:

    $ ionic serve
    
    

工作原理…

由于这个应用只是对菜单导航的介绍,它不会管理页面路由和状态参数。在更高层次上,这是应用的流程:

  • app.js加载了app.html中的两个菜单模板

  • 左侧菜单将触发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-toolbarion-menu 内的使用是可选的。要显示菜单项的关键是使用 ion-listion-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 的替代品。您需要使用 #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>

因此,如果文本变量不存在(这意味着用户尚未点击任何内容),则 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>

注意,leftMenurightMenu 必须与您在 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-colion-row。带有数字的 width 属性将确定宽度百分比。

参见

使用状态参数导航多个页面

应用导航是一个重要的话题,因为它是一个用户体验的核心。您希望管理用户在提交表单或切换到新标签页后对会发生什么的预期。此外,您可能还想确保用户数据在正确的页面或正确的状态下可用。当涉及到返回导航的要求时,这可能会变得更加复杂。

本节将向您介绍如何使用 NavControllerNavParams,这两个是管理应用所有导航的重要基类。这是您将要开发的应用的截图:

使用状态参数导航多个页面

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

使用状态参数导航多个页面

第二页基本上是捕获参数并在屏幕上显示它们。它还提供了三种不同的选项来导航回上一页。

在这个应用中,你将学习以下内容:

  • 如何使用 NavControllerNavParams

  • 如何在模板中直接使用 [navPush][navParams]

  • 如何在输入框中添加双向数据绑定

  • 如何使用 pipe 将 JSON 对象转换为字符串并在屏幕上渲染

准备工作

你只需要确保有 Ionic 2 CLI 可用即可运行此应用。

如何做到这一点…

这里是说明:

  1. 使用空白模板创建一个新的 Navigation 应用,如下所示,然后进入 Navigation 文件夹:

    $ ionic start Navigation blank --v2
    $ cd Navigation
    
    
  2. 使用以下代码编辑 ./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';
    import { OtherPage } from '../pages/otherPage/otherPage';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        OtherPage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage,
        OtherPage
      ],
      providers: []
    })
    export class AppModule {}
    

    小贴士

    你必须修改此文件的主要原因是为了通过 NgModule 声明 OtherPage 为动态加载的模块。你将不得不在 home.ts 文件中再次声明 OtherPage

  3. 使用以下代码编辑 ./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>
              <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>
    
  4. 使用以下代码编辑 ./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'});
      }
    }
    
  5. 创建 ./src/app/pages/otherPage 文件夹。

  6. 在之前创建的 otherPage 文件夹中创建 otherPage.html 文件:

    <ion-header>
      <ion-navbar>
        <ion-title>
          Other Page
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content>
      <ion-card *ngIf="params.data">
        <ion-card-header>
          params.data
        </ion-card-header>
        <ion-card-content>
          {{ params.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>
    
  7. 在同一文件夹中,添加 otherPage.ts,代码如下:

    import { Component } from '@angular/core';
    import { NavController, NavParams } from 'ionic-angular';
    
    @Component({
      selector: 'other-page',
      templateUrl: 'OtherPage.html'
    })
    export class OtherPage {
      constructor(public navCtrl: NavController, public params: NavParams) {
      }
    
      goBack() {
        this.navCtrl.pop();
      }
    
      onPageWillEnter() {
        console.log('Enter OtherPage');
      }
    }
    
  8. 进入你的终端并运行应用:

    $ ionic serve
    
    

它是如何工作的…

从高层次来看,这是应用的结构:

  • 应用将通过 app.ts 启动并加载 home.html 作为 root 页面

  • /home 文件夹中的所有内容都是你的第一页

  • /otherPage 文件夹中的所有内容都是你的第二页

  • 这两个页面通过 NavParams(或模板中的 navParams)进行通信

让我们看看 home.ts。你必须导入 NavControllerNavParams

import {Page, NavController, NavParams} from 'ionic/ionic';

对于构造函数,你需要做一些事情,如下所示:

  public myString: string = 'Example 1 - This is just a string';
  public myJSON: any = {text: ''};
  otherPage: any = OtherPage;

  constructor(public navCtrl: NavController) {
  }

this.navCtrl 变量将引用导入的 NavController。你应该像这样将其引入,以便使用内部导航功能。myStringmyJSON 只是变量,你将它们作为参数传递给第二页。你还要引入 OtherPage 类,并在模板中的 navPush 中使其可访问。

如所示,gotoOtherPage() 方法只做了一件事——它将页面推送到当前导航:

  gotoOtherPage() {
    this.navCtrl.push(OtherPage, {text: 'Example 3 - This is an object'});
  }

通过这样做,你的应用将立即切换到 OtherPage 页面,并且这也会包括参数。

第一页的 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)] 用于任何输入元素。

在你的第二页中,你只需在构造函数中将 NavControllerNavParams 在类中可用。

让我们看看你的 otherPage.js 文件:

constructor(public navCtrl: NavController, public params: NavParams) {
}

第二页(即 otherPage.html)的模板非常简单。首先,顶部的导航栏用于启用默认的返回按钮:

<ion-header>
  <ion-navbar>
    <ion-title>
      Other Page
    </ion-title>
  </ion-navbar>
</ion-header>

返回按钮是 Ionic 2 中的一个自动机制,因此你不必担心它何时会显示。

以下代码将在存在状态参数的情况下显示变量内容:

  <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 2 将此功能重命名为管道。然而,基本概念是相同的。{{ params.data | json }} 代码基本上告诉 Angular 2 应用 json 函数到 params.data,并渲染输出。

你可以使用 nav.pop() 函数返回上一页,如下所示:

  <button block (click)="goBack()">
    goBack()
  </button>

或者,你可以使用指令 navPop 返回,并将其放入你的按钮中,如下所示:

  <button block navPop>
    nav-pop
  </button>

因此,这些都是 Ionic 2 导航功能中的可能性。

参见

更多信息请参考以下链接中的官方 Ionic 2 文档,关于 NavControllerNavParams

要了解更多关于 Angular 管道如何工作,你可以查看以下页面,关于 JSON 管道的先前示例:

第三章:使用 Angular 2 构建块扩展 Ionic 2

在本章中,我们将介绍与创建自定义组件、指令和过滤器相关的以下任务:

  • 创建自定义披萨订购组件

  • 创建自定义用户名输入指令

  • 创建自定义管道

  • 创建一个共享服务以向多个页面提供数据

简介

大多数 Ionic 的内置功能实际上是预构建的组件。在本节中,你将学习如何使用 html 模板创建自己的自定义组件,其中包含 Ionic 组件。

组件实际上定义了 Angular 2。组件不过是一个具有自我描述特征的类。例如,<ul> 是你已熟悉的组件。之前,你使用了各种 Ionic 组件,如 <ion-list><ion-item>。组件是一个装饰器(即 @Component),用于向类添加元数据以描述以下内容:

  • 选择器:这是要在 DOM 中使用的名称(例如,<my-component>

  • 模板或 templateUrl:这指的是组件的渲染方式

  • 指令:这指的是你计划在组件内部使用的指令依赖项列表

  • 提供者:这是你计划在组件内部使用的提供者(即服务)列表

当然,还有许多其他选项,但前面的四个选项是最常见的。

创建自定义披萨订购组件

在本节中,你将构建一个应用来演示具有私有变量和模板的自定义组件。观察以下披萨订购组件的截图:

创建自定义披萨订购组件

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

创建自定义披萨订购组件

准备工作

此应用示例可以在浏览器或物理设备上运行。

如何操作...

执行以下说明:

  1. 使用如下所示的 blank 模板创建一个新的 MyComponent 应用,并进入 MyComponent 文件夹:

    $ ionic start MyComponent blank --v2
    $ cd MyComponent
    
    
  2. 打开 ./app/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>,稍后将会定义。

  3. 打开 ./app/pages/home/home.ts 文件进行全局编辑,以下代码:

    import { Component } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { MyComponent } from '../../components/foo';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
    
      constructor(public navCtrl: NavController) {
    
      }
    
    }
    

    你只需将 MyComponent 声明为依赖项即可。如果你熟悉 Angular 1 的指令概念,组件基本上就是一个带有模板的指令。

  4. 现在,让我们通过首先创建一个指令来创建组件,如下面的代码所示:

    $ mkdir ./src/components
    
    
  5. 在你刚刚创建的 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);
      }
    
    }
    
  6. ./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>
    
  7. 修改./src/app/app.module.ts,如图所示,以便你可以声明MyComponent。观察以下代码:

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { MyApp } from './app.component';
    import { HomePage } from '../pages/home/home';
    import { MyComponent } from '../components/foo';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        MyComponent
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage,
        MyComponent
      ],
      providers: []
    })
    export class AppModule {}
    
  8. 打开你的终端,使用以下命令运行应用程序:

    $ ionic serve
    
    

它是如何工作的…

你可能会想知道为什么需要创建一个组件来切换一个列表(披萨配料选项列表)。答案是,这只是一个演示,说明你可以如何使用组件来模块化你的应用程序。你完成的关键步骤如下:

  • 你创建了一个自定义组件,称为<my-component>,它可以在任何地方使用,包括在你的应用程序外部。

  • 组件内部的数据是完全私有的。这意味着没有其他人可以访问它,除非在组件的类中调用一个方法。

  • 你可以在组件内部添加或更改行为,而不会影响组件外部其他区域。

创建一个组件时,你需要确保从 Angular 2 本身(而不是从 Ionic 2)导入decorator,如下所示:

import { Component } from '@angular/core';

@Component({
  selector: 'my-component',
  templateUrl: 'foo.html'
})

在你的组件模板中,一切都是局部的,即组件类内部的内容。因此,你可以使用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 1 的经验,你可能注意到它没有组件的定义。从 Angular 2 开始,有以下三种类型的指令:

类型 描述
组件 它们有一个与组件关联的模板和类(即ion-input
结构性指令 它们改变其作用域内的 DOM 结构(即*ngIf*ngFor
属性指令 它们通过拦截其显示或事件来改变当前 DOM 的外观

在同一个指令中,你可能会有结构和属性特性的混合。在本节中,你将学习如何创建一个属性指令,它可以防止在用户名中输入某些字符,并通过切换其可见性来显示另一个 DOM 节点(显示您正在输入用户名)。观察以下应用程序的截图:

创建自定义用户名输入指令

GO按钮只是为了美观,你不需要为它编写任何代码。

准备工作

这个应用示例可以在浏览器或物理设备上运行。

如何做到这一点...

注意以下说明:

  1. 使用blank模板创建一个新的MyIonicInputDirective应用,如下所示,并进入MyIonicInputDirective文件夹:

    $ ionic start MyIonicInputDirective blank --v2
    $ cd MyIonicInputDirective
    
    
  2. 打开./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 2 中新的浮动按钮功能的一个示例。你只需要包含bottomcenter来定位它。这些实际上是属性指令的好例子。

  3. 在前一步骤所在的文件夹中打开home.ts,编辑并插入以下代码:

    import { Component } from '@angular/core';
    import { NavController } from 'ionic-angular';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
      private myStyles: Object = {showUsername: false};
    
      constructor(public navCtrl: NavController) {
      }
    
    }
    
  4. 创建./src/directives文件夹,如下命令所示:

    $ mkdir ./src/directives
    
    
  5. directives文件夹中创建my-ionic-input.ts文件,并复制以下代码:

    import {Directive, ElementRef, Input} from '@angular/core';
    
    @Directive({
      selector: '[myIonicInput]',
      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;
          }
        }
      }
      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");
      // }
    }
    
  6. 打开并编辑./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';
    import { MyIonicInputDirective } from '../directives/my-ionic-input';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        MyIonicInputDirective
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: []
    })
    export class AppModule {}
    
  7. 进入你的终端并运行应用,如下所示:

    $ ionic serve
    
    

它是如何工作的…

主页模板(home.html)非常典型,包含ion-listion-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应该声明为依赖项以注入到模板指令中。

要创建一个基本的指令,你必须至少导入DirectiveElementRef以操作 DOM。然而,由于这个Directive有输入(即myStyles),你应在my-ionic-input.ts中导入Input,如下面的代码所示:

import {Directive, ElementRef, Input} from '@angular/core';

你的指令中有selectorhost元数据,如下所示:

@Directive({
  selector: '[myIonicInput]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()'
    // '(keypress)': 'onKeyPress'
  }
})

myIonicInput选择器将从 DOM 中查询,并触发该 DOM 节点的动作。对于 DOM 上的事件检测,你必须将事件名称映射到类方法。例如,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,这已经被故意注释掉了。原因是您将 myIonicInput 指令放置在 ion-input 之上。但实际上,<input> DOM 只是 ion-input 的一个子元素。因此,如果您在父 ion-input 上监听 keypress 事件,您将无法绑定它。

onMouseEnteronMouseLeave 方法非常直观,因为它们只是切换 myStyles.showUsername 变量。再次强调,这个 myStyles 对象只是指向 HomePagemyStyles 的一个引用。因此,如果您在这里更改变量,它也会在主页级别上更改。

参见

创建自定义管道

管道也是 Angular 2 的一个特性,并不特定于 Ionic。如果您熟悉 Angular 1,那么 管道过滤器 完全相同。您可能想要使用管道的主要原因是在视图中以不同的格式显示数据。您不希望更改组件中的实际值。这使得事情变得非常方便,因为您不需要在代码中决定具体的格式,同时在视图层留下灵活性。以下是一些有用的内置管道列表(来自 angular.io/docs/ts/latest/api/#!?apiFilter=pipe):

  • AsyncPipe

  • DatePipe

  • NumberPipe

  • SlicePipe

  • DecimalPipe

  • JsonPipe

  • PercentPipe

  • UpperCasePipe

  • LowerCasePipe

  • CurrencyPipe

  • ReplacePipe

在本节中,您将学习如何使用 @Pipe 装饰器创建自定义管道。以下是该应用的截图:

创建自定义管道

虽然应用界面非常简单,但这个例子是为了向您展示如何创建一个管道来提取对象数据。

准备工作

没有必要在物理设备上进行测试,因为 Angular 2 管道在浏览器中运行得很好。

如何操作...

观察以下说明:

  1. 使用blank模板创建一个新的CustomPipe应用,如下所示,并进入CustomPipe文件夹:

    $ ionic start CustomPipe blank --v2
    $ cd CustomPipe
    
    
  2. 打开./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管道来渲染正确的信息。

  3. 然后,将./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
        }
      }
    
    }
    

    你还没有custom-pipe.ts文件,所以,你需要创建它。

  4. 使用以下命令创建./src/utils文件夹:

    $ mkdir ./utils/utils
    
    

    你可以给这个文件夹起任何名字。然而,由于有时管道被认为是实用函数,让我们称它为utils

  5. 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;
      }
    }
    
  6. UserExtractPipe添加到./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';
    import { UserExtractPipe } from '../utils/custom-pipe'
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        UserExtractPipe
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: []
    })
    export class AppModule {}
    
  7. 打开你的终端并运行应用,如下所示:

    $ ionic serve
    
    

它是如何工作的…

你可以在视图中使用 Angular 2 管道简单地转换或转换任何值到所需值。你对管道的结构没有限制。Angular 2 会自动检测模板中的|符号,并将它前面的值转换为输入。要创建一个管道,你必须导入装饰器并提供一个名称(见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 2 中的 @Injectable 装饰器。

观察您将要构建的应用程序的以下截图:

创建一个共享服务以向多个页面提供数据

用户可以填写表单并实时查看其上方的更新。然后,他们可以转到下一页(转到页面 2)并查看以下截图:

创建一个共享服务以向多个页面提供数据

此页面使用与前一页相同的服务,并引用了具有相同名称和年龄的相同日期。您将在本节学习以下主题:

  • 使用 @Injectable 创建服务

  • 在多个页面间共享服务

  • 在服务内部使用获取器和设置器检测更改

准备工作

此应用程序示例可以在浏览器或物理设备上运行。

如何操作...

仔细观察以下说明:

  1. 按照示例,使用 blank 模板创建一个新的 SharedService 应用程序,然后进入 SharedService 文件夹:

    $ ionic start SharedService blank --v2
    $ cd SharedService
    
    
  2. 由于您有两个页面和一个适用于两者的公共服务,您需要在目录中进行一些更改。让我们首先修改 ./src/app/app.component.ts 文件,以便 rootPage 指向 Page1

    import { Component } from '@angular/core';
    import { Platform } from 'ionic-angular';
    import { StatusBar, Splashscreen } from 'ionic-native';
    import { Page1 } from '../pages/page1/page1';
    
    @Component({
      template: `<ion-nav [root]="rootPage"></ion-nav>`
    })
    export class MyApp {
      rootPage = Page1;
    
      constructor(platform: Platform) {
        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();
        });
      }
    }
    
  3. 创建 ./src/pages/page1,如下所示:

    $ mkdir ./src/pages/page1
    
    
  4. 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>
    
  5. 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 特定功能,例如获取器和设置器。

  6. 类似地,使用以下命令创建 page2 文件夹:

    $ mkdir ./src/pages/page1
    
    
  7. 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>
    

    这是您的第二个页面,具有相同的 nameage 信息。

  8. 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);
      }
    }
    
  9. 使用以下命令创建 services 文件夹:

    $ mkdir ./src/services
    
    
  10. services 文件夹中的 user.ts 文件中放置 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;
      }
    }
    
  11. 打开并编辑 ./src/app/app.module.ts,以便您可以注入 UserService 作为全局提供者:

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    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: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        Page1,
        Page2
      ],
      providers: [UserService]
    })
    export class AppModule {}
    
  12. 验证您的文件夹结构如下截图所示:如何操作...

  13. 进入您的终端,并按照以下命令运行应用程序:

    $ ionic serve
    
    

您可以从 页面 1 切换到 页面 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

小贴士

不要忘记在 @Injectable() 中使用括号。

其次,如果你想使用获取器和设置器,你需要创建单独的变量,称为 _name_age,来存储数据。然后,你可以使用 get/set 方法在页面访问或设置此通用类中的变量时进行额外的处理。如果你从“页面 1”更改 nameage,你可以在控制台中看到以下日志:

如何工作…

这个功能非常有用,因为你可以用它来替代 watchobservable。如果你还记得 Angular 1,你必须使用 $scope.$watch 来实现类似的功能。

相关内容

第四章:验证表单和制作 HTTP 请求

在本章中,我们将涵盖与创建表单输入验证、模拟 API 调用和 Stripe 支付页面相关的以下任务:

  • 使用输入验证创建复杂表单

  • 使用静态 JSON 文件通过模拟 API 获取数据

  • 集成 Stripe 进行在线支付

简介

所有移动应用都需要获取用户输入并将其发送到后端服务器。一个简单的例子是填写表单,例如用户注册表单或联系表单。在发送到后端之前,信息会根据一组规则进行验证。此外,还有许多其他场景,信息是基于用户在应用中的行为捕获的,例如他们在某个页面上的触摸位置或停留时间。无论如何,你都会遇到许多发送和检索数据的情况。

本章将涵盖以下三个基本示例:

  • 如何验证用户输入,如文本、数字以及必填项与非必填项,并将数据传递到另一页

  • 如何在没有实际后端的情况下渲染数据

  • 如何使用 Stripe 处理支付

所有这些实际上在 Angular 2 中都是原生可用的。然而,由于 Angular 2 在处理数据和与后端服务器交互方面与 Angular 1 相比有很多变化,因此详细讨论这些主题是值得的。

使用输入验证创建复杂表单

在本节中,你将构建一个应用程序来演示使用 ngFormngControl 进行表单验证。以下是表单的截图:

使用输入验证创建复杂表单

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

使用输入验证创建复杂表单

基本上,姓名字段是必填的。电话字段是数字类型,但为可选。评论字段是必填的,用户必须输入至少四个字符。当然,这只是为了演示输入长度。最后,用户必须通过切换输入同意条款和条件。

验证成功后,用户将被带到第二个屏幕,显示上一个屏幕的摘要,如下面的截图所示:

使用输入验证创建复杂表单

准备就绪

此应用程序示例可以在浏览器或物理设备上运行。然而,你可以选择将物理设备连接起来,以验证电话字段是否具有数字键盘。

如何做到这一点……

仔细阅读以下说明:

  1. 使用 blank 模板创建一个新的 MyFormValidation 应用程序,如图所示,并转到 MyFormValidation 文件夹:

     $ ionic start MyFormValidation blank --v2
     $ cd MyFormValidation
    
    
  2. 打开 ./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';
    import { ThankyouPage } from '../pages/thankyou/thankyou';
    import { MyFormService } from '../services/myform';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        ThankyouPage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage,
        ThankyouPage
      ],
      providers: [MyFormService]
    })
    export class AppModule {}
    

    你可能会意识到有一个在应用程序中通用的服务,在这里称为 MyFormService。此示例还有一个第二页,称为 ThankyouPage

  3. 现在,让我们通过首先创建一个目录来创建服务,如下所示:

     $ mkdir ./src/services
    
    
  4. 在你刚刚创建的组件目录中创建一个名为 myform.js 的文件,如下所示:

    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class MyFormService {
      public name: string = '';
      public phone: number;
      public comment: string = '';
    
      constructor() {
      }
    }
    

    为了演示目的,这个例子将保持服务组件简单。

  5. 打开并编辑 ./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>
    

    这可能是表单验证过程最复杂的一部分,因为你有许多地方需要嵌入输入的验证逻辑。

  6. 打开并替换 ./src/pages/home/home.scss 文件的内容,如下所示:

    .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-item-sliding: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;
    }
    
  7. 打开 ./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 命令,你将不得不创建下一个。

  8. 现在,让我们创建 thankyou 文件夹,如下所示:

     $ mkdir ./src/pages/thankyou
    
    
  9. 在你刚刚创建的组件目录中创建一个名为 thankyou.js 的文件,如下所示:

    import { Component } from '@angular/core';
    import { MyFormService } from '../../services/myform'
    
    @Component({
      templateUrl: 'thankyou.html'
    })
    export class ThankyouPage {
    
      constructor(private formData: MyFormService) {
        this.formData = formData;
      }
    
    }
    

    这个页面只是渲染 MyFormService 服务的数据。因此,你可以让它非常简单。

  10. ./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>
    
  11. ./src/pages/thankyou 文件夹中创建 thankyou.scss,如图所示:

    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;
      }
    }
    
  12. 编辑 ./app/app.scss 文件以确保包含两个页面中的 .scss 文件,如下所示:

    @import '../pages/home/home';
    @import '../pages/thankyou/thankyou';
    
  13. 前往你的终端,使用以下命令运行应用程序:

     $ ionic serve
    
    

如何工作…

让我们从包含大部分验证代码的 home.html 文件开始。如果你看看这个页面的结构,它非常典型。你有一个 <ion-navbar>,其中包含 <ion-title><form> 元素必须位于 <ion-content> 区域内。

提示

使用 <form> 元素是 Angular 2 验证功能正常工作的要求。否则,将没有 submit 事件,你无法捕获每个输入的错误。

form 有以下属性:

<form #f="ngForm" novalidate (ngSubmit)="onSubmit(f)">

即时分配一个局部变量,你使用 # 符号。这意味着你希望 f 变量指向 ngForm,它是 Angular 2 自动创建的。这是一个包含与当前表单相关的一切的特殊对象。建议使用 novalidate 来绕过默认的 HTML5 验证,因为你正在使用 Angular 2 进行验证。否则,form 将会冲突。(ngSubmit) 几乎是一个事件,当 type=submitbutton 被触摸或点击时,会触发 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 将被隐藏:

  • 表单尚未提交。否则,人们在输入任何内容之前就会立即看到错误信息。这不是一个好的用户体验。为了检查这一点,你必须使用一个临时的布尔值,称为 isSubmittedf.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 字段,需要使用 requiredminlength=4 进行验证,如下所示:

<ion-input type="text" required minlength=4 name="comment" [(ngModel)]="data.comment"></ion-input>

你可能会认为 required 是不必要的,因为如果长度为零,Angular 2 将触发一个错误标志。然而,这并不正确。当用户在输入框中没有输入任何内容时,输入将没有长度,因为变量不存在。这就是为什么你需要检查这两种情况。

对于 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.validmyForm.value.tos进行一般性检查。你可能想知道为什么我们在这里检查tos而不是在模板内进行验证。原因是,在 Angular 2 中无法验证切换按钮,因为它没有意义,因为它不能是required。因此,你必须在这里进行自定义验证,以确保它在表单中是true。这意味着用户已经检查了同意条款和条件的切换按钮。

这是一个可能是在 Ionic 2(目前为 Beta 21)中存在的细节错误:

noSubmit(e) {
  e.preventDefault();
}

对于每个切换按钮,它默认充当type=submit按钮,因为没有分配type属性。这就是为什么你需要通过调用preventDefault()来取消submit事件。

小贴士

参考 W3 网站,在www.w3.org/TR/html-markup/button.html,以获取关于button元素默认行为的信息。

thankyou页面非常直观,因为你只需在模板中解析formData对象,从MyFormService服务中获取数据。

相关内容

查看以下链接以获取更多信息:

通过使用静态 JSON 文件使用模拟 API 检索数据

作为前端和应用程序开发者,你经常在一个团队中工作,其中另一个人负责后端 API。然而,当你开发前端时,并不总是有可能有后端可用。在最终后端 API 尚未准备好的情况下,你必须模拟后端。

在这个菜谱中,你将学习如何使用http服务调用 REST API。API 端点将只是位于你本地机器上的静态 JSON。你还将学习如何利用占位符图像来满足设计要求。应用程序将显示图像源列表和描述,如下面的截图所示:

通过使用静态 JSON 文件使用模拟 API 检索数据

准备工作

这个应用程序示例可以在浏览器或物理设备上运行。但是,模拟的后端服务器必须在你的本地计算机上运行。

如何操作...

这里是必须遵循的说明:

  1. 首先,让我们快速创建一个的后端服务器。为此,您必须安装http-server

     $ sudo npm install -g http-server
    
    
  2. 创建一个文件夹来存储您的.json文件。让我们称它为MockRest,如图所示:

     $ mkdir MockRest
     $ cd MockRest
    
    
  3. 创建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文件的内容。

  4. MockRest文件夹中的终端调用http-server以启动您的后端服务器,如图所示:

     $ http-server --cors=Access-Control-Allow-
    
    
  5. 通过http://localhost:8080/test.json来验证您是否可以看到 JSON 内容。如果不行,您可能与其他 Web 服务器存在端口冲突。您需要确保没有其他应用程序使用端口8080

  6. 在完成您的后端后,打开另一个终端窗口,使用blank模板创建一个新的MyRestBackend应用,并进入MyRestBackend文件夹,如图所示:

     $ ionic start MyRestbackend blank --v2
     $ cd MyRestbackend
    
    

    小贴士

    您必须不要停止后端服务器或创建一个在MockRest文件夹内的 Ionic 项目。它们是两个独立的项目文件夹。

  7. 打开html.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/的免费照片,因为您可以轻松查询以获取满足您需求的随机照片。

  8. 打开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 内容。

  9. 对样式表home.scss进行以下小修改:

    ion-card {
      img {
        background-color: #f4f4f4;
      }
    }
    
  10. 使用以下命令创建./src/services文件夹:

     $ mkdir ./src/services
    
    
  11. services文件夹中创建quote.ts文件,并复制以下代码:

    import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';
    
    @Injectable()
    export class QuoteService {
      private http: any;
      public data: any;
    
      constructor(http: Http) {
        this.http = http;
      }
    
      getQuotes() {
        this.http.get("http://localhost:8080/test.json")
          .subscribe(res => {
            this.data = res.json();
            console.log(this.data);
          }, error => {
            console.log(error);
          });
      }
    
    }
    
  12. 打开并编辑./src/app/app.module.ts以声明QuoteService,如图所示:

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { MyApp } from './app.component';
    import { HomePage } from '../pages/home/home';
    import { QuoteService } from '../services/quote'
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: [QuoteService]
    })
    export class AppModule {}
    
  13. 前往您的终端并运行应用,如图所示:

    $ ionic serve
    
  14. 您将注意到页面为空,控制台显示以下错误:如何做...

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

    如何做...

  15. 刷新您的浏览器以查看更新的应用。

它是如何工作的...

您的后端简单地返回当前MockRest文件夹中的任何文件。随着您从后端开发者那里获得更多样本响应,您可以将它们复制到这个文件夹中,以提供额外的后端端点。

注意

本节不提供如何处理 POST 请求和复杂场景的示例,其中响应取决于请求参数。您可能希望将处理临时情况的代码保持尽可能简单,因为它们不是生产代码。建议为每个 POST 请求返回相同的内容。

让我们看看quote.ts,因为它是发起Http请求的主要地方。首先,您需要导入InjectableHttp,您可以如下操作:

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';

使用@Injectable装饰器允许其他页面和组件将QuoteService作为依赖项使用。Http服务(或类)由 Angular 2(而非 Ionic 2)提供,这与 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()函数,但您必须subscribe到对象。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 本身无关。

参见

集成 Stripe 进行在线支付

在本节中,你将学习如何集成真实的后端服务进行支付流程。赚取收入是创建应用的重要方面。虽然有许多其他收集支付的方法,但 Stripe 是一个常见的支付系统,并且可以很好地与 Ionic 集成。此外,由于你不会存储信用卡信息,因此不需要提供高级别的安全性和合规性(即 PCI)。

你的应用不会通过真实的支付方式处理,因为你可以使用 Stripe 的公共测试密钥。应用将要求输入一些字段来创建令牌。观察以下应用截图:

集成 Stripe 进行在线支付

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

集成 Stripe 进行在线支付

提示

实际上,你的后端调用 Stripe 进行授权和处理的步骤还有额外的步骤。然而,这不在本节的范围内。Stripe 文档在 Node.js 方面有一个很好的教程页面stripe.com/docs/api/node#authentication

准备工作

没有必要在物理设备上进行测试,因为 Ionic 2 和 Stripe 在浏览器中运行得很好。

如何操作...

仔细阅读以下说明:

  1. 如果你没有 Stripe 账户,你需要注册stripe.com

  2. 登录并前往dashboard.stripe.com/test/dashboard

  3. 点击右上角的用户名并选择账户设置,如下面的截图所示:如何操作...

  4. 选择API 密钥标签。

  5. 复制以下所示的测试发布密钥,并将其放置在某个位置,因为稍后你需要用它来编写 JavaScript 代码:如何操作...

    现在,回到终端。

  6. 使用blank模板创建一个新的StripePayment应用,如下所示,并进入StripePayment文件夹:

     $ ionic start StripePayment blank --v2
     $ cd StripePayment
    
    
  7. 打开./src/index.html文件,并在<body>标签中插入以下所示行:

    <script type="text/javascript" src="img/"></script>
    

    注意

    这是将Stripe对象全局加载到你的应用中。这不是 Angular 2 推荐的方法,因为任何在组件内部使用的东西都必须通过import指令导入。然而,在撰写本书时,angular-stripe 对 Angular 2 不可用。因此,没有正确执行此操作的方法。前面的方法将运行得很好。

  8. 打开./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-list>
    
            <ion-item>
              <ion-input type="number" name="cc" [(ngModel)]="ngForm.cc" placeholder="Number"></ion-input>
            </ion-item>
    
            <ion-item>
              <ion-input type="number" name="cvc" [(ngModel)]="ngForm.cvc" placeholder="CVC"></ion-input>
            </ion-item>
    
            <ion-item>
              <ion-input item-left type="number" name="month" [(ngModel)]="ngForm.month" placeholder="Month"></ion-input>
              <ion-input item-right type="number" name="year" [(ngModel)]="ngForm.year" placeholder="Year"></ion-input>
            </ion-item>
    
            <button type="button" ion-button bottom block (click)="onSubmit()">Pay $20</button>
    
          </ion-list>
        </ion-card-content>
      </ion-card>
    
    </ion-content>
    

    Stripe 只需要信用卡号码、CVC 和到期日期来创建用于收费的令牌。客户姓名和地址是可选的;因此,您不需要在此处包含它们。

  9. 然后,将./src/pages/home/home.ts的内容替换为以下代码:

    import { Component } 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 ngForm: any = {
          cc: '',
          cvc: '',
          month: '',
          year: '',
          amount: 2000    
      };
    
      constructor(public nav: NavController) {
        this.nav = nav;
        console.log(Stripe);
        Stripe.setPublishableKey('YOUR STRIPE PUBLIC KEY HERE');
      }
    
      onSubmit() {
        console.log(this.ngForm);
        Stripe.card.createToken({
          number: this.ngForm.cc, //'4242424242424242',
          cvc: this.ngForm.cvc, //'123',
          exp_month: this.ngForm.month, //12,
          exp_year: this.ngForm.year, //2017,
          amount: 2000
        }, (status, response) => this.stripeResponseHandler(status, response));
      }
    
      stripeResponseHandler(status, 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.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});
        }
      }
    
    }
    

    您需要在此处更改您的测试发布密钥,通过将YOUR STRIPE PUBLIC KEY HERE替换为您之前复制的自己的密钥。

  10. 使用以下代码编辑./src/pages/home/home.scss

    .center {
      text-align: center;
    }
    
    .gray-bg {
      background-color: #f4f4f7;
    }
    
    .icon-large {
      font-size: 150px;
      color: #387ef5;
      opacity: 0.5;
    }
    
    .list-ios > .item-block:first-child {
      border-top: 0;  
    }
    
    ion-card ion-list .item {
      border-bottom: 1px solid #c8c7cc;
    }
    
    ion-list .item-inner {
      border-bottom: 0;
    }
    
    ion-card-content .button-block {
      margin-top: 16px;
       }
    
  11. 通过创建一个名为./src/pages/thankyou的新文件夹,来创建显示令牌 ID 的thankyou页面,如下所示:

    $ mkdir ./src/pages/thankyou
    
    
  12. 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。

  13. 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);
      }
    
    }
    
  14. 创建thankyou.scss文件,使用以下代码修改主题:

    .green-bg {
      color: black;
      background-color: #32db64;
    }
    
    h4.center {
      padding-top: 150px;
    }
    
    .center {
      text-align: center;
    }
    
  15. 由于thankyou.scss文件在项目中是新的,您需要将其包含在./src/app/app.scss中。在代码底部插入此行:

    @import '../pages/thankyou/thankyou';
    
  16. 打开并编辑./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 {}
    
  17. 前往您的终端并运行应用:

    $ ionic serve
    
    
  18. 为了测试目的,您可以使用4242424242424242作为信用卡号码,123作为cvc,以及12/2017作为到期日期。

它是如何工作的…

这是 Stripe 收费过程:

  1. 用户填写支付表单并点击提交按钮。

  2. 前端(您的 Ionic 应用)将使用Stripe对象调用 Stripe API,并发送所有支付信息。

  3. Stripe 将返回一个令牌 ID,这基本上是一种确认一切正确并且现在可以收费的方式。

  4. 前端将使用令牌 ID 发送到其后端(不包含信用卡信息)以授权收费。

  5. 后端将调用另一个 Stripe API 表示“我现在要收费”。此时,Stripe 将向后端返回success事件。

  6. 然后,后端将向前端返回success事件。

  7. 前端应渲染一个新页面,例如thankyou页面。

如前所述,本章不会涵盖此应用的后端部分,因为它不专注于 Ionic。您可以使用任何语言构建后端,例如 Node.js、PHP 或 Python。

让我们看看home.ts,因为大多数 Stripe API 处理都位于那里。首先,您需要进行一个declare,如图所示,因为Stripe是一个全局对象,它包含在index.html中:

declare var Stripe: any;

如果您不进行declare,应用仍然可以运行,但您将收到 TypeScript 的错误。

当用户提交表单时,将触发以下方法:

  onSubmit() {
    console.log(this.ngForm);
    Stripe.card.createToken({
      number: this.ngForm.cc, //'4242424242424242',
      cvc: this.ngForm.cvc, //'123',
      exp_month: this.ngForm.month, //12,
      exp_year: this.ngForm.year, //2017,
      amount: 2000
    }, (status, response) => this.stripeResponseHandler(status, response));
  }

当你调用 Stripe.card.createToken 时,Stripe 对象将在后台触发一个 API 调用到 stripe.com/,并提交 JSON 数据。你可能意识到这个示例根本就没有使用 ngModel,但你可以直接从 ngForm 内部获取表单值。这个功能是通过你在 home.html 中的以下代码实现的:

<button type="button" ion-button bottom block (click)="onSubmit()">Pay $20</button>

一旦 Stripe 返回你的令牌 ID,它将调用以下箭头函数:

(status, response) => this.stripeResponseHandler(status, response)

在这里使用箭头函数的原因是因为你在 stripeResponseHandler 方法内的代码需要 HomePage 类的 this 上下文。这是一种访问令牌变量的好方法。观察以下代码:

  stripeResponseHandler(status, 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.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.id 将包含来自 Stripe 的令牌 ID。否则,你可以使用 response.error.message 来处理错误。在这个例子中,因为它只传递令牌 ID 到下一页,所以你可以简单地作为参数 {token: this.token} 发送它:

this.nav.push(ThankyouPage, {token: this.token});

在你的 thankyou.ts 文件中,你可以使用以下代码访问参数 token

this.params.get('token');

参考以下内容

第五章:添加动画

在本章中,我们将介绍与向应用添加动画和交互相关的以下任务:

  • 将全屏内联视频嵌入为背景

  • 使用 Dynamics.js 创建基于物理的动画

  • 通过将手势绑定到动画状态来动画化幻灯片组件

  • 向登录页面添加背景 CSS 动画

简介

用户体验对于用户的初始吸引力至关重要。当您的早期用户第一次使用应用时,他们会有更好的印象,这会建立信任并提高留存率。应用动画也将为用户提供交互式反馈,以便他们知道该做什么或根据非常温和的视觉提示采取行动。

原生应用曾经因为动画性能而优于基于 Web 的混合应用。然而,像 Ionic 和 Angular 这样的框架在近年来在性能上已经缩小了很多差距。Web 动画也更容易学习和编码,因为许多前端开发者已经熟悉 JavaScript 和 CSS。

在本章中,您将学习如何使用视频和 CSS 进行基本动画。然后,您将开始利用基于物理的动画来创建有趣的交互。此外,您甚至可以逐帧绑定手势,以便在滑动事件发生时动画能够即时播放。

将全屏内联视频嵌入为背景

现在,有许多应用利用视频作为介绍屏幕的动画背景。这使得应用更加有趣和富有创意。用户会感觉到他们受到了欢迎。这种策略对于给新用户留下深刻印象并鼓励他们回来是非常好的。

本节将教您如何在背景中添加自动播放的视频:

将全屏内联视频嵌入为背景

您还将学习如何使用animate.css为应用头部文本添加自定义动画。

准备工作

此应用示例可以在浏览器或物理设备上运行。然而,您可以选择将物理设备连接到应用以验证动画在背景中是否正确播放。

如何操作...

这里是操作说明:

  1. 使用blank模板创建一个新的VideoIntro应用,如图所示,并进入VideoIntro文件夹:

    $ ionic start VideoIntro blank --v2
    $ cd VideoIntro
    
    
  2. 您此时需要准备好您的视频。然而,对于这个示例,让我们从不需要许可证的公共网站上下载一个免费视频。导航到www.coverr.co

  3. 您可以下载任何视频。本应用中的示例使用的是Blurry-People.mp4剪辑。将其下载到您的电脑上:如何操作...

  4. 将视频保存到./src/assets/目录下:如何操作...

  5. 打开./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

  6. 对于主模板,你可以修改./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/Blurry-People.webm" type="video/webm">
        </video>
      </div>
      <div class="center animated zoomIn">
        <h1>Beautiful App</h1>
        <h2>Fast. Easy. Cheap.</h2>
      </div>
    </ion-content>
    

    这个页面只有两个重要的项目:视频和带有副标题的页眉。

  7. 使用以下代码在同一个文件夹中打开并编辑./src/pages/home/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;
      }
    
    }
    
    .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 文件编写任何代码。

  8. 打开config.xml文件,并在<widget>标签内添加以下行:

    <preference name="AllowInlineMediaPlayback" value="true"/>
    
  9. 打开你的终端,使用以下命令运行应用:

    $ 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>

注意,这里包含了animatedzoomIn类。这些是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-inease-outcubic-bezier。然而,使用现有的基于 JavaScript 的物理动画会更简单、更好。Dynamics.js就是那些带有实用工具和性能的 JavaScript 之一。实际上,使用原生的 CSS 物理特性并不是一个好的做法,因为它在移动设备上会带来每秒帧数的惩罚。

应用程序将显示一个弹跳按钮,它可以显示和隐藏一个顶部引言框,如下所示,它也使用了物理动画:

使用 Dynamics.js 创建基于物理的动画

准备工作

此应用程序示例可以在浏览器或物理设备上运行。然而,建议您通过物理设备运行应用程序以测试性能。

如何操作...

这里是说明:

  1. 打开一个终端窗口,使用 blank 模板创建一个新的 SpinningButton 应用程序,并进入 SpinningButton 文件夹:

    $ ionic start SpinningButton blank --v2
    $ cd SpinningButton
    
    
  2. 访问 dynamicsjs.com/ 并将 dynamics.min.js 文件下载到您的 ./src/assets 文件夹中,操作如下:如何操作...

  3. 打开 ./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">
    
      <!-- cordova.js required for cordova apps -->
      <script src="img/dynamics.min.js"></script>
      <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">
    
    </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>
    

    此文件的关键部分是确保包含 dynamics.min.js

  4. 打开并编辑 ./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>
    

    在此应用程序中不需要头部导航,因为它将只是一个单页应用。

  5. 在与步骤 2 相同的文件夹中打开 home.ts 文件进行编辑,以下代码:

    import { Component, ViewChild } from '@angular/core';
    import { NavController } from 'ionic-angular';
    declare var dynamics: any;
    
    @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) {
        console.log(dynamics);
      }
    
      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;
          }
    
        }
      }
    
    }
    

    注意,您必须在此文件中导入 dynamics 对象。

  6. 修改 home.scss 样式表,如下所示:

    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;
      }
    }
    
  7. 前往您的终端,使用以下命令运行应用程序:

    $ 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 2 浮动按钮的信息,您可以参考 ionicframework.com/docs/v2/components/#floating-action-buttons 的 Ionic 文档。

这里的简单逻辑如下:

  • 如果按钮正在动画,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;

参见

通过将手势绑定到动画状态来动画化幻灯片组件

从用户那里获得体验的另一种方式是拥有看起来很棒的介绍幻灯片。一个典型的应用程序会有三到五张幻灯片来描述应用程序的功能以及它将如何造福用户。今天,许多应用程序甚至添加了视频或交互式屏幕,以便用户可以感受到应用程序可能的工作方式。这样的交互式动画需要一些内部开发来将触摸手势绑定到动画状态。基于特定状态的动画非常困难,因为你真的需要获取粒度手势数据。另一方面,在状态的开始或结束时进行动画要容易得多。例如,您可以在幻灯片完全显示在屏幕上之后,通过向左滑动来动画化幻灯片内的对象。然而,这种动画效果不如在触摸移动过程中绑定动画那样有趣或吸引人。

在本节中构建的应用程序将包含三个幻灯片,当您向左或向右滑动时,这些幻灯片将进行动画处理:

通过将手势绑定到动画状态来动画化幻灯片组件

您将看到幻灯片之间的淡入和淡出动画效果。以下 Angular 标志在从第二个幻灯片向左滑动时也会向上移动:

通过将手势绑定到动画状态来动画化幻灯片组件

准备工作

由于动画是通过 HTML 和 JavaScript 完成的,因此不需要在物理设备上测试应用程序。然而,建议在您的设备上测试应用程序以评估动画性能。

如何操作...

这里是说明:

  1. 使用blank模板创建一个新的SliderAnimation应用程序,如下所示,并进入SliderAnimation文件夹:

    $ ionic start SliderAnimation blank --v2
    $ cd SliderAnimation
    
    
  2. 打开./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 2</h1>
        </div>
    
        <div class="slide-float" #slidefloat2>
          <ion-icon name="logo-angular"></ion-icon> 
          <h1>Angular 2</h1>
        </div>
    
        <div class="slide-float" #slidefloat3>
          <ion-icon name="logo-javascript"></ion-icon> 
          <h1>Both</h1>
        </div>
    
      </div>
    
      <ion-slides #myslides pager (ionDrag)="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>标签之上,以便单独对它们进行动画处理。

  3. 然后,将./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.slider.on('onProgress', (swiper, 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);
            }
    
          });
    
        }
    
      }
    }
    

    注意,注释对于计算每个对象的动画公式很有用。

  4. 使用以下内容编辑./app/pages/home/home.scss

    .slides-float {
      .slide-float {
        top: 0;
        position: fixed;
        width: 100%;
        margin-top: 20px;
      }
    }
    
    .home {
      background-color: DarkSlateBlue;
    
      h2 {
        font-size: 3rem;
      }
    
      ion-slide {
        color: white;
        background-color: transparent;
      }  
    
      .slides-float {
        color: white;
        text-align: center;
    
        > .slide-float:nth-child(2), > .slide-float:nth-child(3) {
          opacity: 0;  
        }
      }
    
      .slide-float {
        ion-icon {
          font-size: 150px;
        }
    
        h1 {
          font-weight: lighter;
          font-size: 60px;
          margin-top: 0;
        }
      }
    }
    
  5. 进入您的终端,并使用以下命令运行应用程序:

    $ ionic serve
    
    

它是如何工作的…

这是动画的一般过程:

  1. 由于有三个幻灯片,用户必须滑动两次才能到达末尾。这意味着第一次滑动将在 50%进度。

  2. 当用户向左滑动到 25%时,Ionic 标志将淡出。

  3. 当用户滑动到 50%时,Angular 标志将在第二页淡入。

  4. 当用户滑动到 75%时,Angular 标志将向上移动以消失,而不是淡出。

  5. 最后,在最后 75%到 100%的过程中,JavaScript 标志将淡入。

你可能已经注意到,淡入或移动的量将取决于进度百分比。因此,如果你稍微左右滑动,就可以立即看到动画对手势的反应。模板中有两层。如上图所示,浮动静态层必须位于顶部,并且无论当前是哪一页,它都必须保持在相同的位置:

  <div class="slides-float">

    <div class="slide-float" #slidefloat1>
      <ion-icon name="ios-ionic"></ion-icon> 
      <h1>Ionic 2</h1>
    </div>

    <div class="slide-float" #slidefloat2>
      <ion-icon name="logo-angular"></ion-icon> 
      <h1>Angular 2</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 (ionDrag)="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#slidefloat3home.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()方法是你放置所有动画逻辑的地方,它必须与onProgress事件绑定,如下所示:

this.myslides.slider.on('onProgress', {})

首先,让我们看看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 时,它将触发firstQuartersecondQuarter方法。也就是说,你希望在过程结束时淡出第一个浮动幻灯片并淡入第二个浮动幻灯片。对于thirdQuarterfourthQuarter方法,概念是相似的。请注意,你不想直接调用方法,而是只需将函数引用传递给this.rAf内部,以便渲染引擎管理帧率。否则,渲染函数可能会阻塞 UI 中的其他进程,导致运动颠簸。

对于每个象限,你只需要更改style属性,给定一个已知的进度值,如下所示:

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 对象。实际上,编写你自己的数学函数来计算滑动过程中的位置或不透明度,取决于你。在这个例子中,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属性而不是使用lefttop属性。你想要达到最高的每秒帧数。在thirdQuarter方法中,你的slidefloat2transform将被计算,并使用translateY()更新一个新的 Y 位置。

提示

注意,你必须使用this.bindOnProgress来禁用对onProgress的另一个事件绑定,因为对于每一次滑动,它都会继续添加更多的事件。

参见

为登录页面添加背景 CSS 动画

动画也可以完全使用 CSS 完成。在许多情况下,你可能会在网上遇到一些有趣的演示,并希望将仅使用 CSS 的动画代码集成进来。如果动画对用户体验不是那么关键,你只需使用它来为应用添加额外的效果。CSS 动画很棒,因为你不需要编写 JavaScript 代码来管理动画,只需让浏览器处理它即可。

在本节中,你将构建一个应用,在登录页面的背景中显示一些浮动方块,如图所示:

为登录页面添加背景 CSS 动画

准备工作

没有必要在物理设备上进行测试,因为在 Ionic 应用中 CSS 动画将运行得很好。

如何做到...

这里是说明:

  1. 使用blank模板创建一个新的BubbleLogin应用,如下所示,并进入BubbleLogin文件夹:

    $ ionic start BubbleLogin blank --v2
    $ cd BubbleLogin
    
    
  2. 打开./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> 列表转换为浮动正方形块。

  3. 使用以下内容编辑 ./src/pages/home/home.scss

    .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); }
       }
    
  4. 打开您的终端,并使用以下命令运行应用:

    $ ionic serve
    
    

它是如何工作的…

由于此应用不使用 JavaScript 进行动画,因此您不需要在 home.ts 中进行任何修改。

以下代码将使用 CSS 无限驱动动画:

animation: square 25s infinite;
transition-timing-function: linear;

您还将使用 square keyframe 中的两个点:

@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,因为它将保持在其他层(如表单和按钮)之上。

参见

第六章:使用 Ionic Cloud 进行用户认证和推送通知

在本章中,我们将涵盖与用户认证、注册和接收推送通知消息相关的以下任务:

  • 使用 Ionic Cloud 注册和验证用户

  • 构建用于接收推送通知的 iOS 应用

  • 构建用于接收推送通知的 Android 应用

简介

跟踪和吸引用户是您的应用增长的关键功能。这意味着您应该能够注册和验证用户。一旦用户开始使用应用,您还需要对用户进行细分,以便您可以定制他们的交互。然后,您可以发送推送通知来鼓励用户重新访问应用。

您的项目需要使用以下三个组件,如下所示:

  • Ionic Cloud:这是一个云服务,帮助您保存用户信息,并在您、苹果或谷歌与最终用户设备之间协调推送通知。

  • Ionic Cloud Angular 模块:这只是一个 Angular 模块,用于导入到您的本地项目中。它为您的代码提供了一些与 Ionic Cloud 交互的简单实用工具。否则,直接调用 Ionic Cloud API 会非常复杂。

  • Cordova 推送通知和 InAppBrowser:由于您的代码是用 JavaScript 编写的,您需要与设备的原生功能进行通信,例如推送通知。

使用 Ionic Cloud 注册和验证用户

Ionic Cloud 可以提供开箱即用的所有用户管理和认证功能。以下提供者是 Ionic Cloud 支持的:

  • 电子邮件/密码

  • 自定义认证

  • 脸书

  • Google

  • Twitter

  • Instagram

  • 领英

  • GitHub

根据应用的不同,您可能不需要使用所有这些认证方法。例如,对于专注于职业人士的应用,使用领英认证可能更有意义,以缩小符合应用用户画像的受众范围。如果您有自己的认证服务器,其中您维护自己的用户数据库,您仍然可以使用 Ionic Cloud 的自定义认证来创建自定义令牌。

本章将尽可能简化认证概念。您将学习如何完成以下事情:

  • 注册新用户

  • 登录和注销用户

  • 使用自定义字段更改用户个人资料数据

观察以下应用的截图:

使用 Ionic Cloud 注册和验证用户

准备工作

您可以通过浏览器运行此应用。无需使用物理设备测试用户认证。

如何操作...

观察以下说明:

  1. 使用 blank 模板创建一个新的 MySimpleAuth 应用,如图所示,并转到 MySimpleAuth 文件夹:

    $ ionic start MySimpleAuth blank --v2
    $ cd MySimpleAuth
    
    
  2. 使用以下命令安装 Ionic Cloud Angular:

    $ npm install @ionic/cloud-angular --save
    
    
  3. 初始化您的 Ionic Cloud 设置,以便在您的账户中创建一个应用 ID,如下所示:

    $ ionic io init
    
    

    注意

    执行此命令行时,将提示你登录到你的 Ionic Cloud 账户。初始化过程会将 app_idapi_key 值保存到你的项目 ionic.io.bundle.min.js 中。这意味着在此之后你不能更改你的 Ionic Cloud 账户中的项目(否则你将不得不手动删除 ID 并重新初始化)。你的应用 ID 也记录在 ionic.config.json 文件中。

  4. 登录到你的 apps.ionic.io 的 Ionic Cloud。

  5. 识别你初始化的新应用并复制应用 ID(即 7588ce26),如下截图所示:如何做...

  6. 使用以下代码打开并编辑 ./src/app/app.module.ts

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { CloudSettings, CloudModule } from '@ionic/cloud-angular';
    import { MyApp } from './app.component';
    import { HomePage } from '../pages/home/home';
    
    const cloudSettings: CloudSettings = {
      'core': {
        'app_id': '288db316'
      }
    };
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage
      ],
      imports: [
        IonicModule.forRoot(MyApp),
        CloudModule.forRoot(cloudSettings)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: []
    })
    export class AppModule {} 
    

    注意

    你需要确保 app_id 在你的情况下具有正确的值。

  7. 使用以下代码编辑并替换 ./src/pages/home/home.html:

    <ion-header>
      <ion-navbar>
        <ion-title>
          User Authentication
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content padding>
    
      <div *ngIf="!auth.isAuthenticated()">
    
        <h4>
          Register or Login
        </h4>
    
        <ion-list>
    
          <ion-item>
            <ion-label fixed>Email</ion-label>
            <ion-input type="text" [(ngModel)]="email"></ion-input>
          </ion-item>
    
          <ion-item>
            <ion-label fixed>Password</ion-label>
            <ion-input type="password" [(ngModel)]="password"></ion-input>
          </ion-item>
    
        </ion-list>
    
        <ion-grid>
          <ion-row>
            <ion-col width-50>
              <button ion-button block (click)="register()">Register</button>
            </ion-col>
            <ion-col width-50>
              <button ion-button block (click)="login()">Login</button>
            </ion-col>
          </ion-row>
        </ion-grid>
    
      </div>
    
      <div *ngIf="auth.isAuthenticated()">
    
        <h4>
          User Profile
        </h4>
    
        <ion-list *ngIf="auth.isAuthenticated()">
    
          <ion-item>
            <ion-label fixed>Name</ion-label>
            <ion-input class="right" type="text" [(ngModel)]="name"></ion-input>
          </ion-item>
    
          <ion-item>
            <ion-label>Birthday</ion-label>
            <ion-datetime displayFormat="MM/DD/YYYY" [(ngModel)]="birthday"></ion-datetime>
          </ion-item>
    
        </ion-list>
    
        <ion-grid>
          <ion-row>
            <ion-col width-50>
              <button ion-button color="secondary" block (click)="save()">Save</button>
            </ion-col>
            <ion-col width-50>
              <button ion-button color="dark" block (click)="logout()">Logout</button>
            </ion-col>
          </ion-row>
        </ion-grid>
    
      </div>
    
    </ion-content>
    

    注意

    这些只是基本的 登录注销 模板。所有内容都在一个页面上以保持简单。

  8. 使用以下代码打开并编辑 ./src/pages/home/home.ts

    import { Component } from '@angular/core';
    import { NavController, LoadingController, ToastController } from 'ionic-angular';
    import { Auth, User, UserDetails, IDetailedError } from '@ionic/cloud-angular';
    
    @Component({
      templateUrl: 'home.html'
    })
    export class HomePage {
      public email: string = "";
      public password: string = "";
      public name: string = "";
      public birthday: string = "";
    
      constructor(public navCtrl: NavController, public auth: Auth, public user: User, public loadingCtrl: LoadingController, public toastCtrl: ToastController) {
        this.initProfile();
      }
    
      private initProfile() {
        if (this.auth.isAuthenticated()) {
          this.name = this.user.get('name', '');
          this.birthday = this.user.get('birthday', '');
        }
      }
    
      register() {
        let details: UserDetails = {'email': this.email, 'password': this.password};
    
        let loader = this.loadingCtrl.create({
          content: "Registering user..."
        });
        loader.present();    
    
        this.auth.signup(details).then(() => {
          console.log('User is now registered');
          console.log(this.user);
          loader.dismiss();
          return this.auth.login('basic', {'email': this.email, 'password': this.password});
    
        }, (err: IDetailedError<string[]>) => {
          loader.dismiss();
    
          for (let e of err.details) {
            if (e === 'conflict_email') {
              alert('Email already exists.');
            } else {
              alert('Error creating user.');
            }
          }
        });
      }
    
      login() {
        let details: UserDetails = {'email': this.email, 'password': this.password};
    
        let loader = this.loadingCtrl.create({
          content: "Logging in user..."
        });
        loader.present();    
    
        this.auth.login('basic', details).then((data) => {
          console.log('Finished login');
          this.initProfile();
    
          loader.dismiss();
    
          console.log(data);
          console.log(this.user);
        }, (err) => {
          loader.dismiss();
          alert('Login Error');
        } );    
      }
    
      save() {
        let toast = this.toastCtrl.create({
          message: 'User profile was saved successfully',
          position: 'bottom',
          duration: 3000
        });
        toast.present();
    
        this.user.set('name', this.name);
        this.user.set('birthday', this.birthday);
        this.user.save();
      }
    
      logout() {
        this.auth.logout();
        this.email = '';
        this.password = '';
        this.name = '';
        this.birthday = '';
      }
    
    }
    

    注意

    上述代码提供了四种方法来注册、保存数据、登录和注销用户。

  9. 最后,在 ./src/pages/home/home.scss 中添加一个小的 input 字段对齐,如下所示:

    .home-page {
      ion-input.right > input {
        text-align: right;
      }
    }
    
  10. 前往你的终端并运行应用:

    $ ionic serve
    
    

如何工作...

简而言之,Ionic Cloud 作为你的应用的后端服务器。它允许你在其数据库中创建新的用户记录。通过 user 类,你可以与 Ionic Cloud 认证系统交互。第一步是注册用户。这需要一个 电子邮件密码,如下截图所示:

如何工作...

当你点击 注册 时,以下代码将被执行:

this.auth.signup(details).then(() => {
  console.log('User is now registered');
  console.log(this.user);
  loader.dismiss();
  return this.auth.login('basic', {'email': this.email, 'password': this.password});
}

details 对象包含表单中的电子邮件和密码。一旦成功完成,它将通过 this.auth.login 自动登录用户。

注意,这里的这段代码只是为了显示加载屏幕,防止用户多次点击注册按钮:

let loader = this.loadingCtrl.create({
  content: "Registering user..."
});
loader.present();  

此示例应用仅包含 姓名生日 作为自定义用户数据,如下截图所示:

如何工作...

要保存 用户资料,你调用 save() 方法,如下代码所示:

save() {
  let toast = this.toastCtrl.create({
    message: 'User profile was saved successfully',
    position: 'bottom',
    duration: 3000
  });
  toast.present();

  this.user.set('name', this.name);
  this.user.set('birthday', this.birthday);
  this.user.save();
}

如果你查看控制台日志,用户令牌和自定义数据也都可以看到,如下截图所示:

如何工作...

你也可以在 Ionic Cloud 门户中查看 UserData。登录到你的账户并导航到应用的 认证 菜单。

点击你创建的用户旁边的 查看 按钮,如下截图所示:

如何工作...

选择 自定义数据 选项卡,你可以看到为用户存储的相同信息,如下截图所示:

如何工作...

如需了解更多关于用户认证的信息,你可能需要参考官方的 Ionic 文档,网址为 docs.ionic.io/services/users/

构建一个用于接收推送通知的 iOS 应用

推送通知是吸引用户频繁参与的重要功能,尤其是在用户没有使用应用时。许多人下载了应用,但只打开了几次。如果你向他们发送推送通知消息,这将鼓励他们打开应用参与新的活动。如果你必须从头开始构建一切,实现推送通知非常复杂。然而,Ionic 通过利用 Cordova 推送通知插件和 Ionic Cloud 作为提供商,使这个过程变得非常简单。推送通知提供商是一个可以与 Apple 推送通知服务 (APNs) 或 Google 的 Firebase 云消息 (FCM) 通信的服务器。你可以使用现有的开源软件设置自己的提供商服务器,但你必须单独维护此服务器,并跟上 APN API 的潜在变化。

在本节中,你将学习以下内容:

  • 设置 Ionic Cloud 以支持 iOS 推送通知

  • 配置 iOS 应用、证书(应用和推送)以及配置文件

  • 编写代码以接收推送通知

以下是在接收了几条通知消息后应用的截图:

构建一个用于接收推送通知的 iOS 应用

准备工作

为了测试通知消息,需要有一个可用的物理 iOS 设备。

你还必须注册 Apple 开发者计划 (ADP),以便访问 developer.apple.comitunesconnect.apple.com,因为这些网站将需要经过批准的账户。

此外,以下说明使用了这些组件的具体版本:

  • Mac OSX El Capitan 10.11.4

  • Xcode 7.3.1

  • Ionic CLI 2.1.8

  • Cordova 6.4.0

  • Node 6.8.1

  • NPM 3.10.8

如何操作...

仔细阅读以下说明:

  1. 使用 blank 模板创建一个新的 MyiOSPush 应用,如图所示,然后进入 MyiOSPush 文件夹:

    $ ionic start MyiOSPush blank --v2
    $ cd MyiOSPush
    
    
  2. 安装 Ionic cloud-angular 客户端,这是一个用于与 push 对象交互的库,安装方法如下:

    $ npm install @ionic/cloud-angular --save
    
    

    注意

    你需要拥有 Node 版本 4.x 或更高版本以及 NPM 版本 3.x 或更高版本。

  3. 按照图示初始化你的 Ionic Cloud 设置,以便在你的账户中创建一个应用 ID:

    $ ionic io init
    
    

    注意

    在此命令行中,你将被提示登录你的 Ionic Cloud 账户。初始化过程将保存 app_idapi_key 值到你的项目的 ionic.io.bundle.min.js 文件中。这意味着在此之后你不能更改你的 Ionic Cloud 账户中的项目(否则你将不得不手动删除 ID 并重新初始化)。你的应用 ID 也记录在 ionic.config.json 文件中。

  4. 您需要安装 Cordova 推送通知插件,并提供一些值作为SENDER_ID。由于您仅使用 iOS 推送通知,您可以在这里提供一个假值,暂时如下所示:如何操作...

    $ cordova plugin add phonegap-plugin-push --variable SENDER_ID=12341234 --save
    
    
  5. 在项目文件夹中打开您的./ionic.config.json文件,并复制app_id值。在这种情况下,值是00f293c4,如下所示:

    {
      "name": "MyiOSPush",
      "app_id": "00f293c4",
      "v2": true,
      "typescript": true
    }
    
  6. 使用以下内容打开并编辑./src/app/app.module.ts

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { CloudSettings, CloudModule } from '@ionic/cloud-angular';
    import { MyApp } from './app.component';	
    import { HomePage } from '../pages/home/home';
    
    const cloudSettings: CloudSettings = {
      'core': {
        'app_id': '00f293c4'
      },
      'push': {
        'sender_id': 'SENDER_ID',
        'pluginConfig': {
          'ios': {
            'badge': true,
            'sound': true
          },
          'android': {
            'iconColor': '#343434'
          }
        }
      }
    };
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage
      ],
      imports: [
        IonicModule.forRoot(MyApp),
        CloudModule.forRoot(cloudSettings)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: []
    })
    export class AppModule {}
    

    注意

    您必须将'app_id': '00f293c4'替换为您自己的 App ID。

  7. 访问 Apple 开发者网站developer.apple.com,并使用您的凭据登录。

  8. 点击证书、标识符和配置文件,如图所示:如何操作...

  9. 选择您要针对的正确设备平台。在这种情况下,它将是iOS、tvOS、watchOS,如图所示:如何操作...

  10. 导航到标识符 > App ID以创建一个 App ID,如图所示:如何操作...

  11. 点击屏幕右上角的加号(+)按钮,如图所示:如何操作...

  12. 填写表格以注册您的App ID名称字段可以是任何内容,并且它与 Ionic Cloud 的 App ID 不同。您可以为您的项目(即MyiOSPush)提供名称以保持简单,如图所示:如何操作...

  13. 在这里需要正确操作的重要部分是捆绑 ID,因为它必须与./config.xml文件或 Xcode 中的捆绑标识符匹配,如图所示:如何操作...

  14. 要启用推送通知,您需要在以下页面检查推送通知服务:如何操作...

  15. 选择注册,如图所示:如何操作...

  16. 选择完成以完成创建App ID的步骤,如图所示:如何操作...

  17. 要开始证书创建,您需要在 Mac OSX 本地使用钥匙串访问生成证书签名请求文件。导航到钥匙串访问左上角的菜单,并导航到证书助手 > 从证书颁发机构请求证书…,如图所示:如何操作...

  18. 输入您的用户电子邮件地址通用名称。将CA 电子邮件地址字段留空,并勾选保存到磁盘,如图所示:如何操作...

  19. 保存您的CertificateSigningRequest.certSigningRequest文件,如下所示:如何操作...

  20. 导航到 Apple 开发者网站,并导航到证书 > 所有,如图所示:如何操作...

  21. 点击右上角的加号按钮开始创建证书,如下所示:如何操作...

  22. 您只需按照网站上的步骤填写必要的信息。在这个例子中,您将选择开发版本而不是生产版本,如图所示:如何操作...

  23. 按照以下方式点击继续按钮以继续:如何操作...

  24. 按如图所示点击选择文件…按钮,上传您之前保存的签名请求文件:如何操作...

  25. 点击如图所示的继续按钮以继续:如何操作...

  26. 点击下载按钮以下载您的 iOS 开发证书文件:如何操作...

  27. 点击您下载的.cer文件,如图所示,以便将其导入到钥匙串访问如何操作...

  28. 钥匙串访问中找到您的证书,因为您需要将其导出为.p12文件格式。Ionic Cloud 稍后需要此文件以生成推送令牌并向应用发送推送通知。观察以下截图:如何操作...

  29. 右键单击并从下拉菜单中选择导出如何操作...

  30. 按如图所示保存您的Certificates.p12文件,以便您可以在以后将其导入到 Ionic Cloud:如何操作...

  31. 如以下截图所示,为此文件提供密码以保护它:如何操作...

    注意

    在此步骤中,您必须提供密码,尽管在钥匙串访问中它是可选的。原因是 Ionic Cloud 无法在没有密码的情况下导入.p12文件。

  32. 如果您需要将应用推送到特定设备,您必须注册该设备。转到设备 > 所有如何操作...

  33. 点击加号按钮:如何操作...

  34. 提供设备的UDID并保存以注册设备。观察以下截图:如何操作...

  35. 您需要配置文件。导航到配置文件 > 所有如何操作...

  36. 点击加号按钮:如何操作...

  37. 选择iOS 应用开发作为您的配置文件,因为此示例仅针对开发版本:如何操作...

  38. 点击继续按钮:如何操作...

  39. 在下拉菜单中选择正确的App ID并保存以完成您的配置文件创建:如何操作...

  40. 点击继续按钮:如何操作...

  41. 选择您之前创建的 iOS 开发证书,如图所示:如何操作...

  42. 如以下截图所示,选择至少一个您想要安装应用进行测试的设备:如何操作...

  43. 为你的配置文件提供配置文件名称,如图所示:如何操作...

  44. 点击下载按钮以下载配置文件文件(即MyiOSPush_Provisioning_Profile.mobileprovision):如何操作...

  45. 点击你刚刚下载的MyiOSPush_Provisioning_Profile.mobileprovision,以便将其导入到 Xcode 中:如何操作...

    注意

    这一步非常重要,因为如果你没有将其导入到 Xcode 中,你的应用将无法成功构建。如果你的应用因为无效的配置文件而无法构建,最好在开发者控制台中检查配置文件状态。

  46. 要启用推送通知功能,你必须请求一个推送证书,它与应用证书不同。选择你之前创建的App ID(即MyiOSPush):如何操作...

  47. 点击页面底部的编辑按钮:如何操作...

    注意

    推送通知必须显示可配置状态。否则,你的应用将无法使用推送通知

  48. 点击推送通知 > 开发 SSL 证书部分下的创建证书...按钮:如何操作...

  49. 你将被带到新页面以创建你的 CSR 文件。点击继续按钮:如何操作...

  50. 点击底部的选择文件...按钮:如何操作...

  51. 找到你之前创建的CertificateSigningRequest.certSigningRequest文件:如何操作...

    注意

    你必须上传与应用证书相同的.certSigningRequest文件。否则,你的应用将无法接收推送通知,并且很难调试。

  52. 点击继续按钮:如何操作...

  53. 点击下载按钮以下载证书文件。你可以将其命名为aps_certificate.cer以避免覆盖早期的.cer文件:如何操作...

  54. 下载完.cer文件后,你需要点击它以将其导入到钥匙串访问如何操作...

  55. 钥匙串访问中找到新的推送服务证书并选择它,如图所示:如何操作...

  56. 右键单击证书并选择导出如何操作...

  57. 给它一个新的名称以避免覆盖应用证书。这个过程基本上是将.cer文件转换为.p12文件以供 Ionic Cloud 使用:如何操作...

  58. 为这个.p12文件提供密码以保护它:如何操作...

    注意

    .p12文件的密码是必需的,因为 Ionic Cloud 不会导入没有密码的 APN .p12文件。

  59. 完成在 Apple 开发者网站上的设置后,您需要将配置文件和两个证书(应用和推送)上传到 Ionic Cloud。导航到apps.ionic.io并使用您的凭据登录。

  60. 选择为此项目生成的应用(即MyiOSPush):如何操作...

  61. 导航到设置 > 证书如何操作...

  62. 选择新建安全配置文件如何操作...

  63. 提供一个配置文件名称并点击创建按钮以保存配置文件:如何操作...

  64. 选择您刚刚创建的MyPushProfile旁边的编辑以编辑设置:如何操作...

  65. 您需要上传三个文件:一个配置文件,一个用于应用本身的应用开发证书,以及一个用于推送通知的 APN 证书。让我们从应用要求开始。在构建凭据下的选择文件处点击,上传两个文件:Certificates.p12(第 30 步)和MyiOSPush_Provisioning_Profile.mobileprovision(第 44 步)。确保您提供与之前保护.p12文件相同的密码:如何操作...

  66. 推送通知服务下点击选择文件并上传push_Certificates.p12文件(第 58 步)。确保您提供保护此文件的相同密码:如何操作...

    备注

    您不应混淆两个.p12文件,因为一个是您的应用,另一个是您的推送通知功能。

  67. 点击保存按钮以保存您的安全配置文件。这完成了您的推送通知的 Ionic Cloud 设置。

  68. 您需要修改主页代码以接收通知消息。打开并编辑./src/pages/home/home.html,然后粘贴以下代码:

    <ion-header>
      <ion-navbar>
        <ion-title>
          Push Notification
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content padding>
    
      <code class="center">{{ pushToken }}</code>
      <button ion-button block [disabled]="clicked" [hidden]="pushToken" (click)="registerPush()">
        <span [hidden]="clicked">Register Push</span>
        <span [hidden]="!clicked">Registering...</span>
      </button>
    
      <h2 class="big-square" *ngIf="!pushToken">
        You have no message
      </h2>
    
      <h3 class="sub-title" *ngIf="pushToken">
        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>
    
  69. 将同一文件夹中的home.ts文件的内容替换为以下代码:

    import { Component, ApplicationRef } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { Push, PushToken } from '@ionic/cloud-angular';
    
    @Component({
      templateUrl: 'home.html'
    })
    export class HomePage {
      public push: any;
      public pushToken: string;
      public messages = [];
      public clicked: Boolean = false;
      constructor(public navCtrl: NavController, push: Push, private applicationRef: ApplicationRef) {
        this.push = push;
      }
    
      private processPush(msg, that) {
        console.log('Push notification message received');
        console.log(msg);
        this.messages.push({
          title: msg.title,
          text: msg.text
        })
        this.applicationRef.tick();
      }
    
      registerPush() {
        this.clicked = true;
    
        this.push.register().then((t: PushToken) => {
          return this.push.saveToken(t);
        }).then((t: PushToken) => {
    
          this.push.rx.notification().subscribe(msg => this.processPush(msg, this));
    
          console.log('Token saved:', t.token);
          this.pushToken = t.token;
        }, (err) => {
          alert('Token error');
          console.log(err);
        });
    
      }  
    }
    
  70. /home文件夹中的home.scss替换为以下代码:

    .home-page {
      .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;
      }
    }
    
  71. 通过 USB 连接将您的物理 iPhone 连接到 Mac。

  72. 确保您位于应用文件夹中,并针对 iOS 平台进行构建,如下所示:

    $ ionic run ios --device
    
    

    备注

    由于 Ionic 2 存在一个现有错误,您可能需要在末尾包含--device参数。

  73. 操作系统将提示允许使用 iOS 开发者证书进行 codesign 签名。您必须接受此提示以允许访问,以便构建应用并将其上传到您的设备:如何操作...

  74. 验证应用是否在设备上成功运行。初始屏幕应如图所示:如何操作...

到目前为止,您已完成了推送通知的设置和编码。下一步是验证您是否通过应用接收通知。以下是说明:

  1. 在移动应用中点击 注册推送 按钮以向 Ionic Cloud 提供者服务器注册推送通知并获取令牌。点击 确定 接受接收推送通知的权限,如下所示:如何操作...

  2. 导航到 Ionic Cloud > 您的应用 (MyiOSPush) > 推送如何操作...

  3. 点击 创建您的第一个推送 按钮:如何操作...

  4. 填写推送通知表单以创建您的第一个推送消息:如何操作...

  5. 在右侧屏幕上验证以确保推送消息显示如预期:如何操作...

  6. 将段保留为 所有用户,以便任何人都可以接收推送:如何操作...

  7. 选择您之前创建的安全配置文件(即 mypushprofile)。然后,点击 发送此推送 按钮:如何操作...

  8. 验证从 Ionic Cloud 发送的推送通知是否成功。它应该有 已发送 状态,如下所示:如何操作...

  9. 在您的移动设备上的应用中,验证推送消息是否已按图示出现:如何操作...

您已成功完成验证步骤。

它是如何工作的...

为了理解整个流程是如何工作的,让我们总结一下您所做的工作,如下所示:

  • 创建了一个 Ionic 项目并将其初始化以创建一个 Ionic Cloud 项目:

    • 您本地的 Ionic 项目必须与 Ionic Cloud 项目同步,以便进行推送通知和其他管理,例如用户身份验证
  • 通过以下步骤设置您的苹果开发者账户:

    • 创建了一个应用 ID

    • 创建了应用证书(在本地通过 Keychain Access 创建签名请求之后)

    • 创建了一个配置文件

    • 创建了一个推送证书

  • 设置您的 Ionic Cloud 账户:

    • 创建了一个安全配置文件。

    • 从苹果开发者那里导入三个文件:一个应用证书、一个配置文件和一个推送证书。这些文件是必需的,以便 Ionic Cloud 可以成为受信任的提供者,与苹果的 APN 服务器通信以触发推送通知。

  • 在您的应用中编写了接收通知的代码:

    • 您需要安装 Ionic Cloud Angular 库和 Cordova 推送通知插件。基本思想是使 push 对象可供应用使用。此 push 对象已配置为使用您的 Ionic Cloud 推送提供者。

现在,让我们专注于编码部分本身,以了解它是如何工作的。

您需要更改 app.module.ts 中的应用启动方式。这需要导入 provideCloudCloudSettings 提供者,如下所示:

import { provideCloud, CloudSettings } from '@ionic/cloud-angular';

除了将 app_id 设置为与 Ionic Cloud 中的项目 app_id 匹配之外,您还需要指定具有您想要为 iOS 和 Android 设置的参数的 push 对象,如下所示:

const cloudSettings: CloudSettings = {
  'core': {
    'app_id': '00f293c4'
  },
  'push': {
    'sender_id': 'SENDER_ID',
    'pluginConfig': {
      'ios': {
        'badge': true,
        'sound': true
      },
      'android': {
        'iconColor': '#343434'
      }
    }
  }
};

然后,在 NgModule 内部,您需要插入以下行,以便 Ionic 知道它还需要初始化 Ionic Cloud:

CloudModule.forRoot(cloudSettings)

在您的 home.html 模板中,有一个按钮可以通过调用 registerpush() 触发推送通知的注册:

<code class="center">{{ pushToken }}</code>
<button ion-button block [disabled]="clicked" [hidden]="pushToken" (click)="registerPush()">
  <span [hidden]="clicked">Register Push</span>
  <span [hidden]="!clicked">Registering...</span>
</button>

此注册过程必须由用户手动干预,因为用户将需要在下一步接受权限。不建议在用户立即打开应用时就要求他们接受推送通知请求。主要原因是因为他们不熟悉您的应用,不知道会期待什么(即,他们是否会后来被通知轰炸)。

消息将通过 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 项目都有 titletext 字段。

home.ts 文件中,有两个关键的导入项,您必须注意:PushPushToken 是注册和接收推送通知所必需的。ApplicationRef 将在稍后讨论,因为您需要手动触发 Angular 模板的重新渲染,如下所示:

import { Component, ApplicationRef } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Push, PushToken } from '@ionic/cloud-angular';
The registerPush() method is the key to acquire the PushToken from Ionic Cloud, as shown:
registerPush() {
  this.clicked = true;

  this.push.register().then((t: PushToken) => {
    return this.push.saveToken(t);
  }).then((t: PushToken) => {

    this.push.rx.notification().subscribe(msg => this.processPush(msg, this));

    console.log('Token saved:', t.token);
    this.pushToken = t.token;
  }, (err) => {
    alert('Token error');
    console.log(err);
  });
}  

您只需要调用 this.push.register() 函数。这将返回一个 PushToken 对象,您可以在以下控制台日志的屏幕截图中看到:

如何工作...

要接收通知,您需要使用以下代码进行订阅:

this.push.rx.notification().subscribe()

这将在每次有新的通知消息时调用 processPush(),如下所示:

private processPush(msg, that) {
  console.log('Push notification message received');
  console.log(msg);
  this.messages.push({
    title: msg.title,
    text: msg.text
  })
  this.applicationRef.tick();
}

当用户收到推送消息时,此函数将向 messages 数组中添加。如果您不调用 this.applicationRef.tick(),由于此过程在 Angular 生命周期之外,UI 将不会更新。如果您查看控制台日志,PushMessage 将如下所示,其中包含 texttitle 字段:

如何工作...

如果用户没有打开应用,您会看到通知出现在通知区域。

还有更多...

Ionic 有自己的 iOS 设置说明页面,如下所示:

Cordova 推送通知插件可以直接在 github.com/phonegap/phonegap-plugin-push 找到。

关于 Apple 推送通知服务的更多信息,您可以访问官方文档 developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html

构建一个用于接收推送通知的 Android 应用

Google 推送通知与 iOS 的工作方式相同。但是,您将通过 Firebase Cloud Messaging 服务器进行操作,这是 Google Cloud Messaging(GCM)的新替代品。然而,Ionic Cloud 抽象了此过程,因此您无需使用不同的 API 进行编码。您将使用与 iOS 应用相同的 push 对象。

注意

有关 FCM 和 GCM 之间差异的更多信息,请访问firebase.google.com/support/faq中的常见问题解答。

在本节中,您将学习如何完成以下事项:

  • 设置 Android 推送通知的 Ionic Cloud

  • 配置 Firebase 项目以使用推送 API

  • 编写 Android 接收推送通知的代码

您将使用与您的 iOS 推送通知示例相同的代码库。主要区别在于在您的 Firebase 和 Ionic Cloud 账户中设置的过程。

准备工作

您可以使用 Android 模拟器测试 Android 推送通知。因此,无需物理 Android 设备。

您还必须注册 Firebase 以访问console.firebase.google.com

此外,以下说明使用了这些组件的特定版本:

如何操作...

这里是说明:

  1. 使用 blank 模板创建一个新的 MyAndroidPush 应用,如下所示,并转到 MyAndroidPush 文件夹:

    $ ionic start MyAndroidPush blank --v2
    $ cd MyAndroidPush
    
    
  2. 安装 Ionic Cloud Angular 客户端库,以与 push 对象交互,如图所示:

    $ npm install @ionic/cloud-angular --save
    
    

    注意

    您需要安装 Node 版本 4.x 或更高版本以及 NPM 版本 3.x 或更高版本。

  3. 初始化您的 Ionic Cloud 设置,以便在您的账户中创建一个应用 ID,如下所示:

    $ ionic io init
    
    

    注意

    在此命令行中,您将被提示登录到您的 Ionic Cloud 账户。

  4. 您需要 Firebase 项目编号和 Firebase 服务器 ID 才能正确设置您的 app.ts 文件。首先,让我们登录到 Firebase 控制台console.firebase.google.com

  5. 点击创建新项目按钮,并填写一个项目名称(即MyAndroidPush):如何操作...

  6. 导航到左侧导航菜单中的增长 > 通知如何操作...

  7. 选择 Android 图标:如何操作...

    注意

    Firebase 云消息服务也支持 iOS 应用。因此,你可能可以在 iOS 和 Android 项目中都使用 FCM。

  8. 在表单中提供包名。你可以从你的应用项目在./config.xml中的包名复制并粘贴:如何操作...

  9. 选择继续并保存 JSON 文件到某个位置。你不需要这个 JSON 文件用于 Ionic 项目:如何操作...

  10. 点击完成按钮以完成设置通知服务:如何操作...

  11. 现在,你需要服务器密钥发送者 ID。导航到左上角的齿轮图标,并选择项目设置菜单项:如何操作...

  12. 选择云消息选项卡:如何操作...

  13. 复制服务器密钥发送者 ID(如果使用 Google Cloud Messaging,则与项目 ID相同):如何操作...

  14. 返回到终端并确保你处于 Ionic 项目文件夹中。

  15. 你需要安装 Cordova 推送通知插件,并提供与之前命令行中的SENDER_ID值相同的发送者 ID,如下所示:

    $ cordova plugin add phonegap-plugin-push --variable SENDER_ID=749277510481 --save
    
    
  16. 在项目文件夹中打开你的./ionic.config.json文件,并从以下代码中复制app_id值(在这个例子中,值是e546b9f6):

    {
      "name": "MyAndroidPush",
      "app_id": "e546b9f6",
      "v2": true,
      "typescript": true
    }
    
  17. 使用以下内容打开并编辑./src/app/app.module.ts

    import { NgModule } from '@angular/core';
    import { IonicApp, IonicModule } from 'ionic-angular';
    import { MyApp } from './app.component';
    
    import { CloudSettings, CloudModule } from '@ionic/cloud-angular';
    
    import { HomePage } from '../pages/home/home';
    
    const cloudSettings: CloudSettings = {
      'core': {
        'app_id': 'e546b9f6'
      },
      'push': {
        'sender_id': '749277510481',
        'pluginConfig': {
          'ios': {
            'badge': true,
            'sound': true
          },
          'android': {
            'iconColor': '#343434'
          }
        }
      }
    };
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage
      ],
      imports: [
        IonicModule.forRoot(MyApp),
        CloudModule.forRoot(cloudSettings)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: []
    })
    export class AppModule {}
    

    注意

    你必须将'app_id': '00f293c4'替换为你自己的应用 ID。此外,你还需要在这里再次提供发送者 ID

  18. 你的主页代码与 iOS 推送示例非常相似。打开并编辑./src/pages/home/home.html,并粘贴以下代码:

    <ion-header>
      <ion-navbar>
        <ion-title>
          Push Notification
        </ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content padding>
    
      <code class="center">{{ pushToken }}</code>
      <button ion-button block [disabled]="clicked" [hidden]="pushToken" (click)="registerPush()">
        <span [hidden]="clicked">Register Push</span>
        <span [hidden]="!clicked">Registering...</span>
      </button>
    
      <h2 class="big-square" *ngIf="!pushToken">
        You have no message
      </h2>
    
      <h3 class="sub-title" *ngIf="pushToken">
        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>
    
  19. 将同一文件夹中的home.ts文件的内容替换为以下代码:

    import { Component, ApplicationRef } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { Push, PushToken } from '@ionic/cloud-angular';
    
    @Component({
      templateUrl: home.html'
    })
    export class HomePage {
      public push: any;
      public pushToken: string;
      public messages = [];
      public clicked: Boolean = false;
      constructor(public navCtrl: NavController, push: Push, private applicationRef: ApplicationRef) {
        this.push = push;
      }
    
      private processPush(msg, that) {
        console.log('Push notification message received');
        console.log(msg);
        this.messages.push({
          title: msg.title,
          text: msg.text
        })
        this.applicationRef.tick();
      }
    
      registerPush() {
        this.clicked = true;
    
        this.push.register().then((t: PushToken) => {
          return this.push.saveToken(t);
        }).then((t: PushToken) => {
    
          this.push.rx.notification().subscribe(msg => this.processPush(msg, this));
    
          console.log('Token saved:', t.token);
          this.pushToken = t.token;
        }, (err) => {
          alert('Token error');
          console.log(err);
        });
    
      }  
    }
    
  20. home.scss替换为以下代码,也在/home文件夹中:

    .home-page {
      .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;
      }
    }
    
  21. 如果你第一次创建此应用,你必须有一个keystore文件。此文件用于识别你的应用以进行推送和发布。如果你丢失它,你以后无法更新你的应用。要创建keystore,请输入以下命令行,并确保它与 SDK 的keytool版本相同(即检查你的PATH环境变量):

    $ keytool -genkey -v -keystore MyAndroidPushKey.keystore -alias MyAndroidPushKey -keyalg RSA -keysize 2048 -validity 10000
    
    
  22. 一旦你在命令行中填写了信息,请将此文件的副本保存在安全的地方,因为你以后需要它。观察以下代码:

    Enter keystore password:
    Re-enter new password:
    What is your first and last name?
     [Unknown]:  Hoc Phan
    What is the name of your organizational unit?
     [Unknown]:
    What is the name of your organization?
     [Unknown]:
    What is the name of your City or Locality?
     [Unknown]:
    What is the name of your State or Province?
     [Unknown]:
    What is the two-letter country code for this unit?
     [Unknown]:
    Is CN=Hoc Phan, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
     [no]:  yes
    
    Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days
     for: CN=Hoc Phan, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
    Enter key password for <MyAndroidPushKey>
     (RETURN if same as keystore password):
    [Storing MyAndroidPushKey.keystore]
    
    

    注意

    你必须为此步骤提供密码。否则,Ionic Cloud 不会让你上传keystore文件。

  23. 现在,登录到你的 Ionic Cloud apps.ionic.io以配置安全配置文件。

  24. 导航到你的项目(即 MyAndroidPush)并选择设置菜单:如何操作...

  25. 选择证书,如下所示:如何操作...

  26. 创建一个新的安全配置文件并提供一个名称(例如,MyAndroidPush):如何操作...

  27. 选择编辑,如图所示:如何操作...

  28. 上传你的 keystore 文件(例如,MyAndroidPushKe``y.keystore),别名(例如,MyAndroidPushKey),以及步骤 22 中的密码:如何操作...

  29. 将步骤 13 中的 Server key 粘贴到 GCM API Key 输入框中:如何操作...

  30. 选择保存以完成创建安全配置文件。

  31. 返回终端。

  32. 确保你处于应用文件夹中,并针对 Android 平台进行构建,如下所示:

    $ ionic emulate android
    
    

验证 Android 推送通知的过程与 iOS 非常相似。以下是说明:

  1. 导航到 Ionic Cloud > 你的应用(MyAndroidPush)> 推送如何操作...

  2. 点击创建第一个推送按钮:如何操作...

  3. 从创建第一个推送消息填写推送通知表单:如何操作...

  4. 将段设置为所有用户,以便任何人都可以接收推送:如何操作...

  5. 选择你之前创建的安全配置文件(即 MyAndroidPush)。然后,点击发送此推送按钮。观察以下截图:如何操作...

  6. 验证从 Ionic Cloud 发送的推送通知是否成功。它应该有已发送状态,如图所示:如何操作...

  7. 在你的模拟器设备上的应用中,验证推送消息是否已出现,如图所示:如何操作...

你已成功完成验证步骤。

它是如何工作的...

从高层次来看,这是幕后通信过程:

  • 你的设备向谷歌的 Firebase 服务器发送推送通知的注册请求。你的设备的应用程序必须具备以下内容:

    • Firebase 项目 ID

    • Firebase 发送者 ID

  • 谷歌将回复注册 ID(它与 Ionic push 对象的推送令牌相同)。

  • 你的应用将向 Ionic Cloud(或任何推送提供者服务器)发送注册 ID。此过程在幕后进行,因为 Ionic Cloud Angular 调用 Ionic Cloud API 来执行。然而,你的 Ionic Cloud 必须有一个包含以下列出的四条信息的安全配置文件:

    • Firebase 的服务器密钥

    • 密钥库

    • 密钥库别名

    • 密钥库密码

  • Ionic Cloud 将保存此注册 ID 以请求谷歌稍后发送推送通知。您可以通过 Ionic Cloud UI 触发通知。

这与 Ionic Cloud 与苹果的合作方式非常相似。所有内容都简化为与 Ionic Cloud Angular 模块中的push对象交互。

要接收控制台输出,您可以导航到谷歌的 URL chrome://inspect/#devices。这将提供可用的模拟器列表以进行调试。点击检查链接以打开谷歌开发者工具:

如何工作...

您应该能够看到以下相同的屏幕:

如何工作...

应用程序输出一个令牌(即注册 ID),每个推送通知都将是一个PushMessage对象。您可以打开并检查其属性。

总结来说,Android 推送通知之所以工作得很好,是因为 Ionic Cloud、Ionic Angular 模块和 Firebase 云消息传递之间的即插即用集成。

还有更多...

Ionic 有自己的 Android 设置说明页面,如下所示:

关于 Firebase 通知服务的更多信息,您可以访问官方文档firebase.google.com/docs/cloud-messaging/

第七章. 使用 Ionic Native 支持设备功能

在本章中,我们将介绍与原生设备功能支持相关的以下任务:

  • 使用相机插件拍照

  • 使用社交分享插件共享内容

  • 使用 InAppBrowser 显示服务条款

  • 使用 Google Maps 插件和地理编码支持创建出租车应用

简介

在本章中,你将学习如何访问设备的一些常见功能,例如相机、联系人列表、电子邮件和地图。其中一些功能可以在仅 JavaScript 的环境中编写,但性能并不与原生支持相媲美。

Cordova 有一个非常受支持的社区和许多插件。你可能想查看 plugins.cordova.io/ 以了解有哪些选择。幸运的是,你不需要直接处理这些插件。你将使用基于 Cordova 和 Angular 2 的 Ionic Native (ionicframework.com/docs/v2/native/) 服务。请注意,由于兼容性问题,你必须使用 Ionic Native 而不是 ngCordova 来支持 Ionic 2。你只能使用 ngCordova 来支持 Ionic 1.x。

使用相机插件拍照

对于本节,你将制作一个应用,使用设备相机拍照或从设备相册加载现有图片。图片可以是 Base64 格式,也可以保存在与你的应用相关的本地文件系统中。以下是该应用的截图:

使用相机插件拍照

这里是高级流程:

  • 访问 Cordova 相机插件以触发相机捕获并获取 Base64 或 URI 格式的图像

  • <img> DOM 对象上解析 Base64 数据或 URI

  • 如果是 URI 格式,显示 URI

  • 捕获切换组件的事件

  • 使用水平滚动显示长数据(例如,URI)

准备工作

你应该准备一个物理设备来测试相机功能。你可以通过模拟器运行代码,但不同平台上的文件系统支持可能会有所不同。

如何做到这一点...

以下是在添加相机支持时的说明:

  1. 开始一个空白项目(例如,MyCamera)并进入该文件夹:

    $ ionic start MyCamera blank --v2
    $ cd MyCamera
    
    
  2. 使用以下代码添加 Cordova 相机插件:

    $ ionic plugin add cordova-plugin-camera
    
    

    小贴士

    你不再需要单独添加 ngCordova。此外,你不应该直接使用 cordova add 命令行;相反,使用 ionic plugin add

    你应该能够看到在 /plugins 文件夹中添加了一个新的文件夹 org.apache.cordova.camera

  3. ./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>
    

    由于你只有一个页面,这个模板将显示两个按钮和一个显示图像的区域。

  4. ./src/pages/home/home.ts 替换为以下代码:

    import { Component, Input } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { Camera } from 'ionic-native';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
      public imageData: string;
      @Input('useURI') useURI: Boolean = true;
    
      constructor(public navCtrl: NavController) {
      }
    
      getPicture(sourceType){
        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);
        });
      }  
    }
    

    只有一个方法:getPicture()。此方法将返回照片数据,以便模板可以渲染。

  5. /app/pages/home/home.scss 替换为以下代码:

    .center {
      text-align: center;
    }
    
    .no-border .item-inner {
      border-bottom: 0;
    }
    

    样式上只有少数的改动,这样你可以保持它们简单。

  6. 将你的设备连接到电脑。

  7. 前往终端并执行以下命令行以运行 iOS:

    $ ionic run ios
    
    

    注意

    如果你无法使用前面的命令行将应用推送到物理设备,你可以使用ionic run ios --device来指定 CLI,使其使用物理设备而不是模拟器。

    如果你想在 Android 设备上运行应用,请使用以下代码:

    $ ionic run android
    
    
  8. 当你运行应用并拍照时,你应该会看到如下截图所示的应用界面:如何操作...

它是如何工作的...

Camera.getPicture()只是 Cordova 相机插件中的navigator.camera.getPicture()的一个抽象。如果你已经熟悉 Cordova 或 Ionic 1 中的 ngCordova,这应该非常熟悉。让我们从模板开始。你有以下两个按钮,它们触发相同的getPicture()方法:

  • <button ion-button (click)="getPicture(1)">显示相机</button>

  • <button ion-button (click)="getPicture(0)">显示相册</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;

useURIsourceType都将用于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);
});

调整质量、targetWidthtargetHeight到低值,这样照片不会太大,否则可能会使设备崩溃,尤其是在内存不足的情况下。当你返回 Base64 数据时,必须在字符串前加上data:image/jpeg;base64

这里没有讨论的一项功能是向服务器发送图像数据的能力。常见的场景是从文件系统中上传文件。由于数据大小是原始二进制大小的两倍,所以发送 Base64 数据不是一个好主意。

还有更多...

只需使用 JavaScript 就可以创建类似 Instagram 的滤镜效果。你可以利用现有的库,例如 Filterous (github.com/girliemac/Filterous),直接修改图像画布。

在 GitHub 上有一个 Instagram 插件(github.com/vstirbu/InstagramPlugin),适用于 Cordova。你可以编写一些额外的代码将图像传递到 Instagram。不过,用户必须首先在手机上安装 Instagram。当你计划在 Instagram 执行照片滤镜操作之前做一些酷炫的图像处理(例如,添加有趣的文字)时,这个想法很不错。

您甚至可以添加 Cordova 的社交网络插件,并将生成的图片发布到推特或 Facebook。

使用社交分享插件分享内容

如果您开发一个具有可分享内容的应用程序,您可能希望利用设备的原生功能通过设备授权的社交媒体账户进行分享。使用这种方法有几个好处。首先,用户不需要每次分享时都打开一个单独的浏览器来登录他们的社交媒体账户。其次,所有信息都可以通过编程方式填写,例如标题、正文、链接或图片。最后,由于这是设备的原生功能,菜单选择允许用户看到他们已经熟悉的多个账户,从而进行选择。社交分享插件可以极大地提升用户体验。

这是您将要构建的应用程序:

使用社交分享插件分享内容

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

使用社交分享插件分享内容

如果用户选择推特,将弹出一个包含所有预先填写信息的弹出窗口,如下所示:

使用社交分享插件分享内容

在推特上发布后,用户可以直接返回应用程序,而无需离开。

准备工作

您应该准备好一个物理设备或模拟器,以便测试社交分享功能。

如何操作...

以下是指令:

  1. 开始一个空白项目(例如,LinkSocialShare),如下所示,并进入该文件夹:

    $ ionic start LinkSocialShare blank --v2
    $ cd LinkSocialShare
    
    
  2. 使用以下命令行添加 Cordova 摄像头插件:

    $ ionic plugin add cordova-plugin-x-socialsharing
    
    
  3. 打开 ./src/pages/home/index.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 2 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 逻辑都将集中在 分享 按钮上:

  1. 打开 ./src/pages/home/home.ts,如下所示:

    import { Component } from '@angular/core';
    import { NavController } from 'ionic-angular';
    import { SocialSharing } from 'ionic-native';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
    
      constructor(public navCtrl: NavController) {
      }
    
      sendShare(message, subject, url) {
        SocialSharing.share(message, subject, null, url);
      }
    }
    
  2. 前往终端并执行以下任意一条命令行:

    $ ionic run ios
    $ ionic run android
    
    

它是如何工作的...

您可以开始查看模板,因为社交媒体内容就是从这里提取的。主题值来自 #messageSubject 本地变量,如下所示:

<ion-item>
  <h2 #messageSubject>Ionic Developer</h2>
  <p>May 5, 2016</p>
</ion-item>

在前面的例子中,主题是 Ionic Developer,因为您稍后将会访问 messageSubject.innerTextmessageSubject 只是对您的 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';

Ionic 2 使得这非常方便,因为您不需要单独安装 ngCordova。Ionic Native 实际上是项目创建时自带的一个默认选项。

要分享你的内容并触发社交媒体菜单,逻辑,如图所示,非常简单:

sendShare(message, subject, url) {
  SocialSharing.share(message, subject, null, url);
}

如果你想要分享一个文件,你可以将第三个参数(其中是 null)替换为用户本地文件系统的 URL。这在你想让人们通过电子邮件发送 PDF 或 JPG 或者在 Facebook 上发布时非常有用。

更多内容...

使用 InAppBrowser 显示服务条款

在许多应用程序中,你有时需要用户在接受服务条款之前才能继续到下一页。典型的方法是创建一个弹出模态或新页面来显示服务条款。一旦用户阅读完毕,他们可以点击 完成返回 按钮。然而,如果你的服务条款内容发生变化,你可能需要要求用户更新应用程序。在许多情况下,用户不会经常更新应用程序。因此,他们接受的服务条款可能比你的当前版本要旧。因此,有必要将服务条款内容与应用程序本身分开维护。InAppBrowser 插件是最佳解决方案,因为你可以将用户指向已经在你的网站上存在的相同的服务条款页面。

应用程序将只有一个简单的复选框和按钮来演示 InAppBrowser 的工作方式:

使用 InAppBrowser 显示服务条款

一旦用户点击 请同意我们的条款 复选框,他们就会转到 InAppBrowser 页面:

使用 InAppBrowser 显示服务条款

在阅读完服务条款内容后,他们可以点击 完成 并使用启用的 下一步 按钮返回上一页:

使用 InAppBrowser 显示服务条款

准备工作

你应该准备一个物理设备来测试 InAppBrowser,因为作为 iframe 它在浏览器中不会工作。

如何操作...

这里是说明:

  1. 创建一个空的 Ionic 应用(例如,OnlineTOS)并 cd 到该文件夹,如图所示:

    $ ionic start OnlineTOS blank --v2
    $ cd OnlineTOS
    
    
  2. 使用以下命令安装 InAppBrowser 插件:

    $ ionic plugin add cordova-plugin-inappbrowser
    
    
  3. 在项目文件夹中打开 /config.xml 并在 <access origin="*"/> 之下插入以下两行,如图所示:

    <allow-navigation href="http://*/*" />
    <allow-navigation href="https://*/*" />
    

    这将告诉 Cordova 允许导航到任何网站是安全的。否则,设备安全将阻止 InAppBrowser。你可以在 Cordova 文档页面 cordova.apache.org/docs/en/latest/reference/cordova-plugin-whitelist/ 上了解更多信息。

  4. 打开 ./src/pages/home/index.html 并替换为以下代码:

    <ion-content class="home">
      <div class="top-header center">
        <ion-icon name="alert"></ion-icon>
        <br/>
        <h3>
          Important Information
        </h3>
      </div>
      <ion-item>
        <ion-label>Please agree to our terms</ion-label>
        <ion-checkbox dark (click)="openTOS()"></ion-checkbox>
      </ion-item>
      <div class="center">
        <button ion-button class="long" [(disabled)]="!readTOS">Next</button>
      </div>
    </ion-content>
    
  5. 打开 ./src/pages/home/home.ts 并替换为以下代码:

    import { Component } from '@angular/core';
    import { Platform } from 'ionic-angular';
    import { InAppBrowser } from 'ionic-native';
    
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
      private platform: any;
      public readTOS: Boolean = false;
    
      constructor(platform: Platform) {
        this.platform = platform;
      }
    
      openTOS() {
        this.readTOS = !this.readTOS;
        this.platform.ready().then(() => {
          let ref = new InAppBrowser('https://ionic.io/tos','_blank');
          ref.on('exit').subscribe(() => {
            console.log('Exit In-App Browser');
          });
        });
      }
    }
    
  6. 打开 ./src/pages/home/home.scss 并替换为以下代码:

    .home {
      .item-inner {
        border-bottom: 0;
      }  
    }
    
    .top-header {
      margin-top: 50px;
      margin-bottom: 50px;
    
      ion-icon {
        color: #EB6B56;
        font-size: 100px;
      }
    
      h3 {
        color: #75706B;
      }
    }
    
    .center {
      text-align: center;
    }
    
    .long {
      padding: 0 5em;
    }
    

    这只是为了给页面提供一些微小的样式。

  7. 使用以下命令行在终端中运行应用:

    $ ionic run ios
    $ ionic run android
    
    

它是如何工作的…

首先,让我们看看 template home.html

  <ion-item>
    <ion-label>Please agree to our terms</ion-label>
    <ion-checkbox dark (click)="openTOS()"></ion-checkbox>
  </ion-item>
  <div class="center">
    <button ion-button class="long" [(disabled)]="!readTOS">Next</button>
  </div>

有两个区域你应该注意。当 <ion-checkbox> 组件被点击时,它将触发 openTOS() 方法,通过 InAppBrowser 打开 URL。下一步按钮默认禁用(通过 readTOS 变量)。所以,当用户勾选复选框时,这个 readTOS 将为 True,按钮将被启用。

在你的 home.ts 中,你必须首先导入 InAppBrowser 模块,如下所示:

import { InAppBrowser } from 'ionic-native';

这将使 InAppBrowser 对象可用于你的类中。

这里是 openTOS() 方法:

  openTOS() {
    this.readTOS = !this.readTOS;
    this.platform.ready().then(() => {
      let ref = new InAppBrowser('https://ionic.io/tos', '_blank');
      ref.on('exit').subscribe(() => {
        console.log('Exit In-App Browser');
      });
    });
  }

小贴士

你必须将所有内容包裹在 platform.ready() 中,因为有时 Cordova 插件加载速度比应用本身慢,如果用户点击按钮太快触发 InAppBrowser,则会导致错误。

要触发 InAppBrowser,你只需调用 InAppBrowser.open 并传递以下三个参数:

  1. 要加载的 URL。

  2. 打开 URL 的目标。只有四个选项——_self, _blank_system_self 将覆盖你的当前 Ionic 应用;因此,你永远不需要使用此选项。_blank 通常是你想要的,因为它为你提供了返回应用的方式。最后,_system 在应用外部打开一个单独的浏览器。

  3. 选项是最后一个参数,你必须传递一个字符串。你可以访问官方文档获取更多信息 (github.com/apache/cordova-plugin-inappbrowser#cordovainappbrowseropen)。一般来说,你只需要设置 location=true,这样用户就可以看到当前的 URL。

    小贴士

    Android 和 iOS 有不同的选项;因此,你应该检查 GitHub 页面以比较不同的平台。

InAppBrowser 还有一个 exit 事件,你可以通过 ref.on() 来监听。当你想要弹出感谢对话框或记录事件(通过 REST API 到你的服务器)时,这很有用。

还有更多…

使用 Google Maps 插件和地理编码支持创建出租车应用

今天,许多移动应用利用不同的地图功能,例如显示当前位置、创建路线和提供建议性的商业搜索。本节将展示如何使用 Ionic Native 的 Google Maps 插件提供地图支持。

你将创建一个可以执行以下操作的出租车应用:

  • 全屏显示 Google Maps

  • 在地图上添加按钮覆盖

  • 检测当前设备位置

  • 添加任何文本的标记

这是出租车应用的截图:

使用 Google Maps 插件和地理编码支持创建出租车应用

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

使用 Google Maps 插件和地理编码支持创建出租车应用

可以使用 HTML5 和 JavaScript 版本的地理位置和地图,而不是 Cordova 插件。然而,你将看到性能上的负面影响。很明显,如果你使用 SDK,地图渲染和优化通常会更快。此外,HTML5 地理位置有时会有一些奇怪的错误,需要用户接受两次权限——一次是应用,一次是内部浏览器对象。

准备就绪

Google Maps 插件需要为你的项目提供一个 Google Maps API 密钥。你需要一个 Google 账户并登录才能开始。

  1. 导航到 Google APIs 控制台 code.google.com/apis/console/

  2. 如果你还没有创建项目,请创建一个。只需填写所需的字段:准备就绪

  3. 你需要启用 Google Maps SDK for iOSGoogle Maps Android API 或两者都启用。这取决于你计划支持多少平台。在这个例子中,让我们选择 Google Maps SDK for iOS准备就绪

  4. 点击 启用 API 按钮:准备就绪

  5. 前往 凭证 创建你自己的密钥:准备就绪

  6. 点击 创建凭证 | API 密钥 选项:准备就绪

  7. 选择限制选项。在以下示例中,你将选择 iOS apps 单选按钮:准备就绪

  8. 填写你应用的 Bundle ID。你可能还不知道它是什么,因为 Ionic 会创建一个随机 ID。所以只需填写 com.ionicframework.starter 并稍后更改它。准备就绪

  9. 点击 保存 按钮。

  10. 现在你应该会看到 iOS 应用程序部分的密钥如下:准备就绪

  11. 复制 API 密钥,以便你可以使用它来添加 Cordova Google Maps 插件。

如何操作...

让我们从零开始创建一个 Ionic 项目并添加 Google Maps 功能,如下所示:

  1. 创建一个空白 Ionic 项目,如图所示,并进入该文件夹:

    $ ionic start TaxiApp blank --v2
    $ cd TaxiApp
    
    
  2. 使用以下命令行将 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 插件仅与此版本兼容。否则,你的构建将失败。如果可能的话,你应该尝试最新的版本。

  3. 使用你复制的密钥安装 Google Maps 插件,如下所示,将 `YOUR_IOS_API_KEY_IS_HERE 替换为你的 iOS API 密钥:

    $ 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 不会起作用。

  4. 打开./src/pages/home/home.html以修改您的模板,如下所示:

    <ion-content [ngClass]="{'no-scroll': mapRendered}">
      <div id="map">
        <button ion-button color="dark" (click)="getMyLocation()">PICKME UP</button>
      </div>
    </ion-content>
    

    这里的主要元素是你的具有map ID 的div,因为那里是你必须注入 Google Maps 对象的地方。

  5. 在同一文件夹中编辑你的./src/pages/home/home.ts

    import { Component } from '@angular/core';
    import { NavController, Platform } from 'ionic-angular';
    import { GoogleMap, GoogleMapsEvent, GoogleMapsLatLng, GoogleMapsMarkerOptions, GoogleMapsMarker, CameraPosition } from 'ionic-native';
    
    @Component({
      selector: 'home-page',
      templateUrl: 'home.html'
    })
    export class HomePage {
      public map: GoogleMap;
      public mapRendered: Boolean = false; 
    
      constructor(public navCtrl: NavController, publicplatform: Platform) {
        this.platform.ready().then(() => {
          this.showMap();
        });
      }
    
      showMap(){
        let location = new GoogleMapsLatLng(47.6062, -122.3321);
        this.map = new GoogleMap('map', {
          'camera': {
            'latLng': location,
            'tilt': 30,
            'zoom': 15,
            'bearing': 50
          }
        });
    
        this.map.on(GoogleMapsEvent.MAP_READY).subscribe(()=> {
          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: CameraPosition = {
            target: location.latLng,
            zoom: 15
          };
          this.map.moveCamera(position);    
    
          let markerOptions: GoogleMapsMarkerOptions = {
            'position': location.latLng,
            'title': msg
          };
          this.map.addMarker(markerOptions).then((marker: GoogleMapsMarker) => {
            marker.showInfoWindow();
          });
    
        });
    
      }
    }
    
  6. 最后,对样式表进行一些小的调整,以便地图可以全屏显示。编辑./src/pages/home/home.scss,如图所示:

    ion-app._gmaps_cdv_ .nav-decor{
      background-color: transparent !important;
    }
    
    home-page {
      text-align: center;
      #map {
        height: 100%;
        z-index: 9999;
      }
    }
    
    .no-scroll {
      .scroll-content {
        overflow-y: hidden;
      }
    }
    
  7. 使用以下命令行在终端中运行应用程序:

    $ ionic run ios --device
    $ ionic run android
    
    

根据平台,你可以使用上述任一命令行。

它是如何工作的…

该应用程序的核心主要在于 JavaScript 代码——home.ts。为了使用插件对象,你应在顶部声明它,如下所示:

import { GoogleMap, GoogleMapsEvent, GoogleMapsLatLng,GoogleMapsMarkerOptions, GoogleMapsMarker, CameraPosition } from 'ionic-native';

虽然看起来有很多移动部件,但基本流程非常简单,如下所示:

  1. 无论何时 Ionic 和 Cordova 都准备好了,通过在HomePage构造函数中调用showMap()来初始化地图,触发platform.ready().then

  2. 当用户点击按钮时,应用程序将调用getMyLocation以获取位置数据。

  3. 这些数据将用于创建标记并将地图的相机移动到该位置的中心。

需要知道的是,plugin.google.maps.Map.getMap确实需要一些时间来处理,并且一旦成功创建地图,它将触发一个ready事件。这就是为什么你需要添加一个监听器来监听plugin.google.maps.event.MAP_READY。这个示例在地图准备好后并没有立即做任何事情,但稍后,你可以添加更多的处理函数,例如自动跳转到当前位置或在上面的地图上添加更多标记。

当用户点击PICK ME UP按钮时,将触发getMyLocation()方法。返回的位置对象将包含纬度(location.latLng.lat)和经度(location.latLng.lng)。要移动相机到任何地方,只需通过传递位置坐标(location.latLng)调用map.moveCamera。要添加标记,请使用位置和标题作为 HTML 调用map.addMarker

还有更多...

Cordova Google Maps 插件具有许多其他功能,如下所示:

  • 显示信息窗口

  • 添加多行标记

  • 修改图标

  • 文本样式

  • Base64 编码的图标

  • 点击标记

  • 点击信息窗口

  • 创建可拖动的标记

  • 拖动事件

  • 创建平面标记

由于你无法在原生的 Google Maps 上弹出div,因此标记功能非常实用。一些额外的场景如下所示:

  • 触摸标记并跳转到页面:你只需要监听plugin.google.maps.event.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 2 CLI 提供了无缝集成,以改善你的工作流程,确保你可以在构建应用之前提前捕捉到每个平台的所有问题。你可以在同一个浏览器窗口中快速查看应用在不同平台上的表现。这个功能非常强大,因为现在可以针对每个屏幕进行并排比较,并执行特定的交互。如果你想调试 JavaScript 代码,你可以使用在浏览器中已经使用的相同网页开发者工具。这种能力将为你节省大量时间,而不是等待将应用推送到物理设备,如果你的应用变得更大,这可能需要几分钟。

在这个例子中,你将学习如何使用 Sass 变量快速修改主题。然后,你将运行应用并检查不同平台以保持 UI 一致性。

准备工作

没有必要在物理设备上测试主题,因为 Ionic 可以在浏览器中渲染 iOS、Android 和 Windows Phone。

如何操作…

这里是操作说明:

  1. 使用如图所示的 tutorial 模板创建一个新的应用,并转到以下文件夹:

    $ ionic start ThemeApp tutorial --v2
    $ cd ThemeApp
    
    

    小贴士

    在 Ionic 1 中,你需要设置 Sass 依赖项,因为 Ionic 为此使用了许多外部库。然而,Ionic 2 没有这样的要求,因为所有依赖项都是在创建项目时添加的。

  2. 打开 /app/theme/app.variable.scss 文件,并用以下命令替换 $colors 变量:

    $colors: (
     primary:    #2C3E50, // #387ef5,
     clear:      white,
     secondary:  #446CB3, // #32db64,
     danger:     #96281B, // #f53d3d,
     light:      #BDC3C7, // #f4f4f4,
     dark:       #6C7A89, // #222,
     favorite:   #16A085 // #69BB7B
    );
    
    

    小贴士

    默认的颜色代码可以像前面代码所示那样进行注释。

  3. 打开 app.html 文件,并将 clear 属性添加到以下代码块中:

      <ion-toolbar clear>
        <ion-title>Pages</ion-title>
      </ion-toolbar>
    
  4. 打开 ./src/pages/hello-ionic/hello-ionic.html 文件,并用给定的代码替换其内容:

    <ion-header>
      <ion-navbar 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>
    
  5. 在浏览器中运行测试应用,你应该能看到如下屏幕:

    $ ionic serve -l
    
    

    小贴士

    -l(lima)命令意味着为所有三个平台渲染应用。

    如何操作…

工作原理…

Ionic 2 使得开发不同平台的主题变得非常容易。你的典型流程是首先修改 app.variables.scss 中的主题变量。你不应该直接修改任何 .css 文件。此外,Ionic 2 项目现在确保你无法意外编辑错误的主题核心文件,因为这些核心文件不再位于应用文件夹位置。

要更新默认颜色,你只需修改 app.variables.scss 中的颜色代码。你甚至可以添加更多颜色名称,例如 clear: white,Ionic 2 将自动处理其余部分。这意味着 clear 关键字可以作为属性应用于任何接受颜色名称的 Ionic 元素。以下是一些示例:

<ion-navbar primary>
<button ion-button color="secondary" menuToggle>
<ion-toolbar 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 Phone 不同的应用主题的工作流程示例。在传统开发(使用本地语言或其他混合应用解决方案)中,你必须为每个平台维护单独的仓库以自定义主题。从长远来看,这可能会非常低效。

Ionic 2 提供了许多内置功能来支持基于检测到的平台进行主题更改。通过为每个平台分离 Sass 变量,这使得操作非常方便。这将消除许多不必要的自定义。作为开发者,你更愿意专注于应用体验,而不是花时间管理平台。

本节中的示例涵盖了使用 Sass 和 JavaScript 的两种可能的定制方式。以下截图显示了一个 iOS、Android 和 Windows 应用,具有不同的标题栏颜色和文本:

基于平台自定义主题

准备工作

没有必要在物理设备上测试主题,因为 Ionic 可以在浏览器中渲染所有三个平台。

如何做到这一点...

这里是说明:

  1. 使用blank模板创建一个新应用,并进入项目文件夹:

    $ ionic start PlatformStylesApp blank --v2
    $ cd PlatformStylesApp
    
    
  2. 打开./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 引导的使用,现在将进行讨论。

  3. 打开./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);
      }
    }
    
  4. 打开./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 inMarketplace? 
      </p>
    </ion-content>
    

    这是应用唯一的模板,但它的 UI 将根据检测到的平台而有所不同。

  5. ./src/pages/home/home.scss替换为以下样式表:

    .large-icon {
      font-size: 60px;
    }
    
    .center {
      text-align: center;
    }
    
    .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); 
    }
    
    .large-center-title {
      text-align: center;
      .toolbar-title {
        font-size: 25px;
      }
    }
    

    没有必要更改全局变量。因此,您只需修改一个页面的样式。目的是展示为每个平台定制的能力。

  6. 使用以下命令在浏览器中测试运行应用:

    $ ionic serve -l
    
    

它是如何工作的...

Ionic 自动创建了平台特定的父类,并将它们放在<body>标签中。iOS 应用将包含.ios类。安卓应用将包含.md类。因此,对于样式表定制,您可以利用这些现有的类来改变您应用的外观和感觉。

Ionic 2 文档列出了所有平台模式和配置属性ionicframework.com/docs/v2/theming/platform-specific-styles/

平台 模式 详情
iPhone/iPad/iPad ios iOS 样式用于所有苹果产品
安卓 md md代表Material Design,这是安卓设备的默认设计
Windows Phone wp 在 Cordova 或 Electron 中的任何 Windows 设备上查看都使用 Windows 样式
核心 md 对于所有其他平台,Material Design 是默认的

首先,让我们看看 Ionic Angular 中的 Ionic Bootstrap 类。您在app.ts文件中声明了它:

IonicModule.forRoot(MyApp, {
    backButtonText: 'Go Back',
    iconMode: 'md',
    modalEnter: 'modal-slide-in',
    modalLeave: 'modal-slide-out',
    tabbarPlacement: 'bottom',
    pageTransition: 'ios',
})

这个声明基本上指示应用使用MyApp对象进行引导。第三个参数是您可以注入自定义配置属性的地方。所有Config属性列表可以在ionicframework.com/docs/v2/api/config/Config/找到。

这里要指出的一点是iconMode。在 Ionic 2 中,每个平台的图标都大不相同。整个 Ionicons 集现在根据平台名称分开。根据 Ionic 2 的文档页面,有三个平台,在ionicframework.com/docs/v2/ionicons/

您甚至可以使用以下搜索 Ionicons按钮搜索图标名称:

如何工作…

注意,你不需要担心为哪个平台选择哪个图标。即使在这个例子中,代码强制你为三个平台都选择 iOS 图标,你也可以只使用图标名称,让 Ionic 2 决定使用哪个图标:

如何工作…

例如,当你声明图标名称为"add"时,如果用户使用 Android,Ionic 2 将使用"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 2 提供的。如果你在运行应用时打开浏览器控制台,你可以检查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>

最后,你可以直接使用平台类来更改主题。考虑以下示例:

.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); 
}

这意味着,每当它是 Material Design 模式(.md类)时,你将用自己的样式覆盖这些类。前面的例子显示了一个有趣的 CSS 渐变,它在移动设备上工作得非常好。

还有更多…

此外,设备信息可以通过Platform类获取。你甚至可以在ionicframework.com/docs/v2/api/platform/Platform/检测 iPad 设备。

第九章。在不同平台上发布应用程序

在本章中,我们将介绍以下与发布和确保应用程序长期可用性相关的任务:

  • 为确保应用程序的长期可用性添加版本控制

  • 构建并发布 iOS 应用程序

  • 构建并发布 Android 应用程序

简介

在过去,构建和成功发布应用程序非常繁琐。然而,如今互联网上有大量的文档和非官方指南,几乎可以解决你可能遇到的所有问题。此外,Ionic 还自带 CLI 来协助这个过程。本章将指导你完成应用程序构建和发布的步骤。你将学习如何完成以下事情:

  • 为确保应用程序的长期可用性添加版本控制

  • 将你的应用程序发布到 App Store 或 Google Play

本章的目的是提供关于要寻找的内容以及一些需要注意的问题的想法。苹果和谷歌不断更新他们的平台和流程;因此,随着时间的推移,步骤可能不会完全相同。

为确保应用程序的长期可用性添加版本控制

通常情况下,你可能不会考虑跟踪特定用户的版本。然而,随着应用程序的用户数量和发布次数的增加,你很快就会面临更新问题和兼容性问题。例如,一个用户可能运行你应用程序的旧版本,但你现在所有的后端 API 都期望从较新版本的应用程序中获取新参数。因此,你可能需要考虑一种策略来本地检测应用程序版本,以便通知用户更新需求。如果你的后端针对特定应用程序版本有不同的处理方式,这也有帮助。

你将要构建的应用程序非常简单。它将检测当前版本并将信息存储在服务中。这是应用程序的截图:

为确保应用程序的长期可用性添加版本控制

准备工作

此应用程序示例必须在物理设备或模拟器上运行。

如何操作...

以下是一些注意事项:

  1. 使用blank模板创建一个新的MyAppVersion应用程序,如下所示,然后转到MyAppVersion文件夹:

    $ ionic start MyAppVersion blank --v2
    $ cd MyAppVersion 
    
    
  2. 安装app-version插件:

    $ ionic plugin add cordova-plugin-app-version
    
    
  3. 通过更改版本号编辑./config.xml,如下所示:

    <widget id="com.ionicframework.myappversion637242" version="0.0.123"  >
    
    

注意,你的widget id可能与这里提到的不同。你只需要更改版本号。在这种情况下,是0.0.123版本。

  1. 在应用程序文件夹内创建名为services的文件夹,如下所示:

    $ mkdir ./src/services
    
    
  2. services文件夹中创建myenv.ts,代码如下:

    import {Injectable} from '@angular/core';
    import {AppVersion} from 'ionic-native';
    
    @Injectable()
    export class MyEnv {
      public appVersion: any;
    
      constructor() {
        this.appVersion = AppVersion;
      }
    
      getAppVersion() {
        return this.appVersion.getVersionCode();
      }
    }
    

    这是此应用程序的唯一服务。在现实世界的项目中,你需要多个服务,因为其中一些将需要直接与你的后端进行通信。

  3. 打开并编辑你的.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';
    import { MyEnv } from '../services/myenv';
    
    @NgModule({
      declarations: [
        MyApp,
        HomePage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage
      ],
      providers: [MyEnv]
    })
    export class AppModule {}
    

    此文件的主要修改是在整个应用程序中注入MyEnv提供者。

  4. 打开并替换.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>
    
  5. 打开并替换./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) {
        this.myEnv = myEnv;
      }
    
      getVersion() {
        console.log(this.myEnv.getAppVersion());
        this.myEnv.getAppVersion().then((data) => this.ver = data);
      }
    }
    
  6. 在同一文件夹中打开并编辑home.scss

    .home {
      p.large {
        font-size: 16px;
      }
    }
    
    ion-content {
      &.center {
        text-align: center;
      }
    }
    
  7. 前往您的终端并运行应用。如果您想在您的物理设备上运行应用,请输入以下命令:

    $ ionic run ios
    
    

    对于 Android,请输入以下命令:

    $ ionic 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 Developer Center、iTunes Connect 和本地 Xcode 项目中正确配置所有内容。

准备工作

您必须注册 Apple Developer Program 才能访问developer.apple.com/macos/touch-bar/itunesconnect.apple.com,因为那些网站将需要一个已批准的账户。

此外,以下是对这些组件特定版本的说明:

  • Mac OS X El Capitan 10.11.2

  • Xcode 7.2

  • Ionic CLI 2.0

  • Cordova 5.4.1

如何操作...

这里是说明:

  1. 确保您在应用文件夹中,并为 iOS 平台构建:

    $ ionic build ios
    
    

    前往/platforms/ios文件夹,在 Xcode 中打开.xcodeproj文件。观察以下截图:

    如何操作…

  2. 通过常规选项卡,如图所示的以下截图,确保你拥有所有正确的信息,特别是包标识符版本。如有需要,进行更改并保存:如何操作…

  3. 访问 Apple 开发者网站并点击证书、标识符和配置文件,如图所示:如何操作…

  4. 选择你正在针对的正确设备平台。在这种情况下,它将是iOS、tvOS、watchOS,如图所示:如何操作…

  5. 对于 iOS 应用,你需要证书、App ID、测试设备和配置文件。要从证书开始,导航到证书 | 所有,如下所示:如何操作…

  6. 点击如下所示的加号(+)按钮:如何操作…

  7. 你只需按照网站上的步骤填写必要的信息,如下面的截图所示:如何操作…

  8. 完成表格后,你可以保存 CSR 文件并将其导入到 Mac 的钥匙串访问中。

  9. 按照如下所示导航到标识符 | App ID以创建一个 App ID:如何操作…

  10. 点击屏幕右上角的加号(+)按钮,如下所示:如何操作…

  11. 按照如下所示填写表格以注册你的App ID如何操作…

  12. 这里需要正确完成的重要部分是包标识符,如下面的截图所示,因为它必须与 Xcode 中的Bundle Identifier匹配:如何操作…

  13. 如果你的应用需要推送通知或其他应用服务,你需要在页面上检查这些服务:如何操作…

  14. 如果你需要将应用推送到特定设备,你必须注册该设备。导航到设备 | 所有,如图所示:如何操作…

  15. 点击显示的加号(+)按钮,如图所示:如何操作…

  16. 提供设备的UDID,如下所示,并保存以注册设备:如何操作…

  17. 最后,如果你还没有配置文件,你需要一个。通常,Xcode 会自动创建一个。然而,你可以通过导航到配置文件 | 所有来创建自己的,如下所示:如何操作…

  18. 点击如下所示的加号(+)按钮:如何操作…

  19. 选择App Store作为你的配置文件,如图所示:如何操作…

  20. 在下拉菜单中选择正确的App ID并保存,以完成你的配置文件创建,如下所示:如何操作…

  21. 访问iTunes Connect并点击我的应用按钮,如图所示:如何操作…

  22. 选择加号(+)图标以选择新建应用,如图所示:如何操作…

  23. 填写表格并确保您选择了应用的正确捆绑标识符如何操作…

    有几个额外的步骤来提供有关应用的信息,例如截图、图标和地址。如果您只想测试应用,您可以最初提供一些占位符信息,稍后再回来编辑。

    准备您的开发者账号和 iTunes Connect 账号就到这里。

  24. 现在,打开 Xcode 并选择iOS 设备作为存档目标。否则,存档功能将无法启用。您需要在提交到应用商店之前存档您的应用:如何操作…

  25. 在顶部菜单中导航到产品 | 存档,如图所示:如何操作…

  26. 存档过程完成后,选择提交到应用商店以完成发布过程。

  27. 要发布,选择提交进行 Beta 应用审查。您可能想浏览其他标签页,如定价应用内购买,以配置您自己的要求。

工作原理…

显然,本节没有涵盖发布过程中的所有细节。一般来说,您只需确保在提交到应用商店之前,在物理设备上(通过 USB 或 TestFlight)彻底测试您的应用即可。

如果由于某些原因存档功能无法构建,您可以手动前往您的本地 Xcode 文件夹删除该特定临时存档应用以清除缓存,如图所示:

~/Library/Developer/Xcode/Archives

还有更多…

TestFlight 是一个独立的话题。TestFlight 的好处是您不需要您的应用获得苹果的批准就可以在物理设备上进行测试和开发安装。您可以在 developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/BetaTestingTheApp.html 上找到更多关于 TestFlight 的信息。

为 Android 构建和发布应用

构建 Android 应用比 iOS 更直接,因为您只需与命令行接口来构建 .apk 文件并将其上传到 Google Play 开发者控制台。

Ionic 框架文档也为此提供了很好的说明页面,链接为 ionicframework.com/docs/guide/publishing.html

准备工作

要求是您的 Google 开发者账户已准备好并登录到 play.google.com/apps/publish

您的本地环境也应具备正确的 SDK 以及针对该特定版本的 keytooljarsignerzipalign 命令行。

如何操作…

这里是说明:

  1. 进入你的应用文件夹,使用以下命令为 Android 构建:

    $ ionic package build --release android
    
    
  2. 你将在/platforms/android/build/outputs/apk文件夹中看到android-release-unsigned.apk。在终端中转到该文件夹:如何操作…

  3. 如果这是你第一次创建此应用,你必须有一个keystore文件。此文件用于在发布时识别你的应用。如果你丢失了它,你以后无法更新你的应用。要创建keystore,请输入以下命令行,并确保它与 SDK 的keytool版本相同:

    $ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
    
    
  4. 一旦填写了命令行中的信息,请将此文件复制到某个安全的地方,因为以后你需要它。

  5. 下一步是使用该文件来签名你的应用,以便它将创建一个新的.apk文件,Google Play 允许用户安装:

    $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore HelloWorld-release-unsigned.apk alias_name
    
    
  6. 在上传之前准备最终的.apk,你必须使用zipalign打包,如下所示:

    $ zipalign -v 4 HelloWorld-release-unsigned.apk HelloWorld.apk
    
    

你需要确保zipalignPATH中,或者你必须指定绝对路径。应用名称可以是任何你喜欢的,或者你可以使用本章创建的相同名称:

  1. 登录 Google 开发者控制台,点击添加新应用,如图所示:如何操作…

  2. 使用左侧菜单尽可能填写你的应用的内容评级和其他信息:如何操作…

  3. 现在,你已经准备好上传你的.apk文件。首先是要进行 Beta 测试:如何操作…

  4. 一旦完成 Beta 测试,你可以按照开发者控制台的说明将应用推送到生产环境。

注意

如果你在发布应用时遇到任何问题,查看仪表板右上角的“为什么我不能发布?”链接会有帮助。Google 会指导你完成或修复必须完成的特定步骤。

它是如何工作的…

本节不涵盖其他 Android 市场,例如 Amazon 应用商店,因为它们各自有不同的流程。然而,共同的想法是,你需要完全构建.apk的无签名版本,使用现有的或新的keystore文件进行签名,最后zipalign以准备上传。

posted @ 2025-09-29 10:32  绝不原创的飞龙  阅读(8)  评论(0)    收藏  举报