6.3.1 分析需要用到的组件
搭建应用
现在,我们已经搭建好了服务器,并且对我们将要制作的东西有了一个初步的想法,那么我们现在就可以开始了。 执行以下步骤就可以轻松的搭建好这个 app 了:
- 搭建侧边菜单模板
- 重构此模板
- 创建认证,本地存储以及 REST API 工厂
- 为每个路由制作控制器,然后将它们整合到对应的工厂
- 创建模板并整合控制器数据
第一步 - 搭建侧边菜单模板
第一件要做的事情就是搭建侧边菜单模板。新建一个文件夹名为
chapter6 。在 chapter6 文件夹内打开终端/命令行,运行如下命令:
ionic start -a "Ionic Book Store" -i app.bookstore.ionic book-store sidemenu
这条命令将会创建好一个侧边菜单模板应用。我们的应用不会自定义主题,所以不会设置 SCSS。
第二步 - 重构模板
到目前为止的所有范例中,我们都只是使用部分组件。但是在本例中,我们将重构整个模板。在继续进行之前,我们先确认一下一切是否正常。使用
cd 命令进入到 book-store 文件夹,然后运行:
ionic serve
这样,app 就运行起来了。之前在
第二章 欢迎使用 Ionic 提到过,打开开发工具,调整其位置到页面的右边部分。效果图如下:
重构菜单
第一个需要重构的是菜单。我们将使用需要用到的条目来替换默认的条目。打开
www/templates/menu.html ,如下替换左边的 ion-side-menu 部分:
<ion-side-menu side="left"> <ion-header-bar class="bar-assertive"> <h1 class="title">Menu</h1> </ion-header-bar> <ion-content> <ion-list> <ion-item menu-close href="#/app/browse"> Browse Books </ion-item> <ion-item menu-close href="#/app/cart"> My Cart </ion-item> <ion-item menu-close href="#/app/purchases"> My Purchases </ion-item> <ion-item menu-close ng-show="isAuthenticated" ngclick="logout()"> Logout </ion-item> <ion-item menu-close ng-hide="isAuthenticated" ngclick="loginFromMenu()"> Login </ion-item> </ion-list> </ion-content> </ion-side-menu>
我们添加了 4 个菜单条目:
- Browse books:浏览所有书籍
- My cart:浏览购物车
- My purchases:浏览我的购物清单
- Login/Logout:根据用户授权状态条件展示的菜单
接下来,我们将更新
menu.html 的页头或者说 ion-side-menu-content 部分:
<ion-side-menu-content> <ion-nav-bar class="bar-assertive"> <ion-nav-back-button> </ion-nav-back-button> <ion-nav-buttons side="left"> <button class="button button-icon button-clear ion-navicon" menu-toggle="left"> </button> </ion-nav-buttons> <ion-nav-buttons side="right"> <!-- <button ng-show="isAuthenticated" class="button button-icon button-clear ion-unlocked" ngclick="logout()"> </button> --> <a class="button button-icon button-clear ionandroid-cart" href="#/app/cart"> </a> </ion-nav-buttons> </ion-nav-bar> <ion-nav-view name="menuContent"></ion-nav-view> </ion-side-menu-content>
我们将页头的主题修改为成 assertive 心情。同时,我们也在右边添加了两个按钮。一个是登出,这个按钮注释掉了,另一个是购物车。 这些代码教会你如何给应用的页头添加图标。
同时也需要注意到登出图标是一个条件显示图标。只有当用户在授权的状态下才显示。由于我不想再页头右边出现两个按钮,所以我把它注释掉了。
同时将
ion-side-menu 上的 enable-menu-with-back-views 属性设置为 true 。没有设置的话,你在子视图里面将看不到侧边菜单图标。当你保存文件,返回浏览器的时候,将会看到如下结果:
重构模组名
接下来,我们将要给模组重命名。默认开始模板的模组名叫做
starter 。我们会将他重命名为 BookStoreApp 。需要更改的位置有:
www/index.html 里面的 ng-app 指令的名字改为 BookStoreApp
- 打开
www/js/app.js ,将 angular 模组改成这样: angular.module('BookStoreApp', ['ionic', 'BookStoreApp.controllers'])
- 打开
www/js/controller.js ,更新 starter.controllers 为 BookStoreApp.controllers 这样就完成了模组的重命名。从现在开始,在 app 中我们就可以是使用 BookStoreApp 作为模组的命名空间了。
添加 run 方法,修改路由
接着,我们将进行路由的配置;打开
www/js/app.js 。删除当中的 config 部分。接下来,我们将设置 run 方法来持有一些工具方法。 在 www/js/app.js 添加以下代码:
.run(['$rootScope', 'AuthFactory',function($rootScope, , AuthFactory) { $rootScope.isAuthenticated = AuthFactory.isLoggedIn(); // utility method to convert number to an array of elements $rootScope.getNumber = function(num) { return new Array(num); } } ])
可以给一个模组添加多个 run 方法
如果你还记得我们在
menu.html 添加的根据 isAuthenticated 条件显示的登录和登出按钮的话,isAuthenticated 属性就是在此处初始化的。 我们也有一个工具方法名为 getNumber 。这个方法是用来以传入的参数作为长度生成一个数组的。接下来,我们将添加路由。在
run 之下,添加如下 config 代码:
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function($stateProvider, $urlRouterProvider, $httpProvider) { // setup the token interceptor $httpProvider.interceptors.push('TokenInterceptor'); $stateProvider .state('app', { url: "/app", abstract: true, templateUrl: "templates/menu.html", controller: 'AppCtrl' }) .state('app.browse', { url: "/browse", views: { 'menuContent': { templateUrl: "templates/browse.html", controller: 'BrowseCtrl' } } }) .state('app.book', { url: "/book/:bookId", views: { 'menuContent': { templateUrl: "templates/book.html", controller: 'BookCtrl' } } }) .state('app.cart', { url: "/cart", views: { 'menuContent': { templateUrl: "templates/cart.html", controller: 'CartCtrl' } } }) .state('app.purchases', { url: "/purchases", views: { 'menuContent': { templateUrl: "templates/purchases.html", controller: 'PurchasesCtrl' } } }); // if none of the above states are matched, use this as the fallback $urlRouterProvider.otherwise('/app/browse'); } ])
虽然我们可以手动编辑路由,但是我认为这样展示更简单。 同时,如果你的 Ionic 服务一直在运行的时候,你应该会在浏览器的 JavaScript 控制台看到错误输出:找不到依赖库。在所有代码完成之前,app 都不会正常工作。可以在所有代码完成之前关闭服务器。
config 里面我们添加的第一个是
TokenInterceptor 。当我们在使用认证工厂的时候,我们会实现 TokenInterceptor 。在使用 TokenInterceptor 的时候,我会寻回此处。我们添加了以下这些路由:
- /app:这是用来管理应用主视图的一个抽象路由
- /browse:这是应用的主页,用来列出所有书籍
- /book/:bookId:展示某本书的详情
- /purchase:展示用户过往的订购记录 登录和注册标签界面将会是一个 modal 而不是一个 route。
重构模板
我们可以根据路由配置一次重构一个模板,或者为了便利起见,我们可以(或者说将要)删除
www/templates 里面除了 menu.html 之外的所有模板,然后添加一些空白的 HTML 文件。删除除了
menu.html 之外的所有模板之后,我们创建以下几个模板:
- browse.html
- book.html
- cart.html
- purchases.html
- login.html 目前可以将以上这些模板都留空。最后我们才会用到这些模板。
第三步 - 创建认证,本地存储和 REST API 工厂
在
www/js 文件夹内,创建一个文件名为 factory.js 。然后在 www/index.html 的 controller.js 的引用后面添加:
<script src="js/factory.js"></script>
这个文件将用来持有所有的工厂。当然,如果你喜欢的话你也可以给每个功能添加一个工厂文件。但是咱们的范例里面所有工厂都在一个文件里。文件第一行要添加的是 REST API 的基本 URL。这个 URL 可以指向你的本地服务器也可以指向 Heroku 上的已有 REST API 地址。如下:
//var base = 'http://localhost:3000'; var base = 'https://ionic-book-store.herokuapp.com';
可以根据需求来注释其中一行。接下来创建一个新的模组名为
BookStoreApp.factory 来持有所有的工厂定义。然后在主模组
angular.module('BookStoreApp.factory', [])
BookStoreApp 中将此模组添加为依赖。打开 www/js/app.js ,更新 BookStoreApp 如下:
angular.module('BookStoreApp', ['ionic', 'BookStoreApp.controllers', 'BookStoreApp.factory'])
Ionic loading 工厂
第一个制作的工厂是加载工厂。在 AngularJS 模组定义下,添加如下:
.factory('Loader', ['$ionicLoading', '$timeout', function($ionicLoading, $timeout) { var LOADERAPI = { showLoading: function(text) { text = text || 'Loading...'; $ionicLoading.show({ template: text }); }, hideLoading: function() { $ionicLoading.hide(); }, toggleLoadingWithMessage: function(text, timeout) { $rootScope.showLoading(text); $timeout(function() { $rootScope.hideLoading(); }, timeout || 3000); } }; return LOADERAPI; }])
我们有以下三个方法来帮助我们展示,隐藏和切换加载:
- showLoading:显示一个带文本或者
Loading 的阻断 modal
- hideLoading:隐藏使用
showLoading 显示的 modal
- toggleLoadingWithMessage:使用提供的信息显示 modal,并且在提供的超时或者默认的 3 秒后隐藏。这个方法将用来展示自动隐藏的阻断信息。
localStorage 工厂
接下来我们要添加
localStorage 工厂。在 Loader 工厂之前,添加如下:
.factory('LSFactory', [function() { var LSAPI = { clear: function() { return localStorage.clear(); }, get: function(key) { return JSON.parse(localStorage.getItem(key)); }, set: function(key, data) { return localStorage.setItem(key, JSON.stringify(data)); }, delete: function(key) { return localStorage.removeItem(key); }, getAll: function() { var books = []; var items = Object.keys(localStorage); for (var i = 0; i < items.length; i++) { if (items[i] !== 'user' || items[i] != 'token') { books.push(JSON.parse(localStorage[items[i]])); } } return books; } }; return LSAPI; }])
这个工厂提供了我们和 HTML5
localStorage 交互的 API。我们将使用本地存储来保存所有书籍。这样一来,我们不需要每次获取书籍的时候都调用服务端接口。由于这是个范例而已,我们就不会在后台检查新书籍然后更新本地缓存。
loacalStorage 的 clear , getItem ,setItem he deleteItem 方法分别封装成了 clear ,get ,set 和 delete 。由于我们需要将对象存放到本地存储,而本地存储之支持字符串, 所以我们在存数据之前将对象字符串话,在取出来之后对象化。
getAll 方法将用来获取我们在 localStorage 中存放的所有书籍。
注意我们使用了
Object.keys 来将 localStorage (类数组)转换成一个数组。 更多信息关于 Object.keys 请参考: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
认证工厂
下一个进行的是 Authentication(认证)工厂。在
LSFactory 定义后面添加以下代码:
.factory('AuthFactory', ['LSFactory', function(LSFactory) { var userKey = 'user'; var tokenKey = 'token'; var AuthAPI = { isLoggedIn: function() { return this.getUser() === null ? false : true; }, getUser: function() { return LSFactory.get(userKey); }, setUser: function(user) { return LSFactory.set(userKey, user); }, getToken: function() { return LSFactory.get(tokenKey); }, setToken: function(token) { return LSFactory.set(tokenKey, token); }, deleteAuth: function() { LSFactory.delete(userKey); LSFactory.delete(tokenKey); } }; return AuthAPI; }])
此工厂依赖
LSFactory ,用来管理用户认证数据。就像之前说的那样,当用户注册或者登录的时候,服务端在返回用户数据的同时附带了访问 token。 这些封装好了的方法通过 LSFactory 的 API 保存用户数据和 token 到本地存储。接下来,我们将创建
TokenInterceptor 工厂。如果你还记得的话,我们之前在 www/js/app.js 的 config 部分中已经添加了 TokenInterceptor 的引用。 我们告诉 AngularJS 在他每次发起 HTTP 请求的时候调用 TokenInterceptor 。
什么是拦截器(Interceptors)? 当使用 $http 服务发起 AJAX 请求的时候,我们的 app 里面有些时候需要在发起 HTTP 请求之前添加一些额外的数据。 AngularJS 中这种用途的代码就叫做拦截器。 将拦截器添加到$httpProvider 的语法是这样的: $httpProvider.interceptors.push('MyInterceptor'); * 在设置好名为 MyInterceptor*的工厂或者服务之后,可以参考此处获取更多拦截器和他的工作流程的信息: http://www.webdeveasy.com/interceptors-in-angularjs-anduseful-examples/
当调用此方法的时候,他会检查
localStorage 中是否有用户数据和用户 token。如果有的话他会将这些数据添加到请求头里面。如果你还记得的话,添加购物车,查看购物车,检出,以及查看订单路由都需要用户认证。 token 是服务端知道用户是否认证成功是有授权浏览相关内容的唯一途径。在
www/js/factory.js 的 AuthFactory 的下面,添加 TokenInterceptor 工厂的代码如下:
.factory('TokenInterceptor', ['$q', 'AuthFactory', function($q, AuthFactory) { return { request: function(config) { config.headers = config.headers || {}; var token = AuthFactory.getToken(); var user = AuthFactory.getUser(); if (token && user) { config.headers['X-Access-Token'] = token.token; config.headers['X-Key'] = user.email; config.headers['Content-Type'] = "application/json"; } return config || $q.when(config); }, response: function(response) { return response || $q.when(response); } }; }])
REST API 工厂
接下来,我们要创建两个工厂。一个用作与 REST API 服务终端交互,无需认证就可以拿到数据,另一个工厂用作与 REST API 服务终端交互,需要认证。这只是一个逻辑分离。可以在一个工厂里面操作。第一个是
BookFactory 。其中有一个 get 方法与公共的 api/v1/books 终端对话:
.factory('BooksFactory', ['$http', function($http) { var perPage = 30; var API = { get: function(page) { return $http.get(base + '/api/v1/books/' + page + '/' + perPage); } }; return API; }])
api/v1/books REST 终端服务有数据分页的能力。你可以发送 perPage 和 page 数字;这样他将会发挥对应的记录。 你可以使用这个 API 来请求分页数据,然后更具需求加载书籍。但是,在本应用中,我们只请求 1 次,返回 30 条记录。你可以实现一个
ion-infinite-scroll 根据需求加载书籍作为练习。接下来是
UserFactory 。这个工厂是由 login ,register REST 终端以及其他 4 个购物车和订购相关的 API 组成。在
BooksFactory 的后面添加 UserFactory 的相关定义:
.factory('UserFactory', ['$http', 'AuthFactory', function($http, AuthFactory) { var UserAPI = { login: function(user) { return $http.post(base + '/login', user); }, register: function(user) { return $http.post(base + '/register', user); }, logout: function() { AuthFactory.deleteAuth(); }, getCartItems: function() { var userId = AuthFactory.getUser()._id; return $http.get(base + '/api/v1/users/' + userId + '/cart'); }, addToCart: function(book) { var userId = AuthFactory.getUser()._id; return $http.post(base + '/api/v1/users/' + userId + '/cart', book); }, getPurchases: function() { var userId = AuthFactory.getUser()._id; return $http.get(base + '/api/v1/users/' + userId + '/purchases'); }, addPurchase: function(cart) { var userId = AuthFactory.getUser()._id; return $http.post(base + '/api/v1/users/' + userId + '/purchases', cart); } }; return UserAPI; } ])
getCartItems ,addToCart ,getPurchases 和 addPurchase 方法在 REST 调用之前会先从 localStorage 里面获取用户 ID。REST 终端服务需要的也是这样的 URL。完成这些之后,我们成功的加入了工厂方法来管理数据,认证以及 REST 交互。在处理相关控制器的时候,我们会讲解每个 REST 终端的响应。如果你的服务器还在运行并且你正确的做完所有事情的话,你应该会看到一个关于
BrowserCtrl 的错误。这样就是对的,我们接下来就处理控制器了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论