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中引入,将app.css中改成dashboard.css中的内容。
左侧导航
删除中间部分内容代码,用ng-view管理
创建in_theaters/coming_soon/top250三个文件夹,每个文件夹中新建view.html/controller.js文件,view.html中新建初始化代码
,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文件
浏览页面
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遍历数组并逗号分隔
正在热映
在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
自定义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 ->
,在controller方法中,传入HttpService并调用jsonpmodule.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
添加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中,添加
总共:{{totalCount}}条记录,第{{currentPage}}/{{totalPages}}页
访问时,需添加
/1
等后缀
分页按钮
在
标签中添加分页按钮
在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中引入
导航栏切换1
修改导航栏,交由NavController来管理
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
指令
新建auto-focus.js文件,定义module
moviecat.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中引入
与
'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异步加载,加载完成后执行回调函数
由于app.js文件比较小,加载完成后执行函数,前面的依赖没有加载完,会报错,通过
bower install angular-loader --save
安装,在header中引入自动控制依赖顺序。
step-07 搜索模块
index.html修改navbar form,form添加ng-controller与ng-submit,输入框添加ng-model
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的样式如下
{{movie.title}}
{{movie.summary}}
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; // 每一页的条数