2015年4月27日 星期一

Hybrid Apps開發系列之5.1:資料模型與自訂services(重構天氣app線上版)

Ionic/AngularJS apps透過$scope變數可以定義資料模型 (data model) 變數供前端頁面 (views)使用,而在controller端,則會將資料模型變數設定為數值、陣列甚至是透過web services得到的資料。然而,如果有資料處理、頁面共用資料模型等更複雜的運作方式時,應該呼叫.factory()採用「自訂services」的方式,將資料處理等相關程式碼打包進factory,供$scope以及前端頁面運用,如下圖所示:
圖1.各種資料來源可透過factory進行管理
如圖1,factory可將資料庫、web services、或是一般JavaScript物件包裹起來,建立資料處理服務,提供$scope與前端頁面使用。Factory就相當於apps的資料層 (data layer)。

Factory基本格式

Ionic/AngularJS模組都可建立factory,做為資料層,基本格式如下:
angular.module('starter',['ionic])
   .factory('serviceName',function(){
       return {}
   }
)

從語法上看,Factory是一回傳物件的函式。建好factory之後,在controllers中可用"serviceName"存取factory的資料與功能。底下factory範例是一個攝氏華式度數轉換的service:
angular.module('starter.services',[])
.factory('TempService',function(){
var times = 1.8;
var base = 32;
return {
ctof: function(degree){
return degree*times+base;
},
ftoc: function(degree) {
return (degree-base)/times;
}
}
}
)
view raw services.js hosted with ❤ by GitHub
此例建立名為"TempService"的factory,並提供ctof(degree), ftoc(degree)兩個函式服務,只要給予度數參數即可進行轉換。

重構天氣app線上版

此次範例以天氣概況頁面為基礎,改用下拉選單在單一頁面模式下運作:

index.html檔案內如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title></title>
<link href="lib/ionic/css/ionic.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
<link href="css/ionic.app.css" rel="stylesheet">
-->
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<!-- cordova script (this will be a 404 during development) -->
<script src="cordova.js"></script>
<!-- your app's js -->
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
</head>
<body ng-app="starter">
<ion-nav-bar class="bar-positive">
</ion-nav-bar>
<ion-view title="天氣概況" ng-controller="weatherCtrl">
<ion-content>
<div class="list card">
<div class="item">
<label class="item item-input item-select">
<div class="input-label">
選擇城市
</div>
<select ng-model="myCity"
ng-change="changeCity(myCity)"
ng-options="item.q as item.name for item in citys">
</select>
</label>
</div>
<div ng-show="myCity">
<div class="item">
<h2>{{city[0].name}}</h2>
</div>
<div class="item item-avatar">
<img src="{{img_base_url + weather.weather[0].icon + '.png'}}">
<h2>{{weather.main['temp'] - 273.15| number:1}}°C /
{{converter.ctof(weather.main['temp'] - 273.15) | number:1}}°F</h2>
<h2>{{(weather.dt)*1000 | date:'yyyy-MM-dd HH:mm:ss'}}</h2>
</div>
<div class="item">
<div class="row">
<div class="col">濕度</div>
<div class="col">{{weather.main.humidity}}%</div>
</div>
<div class="row">
<div class="col">風速</div>
<div class="col">{{weather.wind.speed}}哩/秒</div>
</div>
</div>
</div>
</div>
</ion-content>
</ion-view>
</body>
</html>
view raw index.html hosted with ❤ by GitHub

  • 第23-24行:分別加入js/controllers.js 與 js/services.js。
  • 第33-43行:加入下拉選單(亦可參考Hybrid Apps開發系列之3.1:下拉選單)。ng-model指定使用資料模型變數myCity儲存下拉選單各選項的數值(value);選項選取狀態改變,則呼叫controller端定義的事件處理器changeCity()。最後下拉選單選項文字與數值,則從citys資料模型變數而來。如參照controllers.js的內容,則會發現citys的內容則是由Factory所提供。
  • 第46行:city資料模型變數內容亦是由factory提供,由於city是陣列,此處則取第一個資料呈現。詳細說明請參考controllers.js段落。
  • 其餘可參考Hybrid Apps開發系列之4.4:多頁面App程式 (天氣app-非線上版)
由於定義了兩個模組controllers與services,因此app.js檔建立app模組時,必須註明inject此兩個模組,如下列程式碼第1行所示:
angular.module('starter', ['ionic','starter.controllers','starter.services'])
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
StatusBar.styleDefault();
}
});
})
view raw app.js hosted with ❤ by GitHub

js/services.js檔案如下:
angular.module('starter.services', [])
.factory('TempService', function ($http, $filter) {
var times = 1.8;
var base = 32;
var citys = [
{name: "台北", q: "Taipei", on: true},
{name: "台中", q: "Taichung", on: true},
{name: "台南", q: "Tainan", on: true},
{name: "高雄", q: "Kaohsiung", on: true},
{name: "花蓮", q: "Hualian", on: true},
]
return {
ctof: function (degree) {
return degree * times + base;
},
ftoc: function (degree) {
return (degree - base) / times;
},
get: function (city) {
return $http.get('http://api.openweathermap.org/data/2.5/weather',
{params: {'q': city}});
},
getCitys: function () {
return citys;
},
getCityQ: function (index) {
return citys[index].q;
},
getCityName: function (q) {
return citys.filter(function (e) {
return (e.q === q)
})
}
}
}
)
view raw services.js hosted with ❤ by GitHub

  • 第2行:建立TempService服務。$http提供連線取得資料功能,$filter則用於陣列搜尋
  • 第5-11行:陣列資料放在service端,透過後續定義的函式,便可供controller取用。
  • 第13-18行:分別建立兩個服務函式ctof 與ftoc。
  • 第19-22行:取得特定城市的天氣資料,city參數便是傳遞進來的城市名稱。對照controllers.js便知城市名稱是由下拉選單改變選擇項目時傳送過來的。
  • 第23-25行:建立資料模型變數citys,其值就是第5-11行宣告的陣列。
  • 第26-28行:提供取得城市英文名稱的服務功能。
  • 第29-32行:提供取得城市中文名稱的服務功能。此處使用了陣列的.filter(),該函式可過濾出符合條件的陣列元素,此處是搜尋陣列元素中欄位q與傳送進來資料相符的元素。
最後js/controllers.js程式如下:
angular.module('starter.controllers', [])
.controller('weatherCtrl', function ($scope, $http, TempService) {
$scope.citys = TempService.getCitys();
$scope.img_base_url = image_base_url;
$scope.converter = TempService;
$scope.changeCity = function(item){
TempService.get(item)
.then(function (resp) {
$scope.weather = resp.data;
}, function (err) {
alert(err);
});
$scope.city=TempService.getCityName(item);
}
})
var image_base_url = "http://openweathermap.org/img/w/"; // open weather api icon
view raw controllers.js hosted with ❤ by GitHub

  • 第2行:由於使用到網路連線與自訂服務TempService兩個服務,所以必須inject此兩個模組。
  • 第3,5行:使用自訂服務TempService,取得程式資料,以及讓頁面可以使用TempService。
  • 第6-14行:呼叫TempService的get(item)功能,取得特定城市的天氣概況。

沒有留言:

張貼留言