Chinese (Simplified) (中文(简体)) translation by Qiang Ji (you can also view the original English article)

1.服务简介
在此Ionic系列的第一和第二部分,我们设置了本地开发环境和用一些Ionic组件,比如基本的导航和目录组件,开发了几个用来加载一个公园目录的视图。 在此教程中,我们将探讨一下Ionic如何提供一些服务,这些服务允许你程序化管理应用程序和界面。
之前,我们演示了Ionic如何通过组件提供交互功能,这些组件是作为HTML元素被使用的(作为Angular指令被实施的)。 然而,有一些界面元素作为组件随着HTML被实例化并没有意义,比如一个加载器或者操作表叠加。
让我们从服务在你应用程序中扮演的角色开始吧。 我已经确定三个在Ionic中主要的服务类型。
- 组件服务
- 委托服务
- 辅助服务
组件服务
组件服务授权组件的使用,但它们是使用JavaScript管理的,而不是使用HTML去声明它们(就像我们看到的ionNavBar
那样)。 换句话说,你将通过向控制器添加代码来运用这些组件。 我们将在下面的例子中使用其中的两个。
将这些服务看作具有生命周期的组件是有用的。 通常,你希望它们在特定的时间点加载,完成加载后,它们会被删除。 比如,$ionicModal
允许你创建一个模态。 模态有一个生命周期,它们会基于特定原因被打开和关闭。 你可能有一个要求用户登录的模态,或者他们可以关闭模态以跳过它,从而完成生命周期。
委托服务
有些组件有一个可以修改或管理组件的委托服务。 你可能希望在创建组件后可以程序化地操作它,这些服务旨在实现这种操作。 它们被这样命名就是因为它们将行为委托给组件。
ionNavBar
组件有一个叫做$ionicNavBarDelegate
的委托服务。 此服务含有几种方法,其中一个例子是title()
方法,它允许你更新导航栏的标题。 每个委托服务可用功能的范围各不相同,但是它们应该很容易在文档中按名称找到。
辅助服务
最后一类是可提供一些辅助的功能或信息的服务。 它们只有几个而且和之前两种服务类别不太一样。 比如:
-
$ionicPlatform
:帮助你与设备硬件交互 -
$ionicGesture
:允许你处理手势事件 -
$ionicPosition
:告诉你元素在屏幕上的位置
这些辅助服务倾向于帮你开发逻辑或处理交互。 它们自己不会生成或修改组件。
在本片教程里我们也会看一下其它的一些东西:
- CSS组件,仅仅是视觉效果并且它不提供像其它JavaScript组件所提供的功能性逻辑。
- Ionic事件,我们能利用它介入事件,比如,当视图加载时或已完成加载时。
- 更多的导航功能,它们使导航,管理状态和向导航栏添加按钮变的容易。
源文件
在此篇教程中,我们将会扩展在之前教程中已经开发的应用。 提醒一下,该应用旨在向用户提供有关他们当地的公共设施,比如图书馆和公园的信息。 此应用程序已显示在Chicago的公园目录,现在我们将使其能够显示加载指示器,查看个别公园详细信息,打开操作菜单和执行一些基本的分享功能。
你可以在GitHub上查看完成的项目。 最终的应用程序也可预览。
你可以用Git下载或查看源文件。 当你有了源文件,你需要运行npm install
来设置项目。 如果你用Git查看代码,你可接着编写代码如果你运行git checkout -b start
来恢复代码库到最后一部分结束的地方。 一旦你有了源文件,你可以通过运行ionic serve
启动Ionic服务器。
2. 实现一个加载指示器
目前,此应用加载数据且无限滚动组件显示一个小的旋转指示器直到数据加载完成。 可我们实际上想覆盖整个应用程序,这样让应用程序在加载数据时变的很明显。
$ionicLoading
服务对覆盖和阻隔用户与应用程序交互直到数据加载完成是非常有用的。 它也是可配置的。 比如你能声明一个加载图标或显现一些文字,是否你要或不要暗背景,是否它应该在显示一段时间后自动地隐藏。 你能在下面的截图中看到加载指示器在操作。

修改www/views/places.js去使用加载指示器。 首先,我们需要通过添加一个$ionicLoading
函数参数给我们的控制器注入服务。 此服务很简单,它只有两个方法,show()
和hide()
。 就像你在这段代码中看到的那样,我们可通过调用那两个方法显示或隐藏加载器。
.controller('PlacesController', function($http, $scope, $ionicLoading, Geolocation) { var vm = this; var base = 'https://civinfo-apis.herokuapp.com/civic/places?type=park&location=' + Geolocation.geometry.location.lat + ',' + Geolocation.geometry.location.lng; var token = ''; vm.canLoad = true; vm.places = []; $ionicLoading.show(); vm.load = function load() { var url = base; if (token) { url += '&token=' + token; } $http.get(url).then(function handleResponse(response) { vm.places = vm.places.concat(response.data.results); token = response.data.next_page_token; if (!response.data.next_page_token) { vm.canLoad = false; } $scope.$broadcast('scroll.infiniteScrollComplete'); $ionicLoading.hide(); }); }; });
$ionicLoading.show()
方法在控制器被加载后立即被调用,这意味着加载器立刻被显示。 现在我们需要告诉加载器在数据加载后隐藏,你看到此代码就在$broadcast
之后。
你也许注意到$ionicLoad.hide()
方法在每一次数据加载时被调用。 这不会有问题。 因为加载器已经被隐藏,此调用没有任何效果。
我们现在已经实现了一个Ionic服务。 很简单。 是吗? 一些服务有点复杂,我们使用操作表来写另一个例子。 在我们做之前,我们扩展我们的应用程序使其拥有两个视图,针对查看注解目录和查看单个注解。
3. 添加注解视图
我们下一步会写一个新的视图,此视图将显示一个具体公园的更多细节。 信息随公园而异,但我们将专注于获取图片,网站,电话和地址信息。 添加此视图的结果如下所示。

要创建新视图,请创建www/views/place/place.js,并包含你在下面看到的内容。 这是place
视图控制器和状态的定义。
angular.module('App') .config(function($stateProvider) { $stateProvider.state('place', { url: '/places/:place_id', controller: 'PlaceController as vm', templateUrl: 'views/place/place.html', resolve: { Place: function($http, $stateParams) { var url = 'https://civinfo-apis.herokuapp.com/civic/place?place_id=' + $stateParams.place_id; return $http.get(url); } } }); }) .controller('PlaceController', function($scope, Place) { var vm = this; vm.place = Place.data.result; });
如果你看一下config()
方法,你会看到我们正在声明一个新的状态。 这是ui-router在操作中,那么你应该查阅ui-router文档,了解有关声明状态的所有详细信息。
此对象定义显示我们正在使用/places/:place_id
的网址。 当你看到前面带冒号的URL部分,比如:place_id
时,ui-router会将此部分路径标记为状态参数。 此状态能够使用$stateParams
对象获取数值并提供给你。 比如,/places/12345
会导致$stateParams.place_id = '12345'
。
你已经看过定义的其他部分,除了resolve
属性。 这是一个允许您请求在创建状态之前调用各种函数的功能。 它接受键和函数值的对象,因此在这里我们将Place
作为键,并将函数的结果分配给它。
在此函数中,它可以接受参数注入,这类似于你能用控制器所做的事情。 在这里,$http
和$stateParams
服务被注入。 然后,该函数使用通过URL传递的place_id
的值,构建并返回HTTP请求。 这实际上是在places视图中完成的,不是控制器。
此解析功能足够聪明,可以确定如果你返回一个promise,它将等待此promise在创建状态之前被解析。 换种说法,$http.get()
返回一个加载数据的promise,并且ui-router会一直等待直到数据在创建状态和传递Place
到控制器之前变的可用。 解析功能对在应用中提前加载数据非常有用,这是一个如何使用它的很基本的示例。
现在我们已经定义了状态,声明了控制器并将结果数据从Place
(这是在状态中被解析的)分配给vm.place
。 我们还需要为此状态创建模板,因此创建www/views/place/place.html并向其中添加以下内容。
<ion-view view-title="{{vm.place.name}}"> <ion-content> <div class="card" ng-if="vm.place"> <div class="item item-text-wrap item-icon-left"> <i class="icon ion-map"></i> {{vm.place.formatted_address}}</p> </div> <div class="item item-image" ng-if="vm.place.photos[0].photo_reference"> <img ng-src="{{'https://civinfo-apis.herokuapp.com/civic/photo?photo_id=' + vm.place.photos[0].photo_reference}}"> </div> <a ng-if="vm.place.website" class="item item-icon-left" ng-href="{{vm.place.website}}" target="_system"> <i class="icon ion-link"></i> {{vm.place.website}} </a> <a ng-if="vm.place.formatted_phone_number" class="item item-icon-left" ng-href="tel://{{vm.place.formatted_phone_number}}"> <i class="icon ion-ios-telephone"></i> {{vm.place.formatted_phone_number}} </a> </div> </ion-content> </ion-view>
此模板通过使用ionView
包装内容开始,因此Ionic导航系统可以正确地跟踪它。 它还根据地点的名称分配标题。 ionContent
封装器包含主要内容,你会注意到模板使用CSS类而不是元素来创建卡组件。
在上一部分中,我们讨论了仅仅是CSS类的一些组件。 卡组件就是这样一个例子。 在概念上,它就像一个列表。 内部内容如同列表一样垂直堆叠,但是样式更像是一张卡片。 这将利用卡片样式,其中包括文档中所述的图像支持,图标和其它整洁布局的功能。
有几个ngIf
指令的使用,因为不能保证返回的数据会有一个电话号码或网站。 ngIf
指令确保不显示空值。 它还使用ngHref
或ngSrc
正确构建链接。
你还会注意到tel://
协议的使用,当在电话上使用时,应提示用户在选择号码时呼叫该号码。 这是一个方便的功能,易于使用,并在一个物理设备上能很好地整合。 计算机上的某些程序(如Skype)也可能会尝试处理为你拨打电话,这具体取决于你的设置。
这应该给我们一个可工作的视图,但我们如何导航到它? 我们需要做一些小的修改,以使导航从places视图开始。
4.在视图之间导航
ui-router提供了一个ui-sref
指令,用于将项目与另一个状态链接。 在这种情况下,我们希望places视图列表中的每个条目都链接到相应的place视图。
打开www/views/places/places.html并添加链接到每个地点的指令。 使用新属性更新ionItem
。
<ion-item ng-repeat="place in vm.places" class="item-avatar" ui-sref="place({place_id: place.place_id})">
ui-sref
指令有一种格式,你可以通过其名称而不是像使用href
那样通过某个URL链接到另一个状态。 这很方便,因为URL可能会改变。 它还可以接受用于构建URL的参数,在我们的示例中,我们要传递place.place_id
属性。 ui-sref
将属性作为对象,所以state-name({param:value})
是语法。
现在预览此应用并选择公园,它会导航到新的place
视图,你可以查看地址栏以查看网址是否添加了place_id
值。 但是,我们现在有一个问题。 我们如何回到目录中?
我们使用ionNavBackButton
功能为我们提供一个自动返回按钮。 打开www/index.html并在ionNavBar
中添加以下代码段。 这将添加一个返回按钮,只有当有一个地方要返回时才会显示。
<ion-nav-bar class="bar-balanced"> <ion-nav-back-button class="button-clear"> <i class="ion-arrow-left-c"></i> Back </ion-nav-back-button> </ion-nav-bar>
Ionic的导航聪明到足以在你使用应用程序时跟踪导航的历史。 如果的确有一个可返回的之前视图,它将显示返回按钮。 否则,它将被简单地隐藏。
我们还想声明,places视图不应该显示后退按钮,我们可以通过在www/views/places/places.html中添加hideBackButton
指令来实现。
<ion-view view-title="Local Parks" hide-back-button="true">
当你在浏览器中开发和预览时,有时历史记录会重置。 比如当你位于place视图并且在编辑器中保存更改时,浏览器会自动重新加载并重置历史记录。 在这种情况下,后退按钮不会按预期显示。 你可以通过返回到目录并刷新以直接设置历史记录来解决此问题。
我们取得了不错的进展,但现在,当你点击目录中的条目时,它会等待直到API调用返回数据后才转换到新的视图。 它可能看起来很快,但有时,如果API很慢,它就很慢。 它可能会导致有人认为应用程序被卡住,变慢,或者没有注册它们的点击事件,因为它没有立即开始对点击做出反应。 我们通过一些生命周期事件来解决这个问题,它们能帮助我们在这段时间内设置和显示一个加载指示器。
5.在转换期间添加加载指示器
为了提供更好的用户体验,我们将在为place视图加载数据时使用$ionicLoading
服务来覆盖应用程序。 为了知道何时显示和隐藏加载器,我们使用生命周期事件。
这些事件基于导航事件触发,例如在进入视图之前/之后或在离开视图之前/之后。 你可以在这些时间点执行任何可能需要的操作,例如重置某些数据或使用它提交使用信息。
为了演示它,让我们在places视图中添加一个事件侦听器,在开始导航到place视图时处理触发加载指示器事件。 打开www/views/places/places.js并将以下内容添加到控制器。 你还需要确保$scope
在控制器函数参数中被声明,因此它是可用的。
$scope.$on('$ionicView.beforeLeave', function() { $ionicLoading.show(); });
这是一个范围事件侦听器,它侦听$ionicView.beforeLeave
事件(请参阅Angular范围事件)。 Ionic将此事件广播到你的控制器,并调用此处被声明的匿名函数。 此函数简单地调用$ionicLoading.show()
方法以启用加载指示器。
这会触发加载指示器在用户点击条目时立即显示。 现在我们向place视图添加类似的代码,以便在视图完成加载时隐藏加载指示器程序。 打开www/views/place/place.js并将以下内容添加到控制器。 你需要将$ionicLoading
和$scope
添加到控制器函数作为参数,因为它们当前未被注入。
$scope.$on('$ionicView.afterEnter', function() { $ionicLoading.hide(); });
在完成进入视图时,它侦听不同的范围事件,并调用隐藏加载器的函数。 加载指示器会在用户点击要查看的地方直到视图完全被加载之间的时间内被显示。 你可以尝试使用其它事件,看看它们什么时候会被触发。
我们在本教程中做的最后一件事是设置一个操作表共享按钮,它允许你发布到Twitter,Facebook或电子邮件和分享公园的信息。
6.使用操作表服务的分享按钮
操作表对于提供一系列额外选项非常有用。 使用操作表的目的通常是在你想要显示一个分组操作的情况下,(在我们的示例中)提供共享公园信息方式的一系列选项。 我们将创建的操作表看起来像这样。

操作表服务比加载服务复杂一些,因为它处理配置和用户输入。 打开www/views/place/place.js并将这个新方法添加到你的控制器。 你还需要确保$ionicActionSheet
会被注入到你的控制器。
vm.openSheet = function() { var sheet = $ionicActionSheet.show({ titleText: 'Share this place', buttons: [ { text: 'Share via Twitter' }, { text: 'Share via Facebook' }, { text: 'Share via Email'} ], cancelText: 'Cancel', buttonClicked: function(index) { if (index === 0) { window.open('https://twitter.com/intent/tweet?text=' + encodeURIComponent('I found this great place! ' + vm.place.url)); } else if (index === 1) { window.open('https://www.facebook.com/sharer/sharer.php?u=' + vm.place.url); } else if (index === 2) { window.open('mailto:?subject=' + encodeURIComponent('I found this great place!') + '&body=' + vm.place.url); } sheet(); } }); };
openSheet()
方法负责创建操作表。 它通过调用$ionicActionSheet.show()
来实现操作表创建,它返回一个存储在sheet
上的函数。 这允许你在稍后完成工作后通过调用sheet()
关闭它。 show()
方法接受一个具有多个属性的对象,我们将对其进行细分。 有几个遵循此模式的Ionic服务的示例,如modals和popovers,所以你总是可以关闭它们。
该表使用titleText
属性管理标题,通常用于告知用户如何使用按钮。 cancelText
属性接受用于启用取消按钮的字符串。 如果你不声明此属性,它将不会选择取消按钮。 你也可以通过点击按钮之外的背景幕来取消。
要声明这些按钮,你可以使用buttons
属性,这是一个具有text
属性的对象数组。 它们按照声明的顺序显示,因此它们以此进行排序。
buttonClicked
属性接受一个函数,它传递所选按钮的索引(因为它在buttons
中被声明)。 因此,你可以根据传递的索引确定要做什么。 在此函数中,检查索引,并打开Facebook,Twitter或使用mailto:
去触发电子邮件客户端。
它可以在Facebook,Twitter或电子邮件应用程序中打开这些链接,这取决于用户设置和设备,但它将至少打开应用程序外部的链接(在外部浏览器中)。 最后一个是调用sheet()
方法,它关闭操作表。
操作表现在可以操作了,但我们仍然需要添加一个按钮来触发它。 为此,我们在调用vm.openSheet()
的place视图中添加导航栏按钮。 打开www/views/place/place.html,并在ionView
和ionContent
之间添加ionNavButtons
代码片段。
<ion-view view-title="{{vm.place.name}}"> <ion-nav-buttons side="right"> <button class="button button-clear" ng-click="vm.openSheet()"> <i class="icon ion-ios-upload-outline"></i> </button> </ion-nav-buttons> <ion-content>
这里是另一个有用的Ionic导航功能,它允许你对特定的视图使用ionNavButtons
添加导航栏按钮。 内部的任何按钮都添加到导航栏,你可以对它们出现在哪一侧进行配置。
在这个时间点上,一切都工作了。 用户可以打开操作表与朋友分享公园的信息。
结论
在本教程中,我们介绍了Ionic服务及其使用方法。 一路上,我们发现了Ionic许多其它的功能:
- Ionic服务在控制器中被调用并且它通常具有独立于当前视图的生命周期。
-
$ionicLoading
服务非常有用,它在你应用程序加载数据或必须阻止用户界面时显示和隐藏加载指示器。 -
$ionicActionSheet
服务向用户提供覆盖应用程序的按钮列表,以便轻松访问重要的操作。 - Ionic导航功能还包括
ionNavBackButton
,在可以返回时自动显示后退按钮。ionNavButtons
允许你将导航栏按钮添加到特定视图。 - Ionic有CSS组件,像卡组件,它没有特殊的交互功能,只是通过声明CSS类使用。
在下一期中,我们将进一步探讨Ionic的一些导航功能。
创建一个Ionic模版赢$1000
如果你已经熟悉了Ionic框架, 那么你也许想考虑一下进入艺平台最想要的Ionic模版大赛。 怎么做?创建一个独特的Ionic模板,并在2016年4月27日之前提交给 Envato 市场。
最好的5个模板会获得$1000奖励。 有兴趣吗? 在大赛的网站上可以了解到更多的有关比赛要求和指南的详细信息。
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post