第一个ionic简例分析
上文中已经将ionic的开发环境搭建完毕,并且创建了一个带有tab页的项目。
运行效果图:
项目代码目录图:
ionic中是使用angularJs和ionic的页面组件来进行开发的,其代码的核心目录为上图中的www目录,代码一般都写在该目录下,主要是html,js和css文件。(上图中的hello.html文件不属于该项目,可忽略)
整个项目运作的流程如下:
1.用户请求应用起始页。
2.用户的浏览器向服务器发起一次HTTP 连接,然后加载index.html 页面,这个页面里面包含了模板。
3.Angular 被加载到页面中,等待页面加载完成,然后查找ng-app 指令,用来定义模板边界。
4.Angular 遍历模板,查找指令和绑定关系,这将触发一系列动作:注册监听器、执行一些DOM 操作、从服务器获取初始化数据。这项工作的最后结果是,应用将会启动起来,并且模板被转换成了DOM 视图。
5.连接到服务器去加载需要展示给用户的其他数据。
下面具体分析一下代码:
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">
<!--
The nav bar that will be updated as we navigate between views.
-->
<ion-nav-bar class="bar-stable">
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<!--
The views will be rendered in the <ion-nav-view> directive below
Templates are in the /templates folder (but you could also
have templates inline in this html file if you'd like).
-->
<ion-nav-view></ion-nav-view>
</body>
</html>
index.html中 head中就是配置一些适配参数和引用一些js,包括app.js,controllers.js,services.js 自己定义的3个js文件。
body中是绘制整个页面的,在body这个标签上加上了ng-app 标记,表示body这个标签之内的东西由angularJs来解析,angularJs承包了body这个鱼塘,并且起了一个名字:starter,这样就会告诉Angular 去管理页面上的所有DOM 元素。
body中就是一些ionic的界面组件了,每个组件的解释和用法可参考该网站:组件api
在最后一行,有个ion-nav-view组件,该组件的作用为:在app启动时,$stateProvider就会检查url,检查它的索引匹配状态,然后尝试将对应的html加载到<ion-nav-view>内。
tabs.html
<!--
Create tabs with an icon and label, using the tabs-positive style.
Each tab's child <ion-nav-view> directive will have its own
navigation history that also transitions its views in and out.
-->
<ion-tabs class="tabs-icon-top tabs-color-active-positive">
<!-- Dashboard Tab -->
<ion-tab title="Status" icon-off="ion-ios-pulse" icon-on="ion-ios-pulse-strong" href="#/tab/dash">
<ion-nav-view name="tab-dash"></ion-nav-view>
</ion-tab>
<!-- Chats Tab -->
<ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline" icon-on="ion-ios-chatboxes" href="#/tab/chats">
<ion-nav-view name="tab-chats"></ion-nav-view>
</ion-tab>
<!-- Account Tab -->
<ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-on="ion-ios-gear" href="#/tab/account">
<ion-nav-view name="tab-account"></ion-nav-view>
</ion-tab>
</ion-tabs>
该html即为整个布局页面,3个tab页:Status,Chats,Account
定义了3个tab,每个tab中有一个ion-nav-view ,还定义了tab 页选中和没选中时的图标,以及指定超链接目标的 URL
tab-dash.html
<ion-view view-title="Dashboard">
<ion-content class="padding">
<div class="list card">
<div class="item item-divider">Recent Updates</div>
<div class="item item-body">
<div>
There is a fire in <b>sector 3</b>
</div>
</div>
</div>
<div class="list card">
<div class="item item-divider">Health</div>
<div class="item item-body">
<div>
You ate an apple today!
</div>
</div>
</div>
<div class="list card">
<div class="item item-divider">Upcoming</div>
<div class="item item-body">
<div>
You have <b>29</b> meetings on your calendar tomorrow.
</div>
</div>
</div>
</ion-content>
</ion-view>
此html中是纯的静态代码,用到了较多的ionic组件,可参考上面的api了解每个组件的用法。
-----重点来了-----
tab-chats.html
<ion-view view-title="Chats">
<ion-content>
<ion-list>
<ion-item class="item-remove-animate item-avatar item-icon-right" ng-repeat="chat in chats" type="item-text-wrap" href="#/tab/chats/{{chat.id}}">
<img ng-src="{{chat.face}}">
<h2>{{chat.name}}</h2>
<p>{{chat.lastText}}</p>
<i class="icon ion-chevron-right icon-accessory"></i>
<ion-option-button class="button-assertive" ng-click="remove(chat)">
Delete
</ion-option-button>
</ion-item>
</ion-list>
</ion-content>
</ion-view>
这是第2个tab页,是一个list列表。首先定义了页面的标题:Chats ,然后定义了一个列表,接着定义列表的item,其中有这么一句:ng-repeat="chat in chats"
, ng-repeat是angular的标签,表示遍历,后面的写法应该就是遍历chats了,那么问题来了:chats从哪来的呢?
带着疑问继续看代码
先看一下angularJs中的一个重要概念
$scope概念
$scope 的使用贯穿整个 Angular App 应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope 就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope 发生改变时也会立刻重新渲染视图。其实就是双向绑定。
有了 $scope 这样一个桥梁,应用的业务代码可以都在 controller 中,而数据都存放在controller 的 $scope 中。
$scope 的作用
$scope 对象在 Angular 中充当数据模型的作用,也就是一般 MVC 框架中 Model 得角色.但又不完全与通常意义上的数据模型一样,因为 $scope 并不处理和操作数据,它只是建立了视图和 HTML 之间的桥梁,让视图和 Controller 之间可以友好的通讯。
再进一步系统的划分它的作用和功能:
- 提供了观察者可以监听数据模型的变化
- 可以将数据模型的变化通知给整个 App
- 可以进行嵌套,隔离业务功能和数据
- 给表达式提供上下文执行环境
在 Javascript 中创建一个新的执行上下文,实际就是用函数创建了一个新的本地上下文,在 Angular 中当为子 DOM 元素创建新的作用域时,其实就是为子 DOM 元素创建了一个新的执行上下文。
$scope 生命周期
Angular 中也有一个'事件'的概念,比如当一个绑定了 ng-model的 input 值发生变化时,或者一个 ng-click 的 button 被点击时,Angular 的事件循环就会启动.事件循环是 Angular 中非常非常核心的一个概念,因为不是本文主旨所以不多说,感兴趣的可以自己看看资料.这里事件就在 Angular 执行上下文中处理,$scope 就会对定义的表达式求值.此时事件循环被启动, Angular 会监控应用程序内所有对象,脏值检查循环也会启动。
$scope 的生命周期有4个阶段:
- 创建
控制器或者指令(ng-controller)创建时, Angular 会使用 $injector 创建一个新的作用域,然后在控制器或指令运行时,将作用域传递进去。
- 链接
Angular 启动后会将所有 $scope 对象附加或者说链接到视图上,所有创建 $scope 对象的函数也会被附加到视图上.这些作用域将会注册当 Angular 上下文发生变化时需要运行的函数.也就是 $watch 函数, Angular 通过这些函数或者何时开始事件循环。
- 更新
一旦事件循环开始运行,就会开始执行自己的脏值检测.一旦检测到变化,就会触发 $scope 上指定的回调函数。
- 销毁
通常来讲如果一个 $scope 在视图中不再需要, Angular 会自己清理它.当然也可以通过 $destroy() 函数手动清理。
可以参考以下网站来进一步了解$scope :
http://www.tuicool.com/articles/nUzMz2J 或者
http://blog.csdn.net/aitangyong/article/details/40267583?utm_source=tuicool&utm_medium=referral
笔者总结一下:
一个controller对应了一个$scope对应了一个DOM,强调一下是DOM,不一定是body,也有可能是div,总之就是一个标签都可以配置一个ng-controller。而这个DOM范围内,都可以引用$scope中存储的数据或者函数
接着上面的问题:chats从哪来的呢?
从来的解释中可以看出,这个chats定是存储在tab-chats.html对应的某个$scope中,由于tab-chats.html中没有直接的DOM指出一个具体的controller,否者我们就直接去controller中找$scope就行了。
但是我们在app.js文件中发现一些端倪:
app.js源代码
// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
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 && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleLightContent();
}
});
})
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.platform.ios.tabs.style('standard');
$ionicConfigProvider.platform.ios.tabs.position('bottom');
$ionicConfigProvider.platform.android.tabs.style('standard');
$ionicConfigProvider.platform.android.tabs.position('bottom');
$ionicConfigProvider.platform.ios.navBar.alignTitle('center');
$ionicConfigProvider.platform.android.navBar.alignTitle('left');
$ionicConfigProvider.platform.ios.backButton.previousTitleText('').icon('ion-ios-arrow-thin-left');
$ionicConfigProvider.platform.android.backButton.previousTitleText('').icon('ion-android-arrow-back');
$ionicConfigProvider.platform.ios.views.transition('ios');
$ionicConfigProvider.platform.android.views.transition('android');
})
.config(function ($stateProvider, $urlRouterProvider) {
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here: https://github.com/angular-ui/ui-router
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: '/tab',
// abstract: true 表明此状态不能被显性激活,只能被子状态隐性激活
abstract: true,
templateUrl: 'templates/tabs.html'
})
// Each tab has its own nav history stack:
.state('tab.dash', {
url: '/dash',
views: {
'tab-dash': {
templateUrl: 'templates/tab-dash.html',
controller: 'DashCtrl'
}
}
})
.state('tab.chats', {
url: '/chats',
views: {
'tab-chats': {
templateUrl: 'templates/tab-chats.html',
controller: 'ChatsCtrl'
}
}
})
.state('tab.chat-detail', {
url: '/chats/:chatId',
views: {
'tab-chats': {
templateUrl: 'templates/chat-detail.html',
controller: 'ChatDetailCtrl'
}
}
})
.state('tab.account', {
url: '/account',
views: {
'tab-account': {
templateUrl: 'templates/tab-account.html',
controller: 'AccountCtrl'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/tab/dash');
});
直接看到以下代码:
.state('tab.chats', {
url: '/chats',
views: {
'tab-chats': {
templateUrl: 'templates/tab-chats.html',
controller: 'ChatsCtrl'
}
}
})
tab-chats.html页面对应的controller为ChatsCtrl,这时候我们就可以去controllers.js中找到名字为ChatsCtrl的controller了。
controllers.js
angular.module('starter.controllers', [])
.controller('DashCtrl', function($scope) {})
.controller('ChatsCtrl', function($scope, Chats) {
$scope.chats = Chats.all();
$scope.remove = function(chat) {
Chats.remove(chat);
};
})
.controller('ChatDetailCtrl', function($scope, $stateParams, Chats) {
$scope.chat = Chats.get($stateParams.chatId);
})
.controller('AccountCtrl', function($scope) {
$scope.settings = {
enableFriends: true
};
});
看到 $scope.chats = Chats.all(); 给chats赋值了,此处的chats即为前面提到问题的答案,tab-chats.html页面的chats的来源即为此处,并且已经赋值了。
那么问题又来了,它(chats )的值是什么呢?
从上面的代码可以看出它的值为:Chats.all() 但是Chats是什么呢?
其实Chats和$scope一样都是服务,只不过$scope是系统服务(就是有系统来定义的),而Chats是自定义服务,需要自己写代码定义。
我们在services.js中可以找到Chats服务。
services.js代码
angular.module('starter.services', [])
.factory('Chats', function() {
var chats = [{
id: 0,
name: 'Ben Sparrow',
lastText: 'You on your way?',
face: 'https://pbs.twimg.com/profile_images/514549811765211136/9SgAuHeY.png'
}, {
id: 1,
name: 'Max Lynx',
lastText: 'Hey, it\'s me',
face: 'https://avatars3.githubusercontent.com/u/11214?v=3&s=460'
}, {
id: 2,
name: 'Adam Bradleyson',
lastText: 'I should buy a boat',
face: 'https://pbs.twimg.com/profile_images/479090794058379264/84TKj_qa.jpeg'
}, {
id: 3,
name: 'Perry Governor',
lastText: 'Look at my mukluks!',
face: 'https://pbs.twimg.com/profile_images/598205061232103424/3j5HUXMY.png'
}, {
id: 4,
name: 'Mike Harrington',
lastText: 'This is wicked good ice cream.',
face: 'https://pbs.twimg.com/profile_images/578237281384841216/R3ae1n61.png'
}];
return {
all: function() {
return chats;
},
remove: function(chat) {
chats.splice(chats.indexOf(chat), 1);
},
get: function(chatId) {
for (var i = 0; i < chats.length; i++) {
if (chats[i].id === parseInt(chatId)) {
return chats[i];
}
}
return null;
}
};
});
上面的factory('Chats', function() 中的 Chats 自定义的服务名称 ,该服务定义了返回值,返回了3个函数:all() ,
remove() ,get() 。
所以上面的Chats.all()的值是什么也就很清楚了,即是var chats 这个对象数组。
关于如何自定义服务,可参考这个网站 http://my.oschina.net/tanweijie/blog/295067
从上面的网站中,了解到这一点:
在Angular里面,services作为单例对象在需要到的时候被创建,只有在应用生命周期结束的时候(关闭浏览器)才会被清除。而controllers在不需要的时候就会被销毁了。
说的是服务的生命周期,很重要的一点。
基本上分析了demo中的大部分,不想写了,下一次再写,分析一下app.js中的一些东西。
文章中用到项目地址 :https://github.com/viphsx123/myApp
完。
参考链接:
http://my.oschina.net/blogshi/blog/316745
http://www.tuicool.com/articles/nUzMz2J
http://blog.csdn.net/aitangyong/article/details/40267583?utm_source=tuicool&utm_medium=referral