step-01 构建项目结构
克隆项目骨架
$ git clone https://github.com/Micua/angular-boilerplate.git moviecat
$ cd moviecat
** 脚本**:npm 在 package.json中的script节点中可以定义脚本任务,脚本可以通过npm run script
的方式执行。
start命令可以直接npm start
执行,并执行prestart与postinstall。.bowerrc文件定义了bower的安装路径"directory":"app/bower_components"
。
文件说明:
.editorconfig -- 统一不同开发者的不同开发工具的不同开发配置
在Sublime中使用需要安装一个EditorConfig的插件
项目骨架:为NG做一个项目骨架的目的是为了快速开始一个新的项目,如github上web-starter-kit,angular-seed等。
设计页面:$ bower install bootstrap --save
安装bootstrap,使用bootstrap页面框架http://v3.bootcss.com/examples/dashboard/
header中引入<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
,将app.css中改成dashboard.css中的内容。
左侧导航
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="#/in_theaters">正在热映</a></li>
<li><a href="#/coming_soon">即将上映</a></li>
<li><a href="#/top250">TOP</a></li>
</ul>
</div>
删除中间部分内容代码,用ng-view管理<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ng-view></div>
创建in_theaters/coming_soon/top250三个文件夹,每个文件夹中新建view.html/controller.js文件,view.html中新建初始化代码<h1 class="page-header">正在热映</h1>
,Controller中,创建代码
(function(angular){
'use strict';
//创建模块
var module = angular.module('moviecat.in_theaters', ['ngRoute'])
//配置模块路由
module.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/in_theaters', {
templateUrl: 'in_theaters/view.html',
controller: 'InTheatersController'
});
}])
module.controller('InTheatersController', ['$scope',function($scope) {
}]);
})(angular)
app.js中引入这3个模块,并配置默认模块
'use strict';
// Declare app level module which depends on views, and components
angular.module('moviecat', [
'ngRoute',
'moviecat.in_theaters',
'moviecat.coming_soon',
'moviecat.top250',
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.otherwise({redirectTo: '/in_theaters'});
}]);
index.html中引入这3个js文件
<script src="in_theaters/controller.js"></script>
<script src="coming_soon/controller.js"></script>
<script src="top250/controller.js"></script>
浏览页面
API的概念:Application Programming Interface应用程序编程接口
豆瓣API V2 https://developers.douban.com/wiki/?title=api_v2
正在上映:api.douban.com/v2/movie/in_theaters 可以加?count=1
WebAPI 通过WEB方式提供结构叫做 WEBAPI
所有有输入有输出的事物都可以是API都是函数
测试WebAPI的工具: POSTMAN
step-02 假数据绑定
使用postman获取数据后,选取subjects数组,在in_theaters的controller中,新建var data = [subjects 数组];
并$scope.subjects = data;
绑定model。
使用bootstrap中的list group linked作为行项目,使用Media作为内容,使用list-group-badges作为评分,修改后html如下,使用ng-repeat遍历数据,图片使用ng-src,导演项使用ng-repeat遍历数组并逗号分隔
<h1 class="page-header">正在热映</h1>
<div class="list-group">
<a ng-repeat="item in subjects" href="#" class="list-group-item">
<span class="badge">{{item.rating.average}}</span>
<div class="media">
<div class="media-left">
![]({{item.images.small}})
</div>
<div class="media-body">
<h2 class="media-heading">{{item.title}}</h2>
<p>导演:<span ng-repeat="d in item.directors">
{{d.name}}
<span ng-if = '!$last'>,</span>
</span></p>
<p>类型:<span>{{item.genres.join(',')}}</span></p>
</div>
</div>
</a>
</div>
在app.css文件中加上
.list-group .media{
margin-top: 0;
}
step-03 请求真实数据
请求本地数据:
将postman中得到的raw data复制到json文件
修改controller,加入$http,首先使得$scope.subjects = [];
,否则由于还没初始化,页面上$scope.subjects是个undefind类型
module.controller('InTheatersController', ['$scope','$http',function($scope,$http) {
$scope.subjects = [];
$scope.message = '';
$http.get('/moviecat/app/datas/in_theaters.json').then(function(res){
if (res.status == 200){
$scope.subjects = res.data.subjects;
}else{
$scope.message = '错误信息'+ res.statusText;
}
},function(err){
$scope.message = '错误信息'+ err.statusText;
})
}]);
JSONP
处理异步请求如下,调用时,会将JSON_CALLBACK替换成angular.callbacks_0等命名规则的随机函数,但豆瓣API不支持加点号的调用方式
var doubanApiAddress = 'http://api.douban.com/v2/movie/in_theaters';
// 测试$http服务
// 在Angular中使用JSONP的方式做跨域请求,
// 就必须给当前地址加上一个参数 callback=JSON_CALLBACK
$http.jsonp(doubanApiAddress+'?callback=JSON_CALLBACK').then(function(res) {
// 此处代码是在异步请求完成过后才执行(需要等一段时间)
if (res.status == 200) {
实现跨域
定义jsonp函数传入url, data, callback三个参数,首先生成1个随机数组合成window对象的callback函数名,等于传入的回调函数,data参数包括了传入的查询参数,添加到'?'后面(判断url中是否有'?'),并附加上callback参数,然后组成url,创建script标签,使得src=url + querystring。
(function(window, document, undefined) {
'use strict';
// url = http://ssss?dsf=sdfs&
var jsonp = function(url, data, callback) {
// 1. 挂载回调函数
var fnSuffix = Math.random().toString().replace('.', '');
var cbFuncName = 'my_json_cb_' + fnSuffix;
window[cbFuncName] = callback;
// window.my_json_cb_02132817213 = callback;
// 2. 将data转换为url字符串的形式
// {id:1,name:'zhangsan'} => id=1&name=zhangsan
var querystring = url.indexOf('?') == -1 ? '?' : '&';
for (var key in data) {
querystring += key + '=' + data[key] + '&';
// id = 1 &
}
// querystring = ?id=1&name=zhangsan&
// 3. 处理url中的回调参数
// url += callback=sdjhkfsdjwe
querystring += 'callback=' + cbFuncName;
// querystring = ?id=1&name=zhangsan&cb=my_json_cb_02132817213
// 4. 创建一个script标签
var scriptElement = document.createElement('script');
scriptElement.src = url + querystring;
// -- 注意此时还不能将其append到页面上
// 5. 将script标签放到页面中
document.body.appendChild(scriptElement);
// append过后页面会自动对这个地址发送请求,请求完成以后自动执行
};
window.$jsonp = jsonp;
})(window, document);
页面调用时,添加一个div,调用jsonp函数,将数据显示到这个div。jsonp相当于在script中添加了window.my_json_cb_02132817213=my_json_cb_02132817213({json data})
通过function(data) 获取json data
<body>
<div id="result"></div>
<script src="http.js"></script>
<script>
(function() {
$jsonp(
'http://api.douban.com/v2/movie/in_theaters', {
count: 10,
start: 5
},
function(data) {
document.getElementById('result').innerHTML = JSON.stringify(data);
});
})();
</script>
</body>
自定义JSONP Angular实现
创建http.js文件,实现jsonp
'use strict';
(function(angular) {
// 由于默认angular提供的异步请求对象不支持自定义回调函数名
// angular随机分配的回调函数名称不被豆瓣支持
var http = angular.module('moviecat.services.http', []);
http.service('HttpService', ['$window', '$document', function($window, $document) {
// url : http://api.douban.com/vsdfsdf -> <script> -> html就可自动执行
this.jsonp = function(url, data, callback) {
var fnSuffix = Math.random().toString().replace('.', '');
var cbFuncName = 'my_json_cb_' + fnSuffix;
// 不推荐
$window[cbFuncName] = callback;
var querystring = url.indexOf('?') == -1 ? '?' : '&';
for (var key in data) {
querystring += key + '=' + data[key] + '&';
}
querystring += 'callback=' + cbFuncName;
var scriptElement = $document[0].createElement('script');
scriptElement.src = url + querystring;
$document[0].body.appendChild(scriptElement);
};
}]);
})(angular);
controller中引入新建的http service var module = angular.module('moviecat.in_theaters', ['ngRoute','moviecat.services.http']);
在index.html中引入JS <script src="components/http.js"></script>
,在controller方法中,传入HttpService并调用jsonp
module.controller('InTheatersController', ['$scope','HttpService',function($scope,HttpService) {
$scope.subjects = [];
$scope.message = '';
HttpService.jsonp(
'http://api.douban.com/v2/movie/in_theaters', {},
function(data) {
$scope.subjects = data.subjects;
$scope.$apply();
// $apply的作用就是让指定的表达式重新同步
});
}]);
JSONP更新
每次点击按钮都生成一个新的script标签,我们应该每次加载完后删除这个script标签
调整
$window[cbFuncName] = callback;
的顺序在scriptElement.src = url + querystring;
之后,callback之后,移除这个标签
scriptElement.src = url + querystring;
$window[cbFuncName] = function(data) {
callback(data);
$document[0].body.removeChild(scriptElement);
};
step-04 Loading加载动画设计
参考http://tobiasahlin.com/spinkit/ 动画,插入到view.html与app.css中
添加mask class,并添加ng-show指令,在controller中,先默认
$scope.loading = true;
在回调函数中,再改成false
<div class="mask" ng-show="loading">
<div class="spinner">
<div class="dot1"></div>
<div class="dot2"></div>
</div>
</div>
添加mask的css
.mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .4);
z-index: 2000;
}
step-05 实现分页功能
分页功能
修改路由$routeProvider.when('/in_theaters/:page', {
controller中,添加参数$routeParams
,定义page的相关参数设置如下,在jsonp的参数中,添加{start: start, count: count}
var count = 5; // 每一页的条数
var page = parseInt($routeParams.page); // 当前第几页
var start = (page - 1) * count; // 当前页从哪开始
# 回调函数中
$scope.totalCount = data.total;
$scope.totalPages = Math.ceil($scope.totalCount / count);
在view.html中,添加
<div ng-show="!loading">
<p>总共:{{totalCount}}条记录,第{{currentPage}}/{{totalPages}}页</p>
</div>
访问时,需添加/1
等后缀
分页按钮
在<div ng-show="!loading">
标签中添加分页按钮
<nav>
<ul class="pager">
<li ng-class="{disabled:currentPage<=1}"><a ng-click="go(currentPage - 1)">« 上一页</a></li>
<li ng-class="{disabled:currentPage>=totalPages}"><a ng-click="go(currentPage + 1)">下一页 »</a></li>
</ul>
在controller中,引入$route
,并暴露一个上一页下一页的行为
$scope.go = function(page) {
// 传过来的是第几页我就跳第几页
// 一定要做一个合法范围校验
if (page >= 1 && page <= $scope.totalPages)
$route.updateParams({ page: page });
};
step-06 抽象公共列表页
实现公共列表页
由于3个模块的API结构形式都相同,因此我们建立成一个movie_list的公共列表页,复制in_theaters文件夹成movie_list,修改模块名moviecat.movie_list
,配置路由$routeProvider.when('/:category/:page', {
,修改controller名 'MovieListController',修改url为'http://api.douban.com/v2/movie/'+ $routeParams.category
app.js中引入'moviecat.movie_list',index中引入<script src="movie_list/controller.js"></script>
导航栏切换1
修改导航栏,交由NavController来管理
<ul class="nav nav-sidebar" ng-controller="NavController">
<li ng-class="{active:type=='in_theaters'}"><a href="#/in_theaters/1">正在热映</a></li>
<li ng-class="{active:type=='coming_soon'}"><a href="#/coming_soon/1">即将上映</a></li>
<li ng-class="{active:type=='top250'}"><a href="#/top250/1">TOP</a></li>
</ul>
app.js中定义NavController,利用$scope.$watch
监测路径值的变化
.controller('NavController', [
'$scope',
'$location',
function($scope, $location) {
$scope.$location = $location;
$scope.$watch('$location.path()', function(now) {
if (now.startsWith('/in_theaters')) {
$scope.type = 'in_theaters';
} else if (now.startsWith('/coming_soon')) {
$scope.type = 'coming_soon';
} else if (now.startsWith('/top250')) {
$scope.type = 'top250';
}
console.log($scope.type);
});
}
]);
导航栏切换2
自定义指令auto-focus
来实现,回到原来状态,加上auto-focus
指令
<ul class="nav nav-sidebar">
<li auto-focus><a href="#/in_theaters/1">正在热映</a></li>
<li auto-focus><a href="#/coming_soon/1">即将上映</a></li>
<li auto-focus><a href="#/top250/1">TOP</a></li>
</ul>
新建auto-focus.js文件,定义modulemoviecat.directives.auto_focus
及自定义指令autoFocus
(function(angular) {
angular.module('moviecat.directives.auto_focus', [])
.directive('autoFocus', ['$location', function($location) {
// Runs during compile
var path = $location.path(); // /coming_soon/1
return {
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
link: function($scope, iElm, iAttrs, controller) {
var aLink = iElm.children().attr('href');
var type = aLink.replace(/#(\/.+?)\/\d+/,'$1'); // /coming_soon
if(path.startsWith(type)){
// 访问的是当前链接
iElm.addClass('active');
}
iElm.on('click', function() {
iElm.parent().children().removeClass('active');
iElm.addClass('active');
});
}
};
}]);
})(angular);
分别在index及app.js中引入<script src="components/auto-focus.js"></script>
与'moviecat.directives.auto_focus',
刚开始访问时,$location.path() 为空,可用下面代码来避免
return {
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
link: function($scope, iElm, iAttrs, controller) {
$scope.$location = $location;
$scope.$watch('$location.path()', function(now) {
// 当path发生变化时执行,now是变化后的值
var aLink = iElm.children().attr('href');
var type = aLink.replace(/#(\/.+?)\/\d+/, '$1'); // /coming_soon
if (now.startsWith(type)) {
// 访问的是当前链接
iElm.parent().children().removeClass('active');
iElm.addClass('active');
}
})
异步加载
异步加载需要使用script.js模块,通过bower install script.js --save
安装,在index文件中引入<script src="bower_components/script.js/dist/script.js"></script>
。通过$script异步加载,加载完成后执行回调函数
<script>
$script([
'./bower_components/angular/angular.js',
'./bower_components/angular-route/angular-route.js',
'./movie_list/controller.js',
'./components/http.js',
'./components/auto-focus.js',
'./app.js' // 由于这个包比较小,下载完成过后就直接执行
], function() {
console.log(angular);
angular.bootstrap(document, ['moviecat']);
// console.log(jQuery);
});
</script>
由于app.js文件比较小,加载完成后执行函数,前面的依赖没有加载完,会报错,通过bower install angular-loader --save
安装,在header中引入<script src="bower_components/angular-loader/angular-loader.js"></script>
自动控制依赖顺序。
step-07 搜索模块
index.html修改navbar form,form添加ng-controller与ng-submit,输入框添加ng-model
<form class="navbar-form navbar-right" ng-controller="SearchController" ng-submit="search()">
<input type="text" class="form-control" placeholder="Search..." ng-model="input">
</form>
app.js中创建controller,暴露input数据及search行为,search中,往url后缀参数添加了p=input
.controller('SearchController', [
'$scope',
'$route',
'AppConfig',
function($scope, $route, AppConfig) {
$scope.input = ''; // 取文本框中的输入
$scope.search = function() {
// console.log($scope.input);
$route.updateParams({ category: 'search', q: $scope.input });
};
}
]);
在controller.js中,jsonp加入q参数{start: start, count: count,q: $routeParams.q}
step-08 详细页模块
复制movie_list为新的movie_detail,在index.html及app.js中分别引入js文件及模块名,路由样式/detail/26748673
,同时也与movie_list的路由匹配,因此需将'moviecat.movie_detail',
放在上面
view的样式如下
<div class="jumbotron">
<h1>{{movie.title}}</h1>
![]({{movie.images.large}})
<p>{{movie.summary}}</p>
</div>
<div class="mask" ng-show="loading">
<div class="spinner">
<div class="dot1"></div>
<div class="dot2"></div>
</div>
</div>
controller中,调用API
var id = $routeParams.id;
var apiAddress = 'http://api.douban.com/v2/movie/subject/' + id;
HttpService.jsonp(apiAddress, {}, function(data) {
$scope.movie = data;
$scope.loading = false;
$scope.$apply();
});
movie_list中,行的超链接指向detail页面href="#/detail/{{item.id}}"
为模块定义常量
在app.js中,定义常量
.constant('AppConfig', {
pageSize: 5,
listApiAddress: 'http://api.douban.com/v2/movie/',
detailApiAddress: 'http://api.douban.com/v2/movie/subject/'
})
在controller中,引入并使用
module.controller('MovieListController', [
'$scope',
'$route',
'$routeParams',
'HttpService',
'AppConfig',
function($scope, $route, $routeParams, HttpService, AppConfig) {
var count = AppConfig.pageSize; // 每一页的条数
网友评论