6.3.2 分析需要用到的组件
第四步 - 给每个路由创建控制器并将其整合到工厂
现在,我们工厂配置好,我们就要创建所需的控制器了。再一次,为了简单起见,我们删掉了
www/js/controller.js 里面的 AppCtrl ,PlaylistsCtrl ,和 PlayListCtrl ,只留下: 。 angular.module('BookStoreApp.controllers', [])
应用控制器
第一个要添加的控制器是应用控制器或者
AppCtrl 。这个控制器用来管理登录和注册功能。如果其他子控制器(例如购物车控制器)想要强制用户登录, 在处理请求之前,这个控制器将会在 scope 内广播一个 showLoginModal ,然后 showLoginModal 的监听器将会负责管理登录和注册流程。一旦用户登录成功,将会通知购物车控制器继续操作。我们将利用
$on 和 $broadcast 来触发自定义事件,例如:showLoginModal 。我们将在
www/js/controller.js 的模组定义后面添加 AppCtrl 。为更好的解释此代码,我们将 AppCtrl 代码分离成两部分。首先,添加定义与所有需要的依赖:
.controller('AppCtrl', ['$rootScope', '$ionicModal','AuthFactory', '$location', 'UserFactory', '$scope', 'Loader', function($rootScope, $ionicModal, AuthFactory, $location,UserFactory, $scope, Loader) { }])
接下来,在 root scope(根范围)内注册
showLoginModal 事件。如下:
$rootScope.$on('showLoginModal', function($event, scope, cancelCallback, callback) { $scope.user = { email: '', password: '' }; $scope = scope || $scope; $scope.viewLogin = true; $ionicModal.fromTemplateUrl('templates/login.html', { scope: $scope }).then(function(modal) { $scope.modal = modal; $scope.modal.show(); $scope.switchTab = function(tab) { if (tab === 'login') { $scope.viewLogin = true; } else { $scope.viewLogin = false; } } $scope.hide = function() { $scope.modal.hide(); if (typeof cancelCallback === 'function') { cancelCallback(); } } $scope.login = function() { Loader.showLoading('Authenticating...'); UserFactory.login($scope.user).success(function(data) { data = data.data; AuthFactory.setUser(data.user); AuthFactory.setToken({ token: data.token, expires: data.expires }); $rootScope.isAuthenticated = true; $scope.modal.hide(); Loader.hideLoading(); if (typeof callback === 'function') { callback(); } }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); } $scope.register = function() { Loader.showLoading('Registering...'); UserFactory.register($scope.user). success(function(data) { data = data.data; AuthFactory.setUser(data.user); AuthFactory.setToken({ token: data.token, expires: data.expires }); $rootScope.isAuthenticated = true; Loader.hideLoading(); $scope.modal.hide(); if (typeof callback === 'function') { callback(); } }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); } }); });
接下来,在
$rootScope 属性上加上两个方法。这两个方法将在 sidemenu 中调用:
$rootScope.loginFromMenu = function() { $rootScope.$broadcast('showLoginModal', $scope, null, null); } $rootScope.logout = function() { UserFactory.logout(); $rootScope.isAuthenticated = false; $location.path('/app/browse'); Loader.toggleLoadingWithMessage('Successfully Logged Out!', 2000); }
showLoginModal 接受 3 个参数:
- scope:modal 会在哪个 scope 里面创建。如果没有提供 scope 的话,就会使用
AppCtrl 。
- cacelCallback:当用户取消登录或者注册流程的时候执行的回调函数。
- callback:注册或者登录成功之后的回调函数。
在
login.html 文件里面,我们使用 $ionicModal 服务创建了一个 modal。我们将在下面的部分使用这个模板。
login 和 register 方法负责与 UserFactory 里面对应的 login 和 register 方法对话。当用户登录或者注册成功的时候,服务端返回的结果看起来应该是这样子的:
{ "error": null, "data": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0 MzM1MjIwNzQ4MzYsInVzZXIiOnsiX2lkIjoiNTU2ODU5NjgyN2ZjY2JjMTZkNjA4MzBi IiwiZW1haWwiOiJhQGEuY29tIiwibmFtZSI6ImEiLCJjYXJ0IjpbeyJpZCI6IjU1NjgzY 2Q4ZmJiMmUxOTI0ZjE4YTRlYSIsInF0eSI6MX1dLCJwdXJjaGFzZXMiOlt7IlB1cmNoYX NlIG1hZGUgb24gMjktTWF5LTIwMTUgYXQgMTc6NTAiOlt7ImlkIjoiNTU2ODNjZDhmYmI yZTE5MjRmMThhNGU4IiwicXR5IjoxfV19LHsiUHVyY2hhc2UgbWFkZSBvbiAyOS1NYXkt MjAxNSBhdCAxNzo1OSI6W3siaWQiOiI1NTY4M2NkOGZiYjJlMTkyNGYxOGE0ZWIiLCJxd HkiOjF9LHsiaWQiOiI1NTY4M2NkOGZiYjJlMTkyNGYxOGE0ZjYiLCJxdHkiOjF9LHsiaW QiOiI1NTY4M2NkOGZiYjJlMTkyNGYxOGE0ZjgiLCJxdHkiOjF9XX0seyJQdXJjaGFzZSB tYWRlIG9uIDI5LU1heS0yMDE1IGF0IDIwOjUxIjpbeyJpZCI6IjU1NjgzY2Q4ZmJiMmUx OTI0ZjE4YTRlOCIsInF0eSI6MX0seyJpZCI6IjU1NjgzY2Q4ZmJiMmUxOTI0ZjE4YTRlY SIsInF0eSI6MX0seyJpZCI6IjU1NjgzY2Q4ZmJiMmUxOTI0ZjE4YTRlZSIsInF0eSI6MX 1dfV19fQ.J0U0BZXhP6C1VWEHDT18BMOzkK_dvXP-xdGUiIr7-z8", "expires": 1433522074836, "user": { "_id": "5568596827fccbc16d60830b", "email": "a@a.com", "name": "a", } }, "message": "Success" }
我们从响应数据中提取
token 和 user 对象,然后使用 AuthFactory 的 setUser 和 setToken 方法将它们设入 localStorage 。TokenInterceptor 将在进行 REST 调用之前在其中读取数据附加到请求中。
loginFromMenu 和 logout 是有菜单条目 Login/Logout 发起调用的。loginFromMenu 所做的只是广播 showLoginModal 来处理用户认证。
logout 方法调用 UserFactory 的登录方法,此方法用来清理 localStorage 中的用户数据和 token。 同时他也会重置 isAuthenticated 属性,并将用户重定向到/app/browse 页面。
浏览控制器(browse controller)
接下来需要处理的是
BrowseCtrl 。这个控制器负责在用户启动应用的时候展示所有书籍。BrowseCtrl 在首次和 REST API 终端服务成功拿取数据之后将它们存放到 localStorage 中。此后,每次调用之前,都会在 localStorage 查找,节约电池,节省流量。
作为练习,实现一个后台程序用最新的书籍来更新
localStorage
.controller('BrowseCtrl', ['$scope', 'BooksFactory', 'LSFactory','Loader', function($scope, BooksFactory, LSFactory, Loader) { Loader.showLoading(); // support for pagination var page = 1; $scope.books = []; var books = LSFactory.getAll(); // if books exists in localStorage, use that instead of making a call if (books.length > 0) { $scope.books = books; Loader.hideLoading(); } else { BooksFactory.get(page).success(function(data) { // process books and store them // in localStorage so we can work with them later on, // when the user is offline processBooks(data.data.books); $scope.books = data.data.books; $scope.$broadcast('scroll.infiniteScrollComplete'); Loader.hideLoading(); }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); } function processBooks(books) { LSFactory.clear(); // we want to save each book individually // this way we can access each book info. by it's _id for (var i = 0; i < books.length; i++) { LSFactory.set(books[i]._id, books[i]); }; } } ])
鉴于咱们 REST API 支持分页,我们需要传入
page 和 BookFactory 里面设置好的 perPage 来获取分页数据。我将页数动态化,你可以根据需求和分页加载书籍。但是本范例中我们将一次加载所有的 30 本书。
书籍控制器(book controller)
用户浏览了所有书籍然后想要查看某特定书籍的详情的时候,他将点击书籍。此时,我们将用户重定向到
/book 页,在这里将会触发 BookCtrl 。 BookCtrl 将在 localStorage 中查找指定 id 的书籍然后展示他的详情。在
www/js/controllers.js 的 BrowseCtrl 后面加上 BookCtrl 代码:
.controller('BookCtrl', ['$scope', '$state', 'LSFactory','AuthFactory', '$rootScope', 'UserFactory', 'Loader', function($scope, $state, LSFactory, AuthFactory, $rootScope, UserFactory, Loader) { var bookId = $state.params.bookId; $scope.book = LSFactory.get(bookId); $scope.$on('addToCart', function() { Loader.showLoading('Adding to Cart..'); UserFactory.addToCart({ id: bookId, qty: 1 }).success(function(data) { Loader.hideLoading(); Loader.toggleLoadingWithMessage('Successfully added ' + $scope.book.title + ' to your cart', 2000); }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); }); $scope.addToCart = function() { if (!AuthFactory.isLoggedIn()) { $rootScope.$broadcast('showLoginModal', $scope, null, function() { // user is now logged in $scope.$broadcast('addToCart'); }); return; } $scope.$broadcast('addToCart'); } } ])
我们使用
LSFactory.get(bookId) 从 localStorage 中获取书籍。查看 demo 的时候,你会发现在书籍详情页面上有一个 (添加到购物车)的按钮。添加到购物车的逻辑非常简单。用户在点击 的时候, 我们检查用户时候已经登录。如果没有,我们广播一个
Add to CartAdd to Cart showLoginModal 事件,尔后在用户登录成功之后广播 addToCart 事件,这样就可以将当前展示的书籍添加到购物车。如果用户已经登录,那么直接广播
addToCart 。
addToCart 事件创建了一个对象,含有 bookid 和数量属性(硬编码到 1),然后调用 UserFactory.addToCart 方法。这个方法负责添加书籍到购物车。
购物车控制器(cart controller)
接下来就是购物车控制器了。这个控制器是在用户点击页头的购物车图标或者点击侧边菜单的购物车按钮的时候调用的。当用户点击购物车的时候,我们会检查用户是否是登录状态。如果用户是登录状态,我们将获取用户购物车条目并展示给用户看。如果没有登录的话就广播
showLoginModal ,这个事件负责处理认证。 如果用户取消登录 modal,我们会将用户带回/app/browse 。当用户认证成功或者登录成功的时候,我们将广播
getCart 事件。此事件将调用 UserFactory.getCartItems 方法获取所有的购物车条目。获取成功之后,我们会将他们赋值给$scope 的 books 属性。在
BookCtrl 后面添加 CartCtrl 代码:
.controller('CartCtrl', ['$scope', 'AuthFactory', '$rootScope','$location', '$timeout', 'UserFactory', 'Loader', function($scope, AuthFactory, $rootScope, $location, $timeout,UserFactory, Loader) { $scope.$on('getCart', function() { Loader.showLoading('Fetching Your Cart..'); UserFactory.getCartItems().success(function(data) { $scope.books = data.data; Loader.hideLoading(); }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); }); if (!AuthFactory.isLoggedIn()) { $rootScope.$broadcast('showLoginModal', $scope, function() { // cancel auth callback $timeout(function() { $location.path('/app/browse'); }, 200); }, function() { // user is now logged in $scope.$broadcast('getCart'); }); return; } $scope.$broadcast('getCart'); $scope.checkout = function() { // we need to send only the id and qty var _cart = $scope.books; var cart = []; for (var i = 0; i < _cart.length; i++) { cart.push({ id: _cart[i]._id, qty: 1 // hardcoded to 1 }); }; Loader.showLoading('Checking out..'); UserFactory.addPurchase(cart).success(function(data) { Loader.hideLoading(); Loader.toggleLoadingWithMessage('Successfully checked out', 2000); $scope.books = []; }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); } } ])
我们在
$scope 上添加了 checkout 方法。当用户查看他的购物车的时候,他们可以检出购物车,此方法会调用 UserFactory.addPurchase 方法,并传入 cart 数组。 cart 数组是由添加到购物的书籍的 id 和 quantity 组成的对象所组成的。
订购控制器(purchase controller)
咱们应用的最后一个控制器是订购控制器。这个控制器展示了当前登入用户的订购清单。跟购物车控制器一样,我们要先检查用户是否已经登录之后才能继续。 一旦用户登录成功之后,我们广播
getPurchases 事件。这个事件将负责与 UserFactory.getPurchases 方法联络,获取订购清单:
.controller('PurchasesCtrl', ['$scope', '$rootScope','AuthFactory', 'UserFactory', '$timeout', 'Loader', function($scope, $rootScope, AuthFactory, UserFactory,$timeout, Loader) { // http://forum.ionicframework.com/t/expandable-list-inionic/3297/2 $scope.groups = []; $scope.toggleGroup = function(group) { if ($scope.isGroupShown(group)) { $scope.shownGroup = null; } else { $scope.shownGroup = group; } }; $scope.isGroupShown = function(group) { return $scope.shownGroup === group; }; $scope.$on('getPurchases', function() { Loader.showLoading('Fetching Your Purchases'); UserFactory.getPurchases().success(function(data) { var purchases = data.data; $scope.purchases = []; for (var i = 0; i < purchases.length; i++) { var key = Object.keys(purchases[i]); $scope.purchases.push(key[0]); $scope.groups[i] = { name: key[0], items: purchases[i][key] } var sum = 0; for (var j = 0; j < purchases[i][key].length; j++) { sum += parseInt(purchases[i][key][j].price); }; $scope.groups[i].total = sum; }; Loader.hideLoading(); }).error(function(err, statusCode) { Loader.hideLoading(); Loader.toggleLoadingWithMessage(err.message); }); }); if (!AuthFactory.isLoggedIn()) { $rootScope.$broadcast('showLoginModal', $scope, function() { $timeout(function() { $location.path('/app/browse'); }, 200); }, function() { // user is now logged in $scope.$broadcast('getPurchases'); }); return; } $scope.$broadcast('getPurchases'); } ])
我们添加了两个方法,
toggleGroup 和 isGroupShown 。这两个方法将在我们建立模块的时候调用。一旦响应返回的时候,我们将格式化这些数据,这样他就可以被组成一个内嵌列表。
建议在继续学习之前检出此 app 的在线版本以查看订购功能。这样你就可以理解此部分代码做了些什么。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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