简介
angularJS 是由谷歌开源的产品,是一个mvc框架,不操作dom元素,由数据驱动页面变化,适用于SPA应用。
angularJS2.0 在升级过程过程中相当于重构了angularJS1.0,api等改变较大。
本文是基于angular1.X 的搭建过程。
本文使用\{\{
\}\}
代替双大括号(即删掉斜线)
特性:
MVC、Module(模块化)、依赖注入、指令系统、双向数据绑定
每个模块都有自己的:scope,model,controller
模块内通过注入可以使用:service,directive,filter
# 初始化项目
mkdir angularDemo&&cd angularDemo&&npm init -y
# 安装依赖
npm install --save requirejs jquery angular angular-ui-router requirejs-domready ngstorage ng-sanitize
添加:favicon.ico
touch index.html
<!DOCTYPE html>
<html xmlns:ng="//angularjs.org" ng-app="myApp">
<head>
<meta charset="utf-8">
<script src="./node_modules/angular/angular.min.js"></script>
<script src="./js/app.js"></script>
</head>
<body>
<div ng-controller="ctrl">\{\{str\}\}</div>
</body>
</html>
mkdir js&&touch js/app.js
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope) {
$scope.str = 'hello world';
});
mkdir js\controller js\directive views
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
</head>
<body>
<div ui-view></div>
<script type='text/javascript' src='./node_modules/requirejs/require.js' data-main='./js/main.js'></script>
</body>
</html>
touch js/main.js
(function() {
var libPath = "";
var rtPath = "../node_modules/";
var requireConfig = {
basePath: './',
paths: {
'jquery': rtPath + 'jquery/dist/jquery.min',
'angular': rtPath + 'angular/angular.min',
'angular-route': rtPath + 'angular-ui-router/release/angular-ui-router.min',
'domReady': rtPath + 'requirejs-domready/domReady',
'bootstrap': "bootstrap",
'app': "app",
'router': "router"
},
shim: {
'angular': {
exports: 'angular'
},
'angular-route': {
deps: ['angular'],
exports: 'angular-route'
}
},
deps: ['bootstrap'],
urlArgs: "bust=" + (new Date()).getTime() //防止读取缓存,调试用
};
var ok = true;
var appInit = function() {
if (ok) {
require.config(requireConfig);
module.exports=requireConfig;
} else {
setTimeout(appInit, 50);
}
}
appInit();
})();
touch js/app.js
define(["angular", 'angular-route', './controller/mainController', './directive/mainDirective'], function(angular) {
return angular.module("webapp", ['ui.router', 'webapp.controllers', 'webapp.directive']);
})
touch js/bootstrap.js
define(['require', 'angular', 'angular-route', 'jquery', 'app', 'router'], function(require, angular) {
require(['domReady!'], function(document) {
angular.bootstrap(document, ['webapp']);
});
});
touch js/router.js
define(["app"], function(app) {
return app.run(['$rootScope', '$state', '$stateParams',
function(root, state, stateParams) {
root.$state = state;
root.$stateParams = stateParams
}
])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', '$uiViewScrollProvider',
function(state, urlRouter, location, uiViewScroll) {
//用于改变state时跳至顶部
uiViewScroll.useAnchorScroll();
// 默认进入先重定向
urlRouter.otherwise('/home');
state
.state('home', {
//abstract: true,
url: '/home',
views: {
'': {
templateUrl: '../views/home.html'
}
}
})
}
])
})
touch views/home.html
<div ng-controller="controller1">
这是home页\{\{str\}\}
<div dir-demo str-demo="'指令'"/>
</div>
touch js/controller/mainController.js
define(['./controller1'], function() {});
touch js/controller/controller1.js
define(['./module', 'jquery'], function(controllers, $) {
controllers.controller('controller1', ['$scope', function(scope) {
//控制器的具体js代码
scope.str = 'controller1';
}])
})
touch js/controller/module.js
define(['angular'], function(angular) {
return angular.module('webapp.controllers', []);
});
touch js/directive/mainDirective.js
define(['./directive'], function() {});
touch js/directive/directive.js
define(['./module', 'jquery'], function(directives, $) {
directives.directive('dirDemo', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
require: '^?accordion',
scope: {
str: '=strDemo'
},
template: '<div> \{\{str\}\}</div>',
link: function(scope, element, attrs, controller) {
}
}
});
})
touch js/directive/module.js
define(['angular'], function(angular) {
return angular.module('webapp.directive', []);
});
index.html
<a ui-sref="test">test</a>
<div ui-view></div>
touch views/layout.html
<div ui-view="nav"></div>
<div ui-view></div>
touch views/nav.html
<ol>
<li><a ui-sref=".page({id:1})">test.page</a></li>
<li><a ui-sref="test.home">test.home</a></li>
</ol>
router.js
//用于改变state时跳至顶部
uiViewScroll.useAnchorScroll();
// // 默认进入先重定向
// urlRouter.otherwise('/pageTab');
// // urlRouter.when("", "/PageTab");
state.state("test", {
url: "/test",
//abstract: true,
views: {
"": { templateUrl: "../views/layout.html"},
"nav@test": {templateUrl: "../views/nav.html"}
}
})
.state("test.page", {
url: "/page?:id",
// templateUrl:"'page3.html'",
template: "test page 页",
controller: ["$stateParams", function($stateParams) {
console.log($stateParams.id); // 1 这里实现传参
}],
params: { id: null }
})
.state("test.home", {
url: "/home",
templateUrl: "../views/home.html"
})
准备:去掉mainController
和mainDirective
安装依赖: npm install --save angular-async-loader
main.js
'asyncLoader': rtPath + 'angular-async-loader/angular-async-loader',
// ...
'asyncLoader': {
exports: 'asyncLoader'
}
app.js
define(["angular", 'asyncLoader','angular-route', './controller/module', './directive/module'], function(angular,asyncLoader) {
var app= angular.module("webapp", ['ui.router', 'webapp.controllers', 'webapp.directive']);
asyncLoader.configure(app);
return app;
})
router.js
.state("test.home", {url: "/home",
templateUrl: "../views/home.html",
controllerUrl: './controller/controller1',
// controller: 'controller1',
// dependencies: ['services/usersService']
})
controller1
define(['app', 'jquery'], function(app, $) {
app.controller('controller1', ['$scope', function(scope) {
scope.str = 'controller1';
}])
})
router.js
app.config(['$controllerProvider','$compileProvider','$filterProvider','$provide',function(controllerProvider,compileProvider,filterProvider,provide){
app.register = {
controller : controllerProvider.register,
directive: compileProvider.directive,
filter: compileProvider.register,
service: provide.service
};
}])
//...
.state("test.home", {
url: "/home",
templateUrl: "../views/home.html",
resolve: {
loadCtrl: ["$q", function($q) {
var deferred = $q.defer();
require(['./controller/controller1'], function() {
deferred.resolve(); });
return deferred.promise;
}]
}
})
controller1.js
define(['app', 'jquery'], function(app, $) {
app.register.controller('controller1', ['$scope', function(scope) {
//控制器的具体js代码
scope.str = 'controller1';
}])
})
touch js/myhttp.js
define(['app'],function(app){
app.service("myhttp",function($http,http,errorMessage,$log){
this.get = function(url, params, success, error){
//$http.post(url ,params).success(function(resp){ }).error(function(resp){ })
$http.get(http + url ,{data:params})
.success(function (resp) {
success && success(resp); })
.error(function (resp) {
$log.debug(resp); });
};
})
})
注入:$templateCache,$http
if ($templateCache.get(url) && !$routeParams.nocache ) {
$scope.templateUrl = url;
} else
$http({
url: url,
method: "POST",
data: DATA,
withCredentials: true,
crossDomain: true
}).success(function(data) {
$templateCache.put(url, data);
$scope.templateUrl = url;
}).error(function(data) {
$scope.templateUrl = 'views/404.html';
});
npm install -g grunt-cli grunt-init
npm install --save-dev grunt grunt-contrib-jshint@1.1.0 grunt-contrib-watch grunt-contrib-qunit grunt-contrib-concat grunt-contrib-uglify
phantomjs(下载)安装失败:
下载后放到:C:\Users\Administrator\AppData\Local\Temp\phantomjs\phantomjs-1.9.8-windows.zip
module.exports = function(grunt) {
grunt.initConfig({
meta: {
version: '0.1.0'
},
// 读取package.json文件
pkg: grunt.file.readJSON('package.json'),
banner: '/*! <%= pkg.title || pkg.name %> - v<%= meta.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>*/\n',
// 任务配置
});
// 加载grunt插件
grunt.loadNpmTasks('grunt-contrib-concat');
// 配置default任务(顺序)
grunt.registerTask('default', []);
};
npm install grunt-contrib-concat --save-dev
在Gruntfile.js配置
module.exports = function(grunt) {
grunt.initConfig({
meta: {
version: '0.1.0'
},
// 读取package.json文件
pkg: grunt.file.readJSON('package.json'),
banner: '/*! <%= pkg.title || pkg.name %> - v<%= meta.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>*/\n',
// 任务配置
//concat用来合并js文件
concat: {
options: {
// 定义一个用于插入合并输出文件之间的字符
separator: ';',
banner: '<%= banner %>',
stripBanners: true
},
dist: {
// 将要被合并的文件
src: ['js/*.js', 'js/**/*.js'],
// 合并后的JS文件的存放位置
dest: 'dist/<%= pkg.name %>.js'
}
}
});
// 加载grunt插件
grunt.loadNpmTasks('grunt-contrib-concat');
// 配置default任务(顺序)
grunt.registerTask('default', ['concat']);
};
npm install grunt-contrib-uglify --save-dev
在Gruntfile.js配置
//uglify用来压缩js文件
uglify: {
options: {
banner: '<%= banner %>'
},
dist: {
files: {
//uglify会自动压缩concat任务中生成的文件
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
// src: '<%= concat.dist.dest %>',
// dest: 'dist/<%= pkg.name %>.min.js'
}
}
// ...
// 加载grunt插件
grunt.loadNpmTasks('grunt-contrib-uglify');
// 配置default任务(顺序)
grunt.registerTask('default', ['concat', 'uglify']);
npm install grunt-contrib-jshint --save-dev
在Gruntfile.js配置
//jshint用来检查js代码规范
jshint: {
// files: ['Gruntfile.js', 'js/*.js', 'js/**/*.js'],
gruntfile: {
src: 'Gruntfile.js'
// files: {'build/js/base.min.js' : ['assets/js/base.js'] }
},
lib_test: {
src: ['js/*.js', 'js/**/*.js']
},
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
unused: false,
expr: true,
asi: true, //允许省略分号
boss: true,
eqnull: true,
browser: true,
force: true,
globals: {
jQuery: false,
$: false,
define: false,
require: false,
console: false,
module: false,
document: true
}
},
}
// ....
// 加载grunt插件
grunt.loadNpmTasks('grunt-contrib-jshint');
// 配置default任务(顺序)
grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
npm install grunt-htmlhint --save-dev
在Gruntfile.js配置
//htmlhint用来检查html代码规范
htmlhint: {
build: {
// htmlhintrc: '.htmlhintrc'
options: {
'tag-pair': true,
'tagname-lowercase': true,
'attr-lowercase': true,
'attr-value-double-quotes': true,
'doctype-first': false,
'spec-char-escape': true,
'id-unique': true,
'head-script-disabled': true,
'style-disabled': true
},
src: ['views/**/*.html']
}
}
// ...
// 加载grunt插件
grunt.loadNpmTasks('grunt-htmlhint');
// 配置default任务(顺序)
grunt.registerTask('default', ['jshint','htmlhint', 'concat', 'uglify']);
npm install grunt-contrib-watch --save-dev
在Gruntfile.js配置
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
},
lib_test: {
files: '<%= jshint.lib_test.src %>',
tasks: ['jshint:lib_test']
}
},
// ...
// 加载grunt插件
grunt.loadNpmTasks('grunt-contrib-watch');
// 配置default任务(顺序)
grunt.registerTask('default', ['jshint', 'htmlhint', 'concat', 'uglify', 'watch']);
// 复制
copy: {
build: {
cwd: 'js', //指向的目录是相对的,全称Change Working Directory更改工作目录
src: ['**'], //指向源文件,**是一个通配符,用来匹配Grunt任何文件
dest: 'images', //用来输出结果任务
expand: true //expand参数为true来启用动态扩展,涉及到多个文件处理需要开启
},
// 注:如果src: [ '**', '!**/*.styl' ],表示除去.styl文件,!在文件路径的开始处可以防止Grunt的匹配模式
},
// 清除
clean: {
build: {
src: ['css/**/*.*']
},
},
less: {
dynamic_mappings: {
files: [{
expand: true,
cwd: 'build/less',
src: ['**/*.less', '!**/header.less'],
dest: 'css',
ext: '.css',
}],
},
},
// CSS压缩
cssmin: {
build: {
expand: true,
cwd: 'css/',
src: ['*.css', '!*.min.css'],
dest: 'css/',
ext: '.css'
}
},
// 在Grunt中加载npm任务
require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);
//JSHint其他配置
jshint:{
files:['Gruntfile.js','app/**/*.js','!app/release/**','modules/**/*.js','specs/**/*Spec.js'],
options:{
curly:true,//使用if和while等结构语句时加上{}
eqeqeq:true,//是否都用了===或者是!==,而不是使用==和!。
immed:true,////匿名函数:(function(){//}());而不是(function(){}(//bla bla));
latedef:true,
newcap:true,//J构造函数名都要大写字母开头。
noarg:true,//如果为真,JSHint会禁止arguments.caller和arguments.callee的使用
sub:true,//如果为真,JSHint会允许各种形式的下标来访问对象。
undef:true,//如果为真,JSHint会要求所有的非全局变量,在使用前都被声明。
boss:true,//如果为真,那么JSHint会允许在if,for,while里面编写赋值语句。
eqnull:true,//JSHint会允许使用"== null"作比较
browser:true,
globals:{
//AMD
module: true,
require:true,
requirejs: true,
define: true,
//Environments
console: true,
//General Purpose Libraries
$: true,
jQuery:true,
//Testing
sinon: true,
describe:true,
it: true,
expect:true,
beforeEach: true,
afterEach: true
}
}
}
Jasmine做单元测试,Karma自动化完成单元测试,Grunt启动Karma统一项目管理
# 安装Karma及其依赖
npm install --save-dev angular-mocks karma karma-jasmine jasmine-core karma-chrome-launcher karma-cli karma-mocha-reporter karma-ng-scenario karma-requirejs
npm install grunt-karma --save-dev
# 初始化配置Karma
karma init karma.require.config.js
module.exports = function(config) {
config.set({
basePath: './',
frameworks: ['jasmine', 'requirejs'],
files: [
{ pattern: 'node_modules/jquery/dist/jquery.min.js', included: false },
{ pattern: 'node_modules/angular/angular.min.js', included: false },
{ pattern: 'node_modules/requirejs-domready/domReady.js', included: false },
{ pattern: 'node_modules/angular-ui-router/release/angular-ui-router.min.js', included: false },
{ pattern: 'node_modules/angular-async-loader/angular-async-loader.js', included: false },
{ pattern: 'node_modules/angular-mocks/angular-mocks.js', included: false },
{ pattern: 'js/**/*.*', included: false, served: true },
{ pattern: 'js/*.*', included: false, served: true },
{ pattern: 'test/*_spec.js', included: false, served: true },
'test/test-main.js'
],
exclude: [
'./js/main.js',
'./karma.require.config.js'
],
preprocessors: {},
// plugins: ['karma-mocha-reporter','karma-chrome-launcher','karma-jasmine','karma-requirejs'],
reporters: ['progress', 'mocha'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
})
}
var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
// var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
var normalizedTestModule = file;
allTestFiles.push(normalizedTestModule);
}
});
var libPath = "";
var rtPath = "../node_modules/";
var requireConfig = {
baseUrl: '/base/js',
paths: {
'jquery': rtPath + 'jquery/dist/jquery.min',
'angular': rtPath + 'angular/angular.min',
'angular-route': rtPath + 'angular-ui-router/release/angular-ui-router.min',
'domReady': rtPath + 'requirejs-domready/domReady',
'asyncLoader': rtPath + 'angular-async-loader/angular-async-loader',
'angularMocks': rtPath + 'angular-mocks/angular-mocks',
'bootstrap': 'bootstrap',
'app': "app",
'router': "router"
},
shim: {
'angular': {
exports: 'angular'
},
'angular-route': {
deps: ['angular'],
exports: 'angular-route'
},
'asyncLoader': {
exports: 'asyncLoader'
},
'angularMocks': {
deps: ['angular'],
exports: 'angular.mock'
}
},
deps: allTestFiles,
callback: window.__karma__.start
};
require.config(requireConfig);
define(['app','angularMocks','jquery','../js/controller/controller1','../js/directive/directive' ], function(app,mocks,jquery) {
describe('Test Controller', function() {
describe('controller1', function() {
var $scope;
//模拟Application模块并注入依赖
beforeEach(mocks.module('webapp'));
//模拟Controller,并且包含 $rootScope 和 $controller
beforeEach(mocks.inject(function($rootScope, $controller) {
//创建一个空的 scope
$scope = $rootScope.$new();
//声明 Controller并且注入已创建的空的 scope
$controller('controller1', {$scope: $scope});
}));
// beforeEach(inject(function($injector) {
// $scope = $injector.get('$rootScope').$new();
// $controller = $injector.get('$controller')('controller1', {
// '$scope': $scope
// });
// }));
it('should str is controller1', function() {
expect($scope.str).toBe('controller1');
});
});
});
describe('Test Directive', function() {
describe('dirDemo', function() {
var $scope,$compile;
//模拟Application模块并注入依赖
beforeEach(mocks.module('webapp'));
beforeEach(mocks.inject(function(_$compile_,_$rootScope_) {
$scope = _$rootScope_;
$compile = _$compile_;
}));
it('should display the demo text', function () {
var elt = $compile('<div dir-demo str-demo="\'指令\'"/>'
)($scope);
$scope.$digest();
expect(elt.html()).toContain('指令');
});
});
})
})
运行:karma start karma.require.config.js
.filter('demo', function() {
return function(text,lable) {
return text+lable;
};
});
describe('Test Filter', function() {
describe('filterDemo', function() {
var $filter;
beforeEach(mocks.module('webapp'));
beforeEach(mocks.inject(function(demoFilter) {
$filter = demoFilter;
}));
it('should filter the demo ', function () {
expect($filter("abc"," filter")).toEqual('abc filter');
});
});
})
npm install --save-dev karma-coverage
karma.require.config.js
preprocessors: {
'test/*_spec.js': 'coverage'
},
reporters: ['progress', 'coverage'],
coverageReporter: {
type: 'html',
dir: 'coverage/'
}
npm install grunt-karma --save-dev
在Gruntfile.js配置
karma: {
unit: {
configFile: 'karma.require.config.js',
// port: 9999,
// singleRun: true,
// browsers: ['Chrome'],
// logLevel: 'ERROR'
}
}
// ...
// 加载grunt插件
grunt.loadNpmTasks('grunt-karma');
// grunt karma
grunt.registerTask('test', ['karma']);
运行:grunt test