返回介绍

6.3.1 分析需要用到的组件

发布于 2025-02-26 23:07:10 字数 13987 浏览 0 评论 0 收藏 0

搭建应用

现在,我们已经搭建好了服务器,并且对我们将要制作的东西有了一个初步的想法,那么我们现在就可以开始了。 执行以下步骤就可以轻松的搭建好这个 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 提到过,打开开发工具,调整其位置到页面的右边部分。效果图如下:
dev tools

重构菜单

第一个需要重构的是菜单。我们将使用需要用到的条目来替换默认的条目。打开

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 。没有设置的话,你在子视图里面将看不到侧边菜单图标。当你保存文件,返回浏览器的时候,将会看到如下结果:

menu side

重构模组名

接下来,我们将要给模组重命名。默认开始模板的模组名叫做

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文