如何在 AngularJS 中对控制器ssm怎么进行单元测试试

angularjs中的单元测试实例
投稿:junjie
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了angularjs中的单元测试实例,本文主要说说利用karma和jasmine来进行ng模块的单元测试,需要的朋友可以参考下
当ng项目越来越大的时候,单元测试就要提上日程了,有的时候团队是以测试先行,有的是先实现功能,后面再测试功能模块,这个各有利弊,今天主要说说利用karma和jasmine来进行ng模块的单元测试.
什么是Karma
karma是一个单元测试的运行控制框架,提供以不同环境来运行单元测试,比如chrome,firfox,phantomjs等,测试框架支持jasmine,mocha,qunit,是一个以nodejs为环境的npm模块.
安装测试相关的npm模块建议使用----save-dev参数,因为这是开发相关的,一般的运行karma的话只需要下面两个npm命令
npm install karma --save-dev
npm install karma-junit-reporter --save-dev
安装karma的时候会自动的安装一些常用的模块,参考karma代码里的package.json文件的peerDependencies属性
&"peerDependencies": {
&&&&&&& "karma-jasmine": "~0.1.0",
&&&&&&& "karma-requirejs": "~0.2.0",
&&&&&&& "karma-coffee-preprocessor": "~0.1.0",
&&&&&&& "karma-html2js-preprocessor": "~0.1.0",
&&&&&&& "karma-chrome-launcher": "~0.1.0",
&&&&&&& "karma-firefox-launcher": "~0.1.0",
&&&&&&& "karma-phantomjs-launcher": "~0.1.0",
&&&&&&& "karma-script-launcher": "~0.1.0"
然后一个典型的运行框架通常都需要一个配置文件,在karma里可以是一个karma.conf.js,里面的代码是一个nodejs风格的,一个普通的例子如下:
module.exports = function(config){
& config.set({
&&& // 下面files里的基础目录
&&& basePath : '../',
&&& // 测试环境需要加载的JS信息
&&& files : [
&&&&& 'app/bower_components/angular/angular.js',
&&&&& 'app/bower_components/angular-route/angular-route.js',
&&&&& 'app/bower_components/angular-mocks/angular-mocks.js',
&&&&& 'app/js/**/*.js',
&&&&& 'test/unit/**/*.js'
&&& // 是否自动监听上面文件的改变自动运行测试
&&& autoWatch : true,
&&& // 应用的测试框架
&&& frameworks: ['jasmine'],
&&& // 用什么环境测试代码,这里是chrome`
&&& browsers : ['Chrome'],
&&& // 用到的插件,比如chrome浏览器与jasmine插件
&&& plugins : [
&&&&&&&&&&& 'karma-chrome-launcher',
&&&&&&&&&&& 'karma-firefox-launcher',
&&&&&&&&&&& 'karma-jasmine',
&&&&&&&&&&& 'karma-junit-reporter'
&&&&&&&&&&& ],
&&& // 测试内容的输出以及导出用的模块名
&&& reporters: ['progress', 'junit'],
&&& // 设置输出测试内容文件的信息
&&& junitReporter : {
&&&&& outputFile: 'test_out/unit.xml',
&&&&& suite: 'unit'
这里要注意的时,上面的插件大部分都不需要单独安装,因为安装karma的时候已经安装了,这里只有karma-junit-reporter导出插件需要单独安装,想要了解更多的关于配置文件的信息可以,
karma就讲到这里,想了解更多关于它的信息可以,
什么是jasmine
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
上面是jasmine官方文档里对它的解释,下面用中文简单的翻译下
jasmine是一个行为驱动开发的测试框架,不依赖任何js框架以及dom,是一个非常干净以及友好API的测试库.
下面简单的以一个例子来说明它的用法
定义一个测试文件命令为test.js
describe("A spec (with setup and tear-down)", function() {
& beforeEach(function() {
&&& foo = 0;
&&& foo += 1;
& afterEach(function() {
&&& foo = 0;
& it("is just a function, so it can contain any code", function() {
&&& expect(foo).toEqual(1);
& it("can have more than one expectation", function() {
&&& expect(foo).toEqual(1);
&&& expect(true).toEqual(true);
上面的例子来自于官网,这里只说下几个重要的API,更多的用法请,
1.首先任何一个测试用例以describe函数来定义,它有两参数,第一个用来描述测试大体的中心内容,第二个参数是一个函数,里面写一些真实的测试代码
2.it是用来定义单个具体测试任务,也有两个参数,第一个用来描述测试内容,第二个参数是一个函数,里面存放一些测试方法
3.expect主要用来计算一个变量或者一个表达式的值,然后用来跟期望的值比较或者做一些其它的事件
4.beforeEach与afterEach主要是用来在执行测试任务之前和之后做一些事情,上面的例子就是在执行之前改变变量的值,然后在执行完成之后重置变量的值
最后要说的是,describe函数里的作用域跟普通JS一样都是可以在里面的子函数里访问的,就像上面的it访问foo变量
想要运行上面的测试例子可以通过karar来运行,命令例子如下:
karma start test/karma.conf.js
下面我们重点的说说ng里的控制器,指令,服务模块的单元测试.
NG的单元测试
因为ng本身框架的原因,模块都是通过di来加载以及实例化的,所以为了方便配合jasmine来编写测试脚本,所以官方提供了angular-mock.js的一个测试工具类来提供模块定义,加载,注入等.
下面说说ng-mock里的一些常用方法
1.angular.mock.module 此方法同样在window命名空间下,非常方便调用
module是用来配置inject方法注入的模块信息,参数可以是字符串,函数,对象,可以像下面这样使用
beforeEach(module('myApp.filters'));
beforeEach(module(function($provide) {
&&&&& $provide.value('version', 'TEST_VER');
它一般用在beforeEach方法里,因为这个可以确保在执行测试任务的时候,inject方法可以获取到模块配置
1.angular.mock.inject 此方法同样在window命名空间下,非常方便调用
inject是用来注入上面配置好的ng模块,方面在it的测试函数里调用,常见的调用例子如下:
angular.module('myApplicationModule', [])
&&&&& .value('mode', 'app')
&&&&& .value('version', 'v1.0.1');
& describe('MyApp', function() {
&&& // You need to load modules that you want to test,
&&& // it loads only the "ng" module by default.
&&& beforeEach(module('myApplicationModule'));
&&& // inject() is used to inject arguments of all given functions
&&& it('should provide a version', inject(function(mode, version) {
&&&&& expect(version).toEqual('v1.0.1');
&&&&& expect(mode).toEqual('app');
&&& // The inject and module method can also be used inside of the it or beforeEach
&&& it('should override a version and test the new version is injected', function() {
&&&&& // module() takes functions or strings (module aliases)
&&&&& module(function($provide) {
&&&&&&& $provide.value('version', 'overridden'); // override version here
&&&&& inject(function(version) {
&&&&&&& expect(version).toEqual('overridden');
上面是官方提供的一些inject例子,代码很好看懂,其实inject里面就是利用angular.inject方法创建的一个内置的依赖注入实例,然后里面的模块注入跟普通ng模块里的依赖处理是一样的
简单的介绍完ng-mock之后,下面我们分别以控制器,指令,过滤器来编写一个简单的单元测试.
ng里控制器的单元测试
定义一个简单的控制器
var myApp = angular.module('myApp',[]);
&&& myApp.controller('MyController', function($scope) {
&&&&& $scope.spices = [{"name":"pasilla", "spiciness":"mild"},
&&&&&&&&&&&&&&&&&&&&&& {"name":"jalapeno", "spiciness":"hot hot hot!"},
&&&&&&&&&&&&&&&&&&&&&& {"name":"habanero", "spiciness":"LAVA HOT!!"}];
&&&&& $scope.spice = "hello feenan!";
然后我们编写一个测试脚本
describe('myController function', function() {
& describe('myController', function() {
&&& beforeEach(module('myApp'));
&&& beforeEach(inject(function($rootScope, $controller) {
&&&&& $scope = $rootScope.$new();
&&&&& $controller('MyController', {$scope: $scope});
&&& it('should create "spices" model with 3 spices', function() {
&&&&& expect($scope.spices.length).toBe(3);
&&& it('should set the default value of spice', function() {
&&&&& expect($scope.spice).toBe('hello feenan!');
上面利用了$rootScope来创建子作用域,然后把这个参数传进控制器的构建方法$controller里去,最终会执行上面的控制器里的方法,然后我们检查子作用域里的数组数量以及字符串变量是否跟期望的值相等.
想要了解更多关于ng里的控制器的信息,可以
ng里指令的单元测试
定义一个简单的指令
var app = angular.module('myApp', []);
app.directive('aGreatEye', function () {
&&& return {
&&&&&&& restrict: 'E',
&&&&&&& replace: true,
&&&&&&& template: '&h1&lidless, wreathed in flame, 1 times&/h1&'
然后我们编写一个简单的测试脚本
describe('Unit testing great quotes', function() {
&&& var $rootS
&&& // Load the myApp module, which contains the directive
&&& beforeEach(module('myApp'));
&&& // Store references to $rootScope and $compile
&&& // so they are available to all tests in this describe block
&&& beforeEach(inject(function(_$compile_, _$rootScope_){
&&&&& // The injector unwraps the underscores (_) from around the parameter names when matching
&&&&& $compile = _$compile_;
&&&&& $rootScope = _$rootScope_;
&&& it('Replaces the element with the appropriate content', function() {
&&&&&&& // Compile a piece of HTML containing the directive
&&&&&&& var element = $compile("&a-great-eye&&/a-great-eye&")($rootScope);
&&&&&&& // fire all the watches, so the scope expression 1 will be evaluated
&&&&&&& $rootScope.$digest();
&&&&&&& // Check that the compiled element contains the templated content
&&&&&&& expect(element.html()).toContain("lidless, wreathed in flame, 2 times");
上面的例子来自于官方提供的,最终上面的指令将会这用在html里使用
&a-great-eye&&/a-great-eye&
测试脚本里首先注入$compile与$rootScope两个服务,一个用来编译html,一个用来创建作用域用,注意这里的_,默认ng里注入的服务前后加上_时,最后会被ng处理掉的,这两个服务保存在内部的两个变量里,方便下面的测试用例能调用到
$compile方法传入原指令html,然后在返回的函数里传入$rootScope,这样就完成了作用域与视图的绑定,最后调用$rootScope.$digest来触发所有监听,保证视图里的模型内容得到更新
然后获取当前指令对应元素的html内容与期望值进行对比.
想要了解更多关于ng里的指令的信息,可以
ng里的过滤器单元测试
定义一个简单的过滤器
var app = angular.module('myApp', []);
app.filter('interpolate', ['version', function(version) {
&&& return function(text) {
&&&&& return String(text).replace(/\%VERSION\%/mg, version);
然后编写一个简单的测试脚本
describe('filter', function() {
& beforeEach(module('myApp'));
& describe('interpolate', function() {
&&& beforeEach(module(function($provide) {
&&&&& $provide.value('version', 'TEST_VER');
&&& it('should replace VERSION', inject(function(interpolateFilter) {
&&&&& expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after');
上面的代码先配置过滤器模块,然后定义一个version值,因为interpolate依赖这个服务,最后用inject注入interpolate过滤器,注意这里的过滤器后面得加上Filter后缀,最后传入文本内容到过滤器函数里执行,与期望值进行对比.
利用测试来开发NG有很多好处,可以保证模块的稳定性,还有一点就是能够深入的了解ng的内部运行机制,所以建议用ng开发的同学赶紧把测试补上吧!
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
小富即安, 积分 4181, 距离下一级还需 819 积分
论坛徽章:2
开发者们都一致认为单元测试在开发项目中十分有好处。它们帮助你保证代码的质量,从而确保更稳定的研发,即使需要重构时也更有信心。
& && && && &
测试驱动开发流程图
AngularJS的代码声称其较高的可测性确实是合理的。单单文档中列出端对端的测试实例就能说明。就像AngularJS这样的项目虽然都说单元测试很简单但真正做好却不容易。即使官方文档中以提供了详尽的实例,但在我的实际应用中却还是很有挑战。这里我就简单示范一下我是怎么操作的吧.
Instant Karma
Karma 是来Angular团队针对JavaScript开发的一个测试运行框架。它很方便的实现了自动执行测试任务从而替代了繁琐的手工操作(好比回归测试集或是加载目标测试的依赖关系)Karma 和Angular的协作就好比花生酱和果冻.
只需要在Karma中定义好配置文件启动它,接下来它就会在预期的测试环境下的自动执行测试用例。你可以在配置文件中制定相关的测试环境。angular-seed,是我强烈推荐的可以快速实施的方案。在我近期的项目中Karma 的配置如下:
module.exports = function(config) {
& & config.set({
& && &&&basePath: '../',
& && &&&files: [
& && && && &'app/lib/angular/angular.js',
& && && && &'app/lib/angular/angular-*.js',
& && && && &'app/js/**/*.js',
& && && && &'test/lib/recaptcha/recaptcha_ajax.js',
& && && && &'test/lib/angular/angular-mocks.js',
& && && && &'test/unit/**/*.js'
& && &&&],
& && &&&exclude: [
& && && && &'app/lib/angular/angular-loader.js',
& && && && &'app/lib/angular/*.min.js',
& && && && &'app/lib/angular/angular-scenario.js'
& && &&&],
& && &&&autoWatch: true,
& && &&&frameworks: ['jasmine'],
& && &&&browsers: ['PhantomJS'],
& && &&&plugins: [
& && && && &'karma-junit-reporter',
& && && && &'karma-chrome-launcher',
& && && && &'karma-firefox-launcher',
& && && && &'karma-jasmine',
& && && && &'karma-phantomjs-launcher'
& && &&&],
& && &&&junitReporter: {
& && && && &outputFile: 'test_out/unit.xml',
& && && && &suite: 'unit'
这个跟angular-seed的默认配置类似只不过有以下几点不同:
需要更改浏览器从Chrome 转到PhantomJS, 这样每次跳转时无需再打开新的浏览器窗口,但在OSX系统会有窗口延迟。所以这个插件还有浏览器设置都做了更改。
由于我的应用需要引用Google的Recaptcha服务因此添加了依赖的recaptcha_ajax.js小文件。这个小配置就像在Karma的配置文件中添加一行代码那么简单。
autoWatch确实是个很酷的设置,它会让Karma在有文件更改时自动回归你的测试用例。你可以这样安装Karma:
npm install karma
angular-seed 提供了一个简单的脚本inscripts/test.sh去触发Karma的测试。
用Jasmine设计测试用例
当使用Jasmine----一种行为驱动开发模式的JavaScript测试框架为Angular设计单元测试用例时大部分的资源都已可获取。
这也就是我接下来要说的话题。
如果你要对AngularJS controller做单元测试可以利用Angular的依赖注入dependency injection 功能导入测试场景中controller需要的服务版本还能同时检查预期的结果是否正确。例如,我定义了这个controller去高亮需要导航去的那个页签:
app.controller('NavCtrl', function($scope, $location) {
& & $scope.isActive = function(route) {
& && &&&return route === $location.path();
如果想要测试isActive方法,我会怎么做呢?我将检查$locationservice 变量是否返回了预期值,方法返回的是否预期值。因此在我们的测试说明中我们会定义好局部变量保存测试过程中需要的controlled版本并在需要时注入到对应的controller当中。然后在实际的测试用例中我们会加入断言来验证实际的结果是否正确。整个过程如下:
describe('NavCtrl', function() {
& & var $scope, $location, $rootScope, createC
& & beforeEach(inject(function($injector) {
& && &&&$location = $injector.get('$location');
& && &&&$rootScope = $injector.get('$rootScope');
& && &&&$scope = $rootScope.$new();
& && &&&var $controller = $injector.get('$controller');
& && &&&createController = function() {
& && && && &return $controller('NavCtrl', {
& && && && && & '$scope': $scope
& && && && &});
& && &&&};
& & it('should have a method to check if the path is active', function() {
& && &&&var controller = createController();
& && &&&$location.path('/about');
& && &&&expect($location.path()).toBe('/about');
& && &&&expect($scope.isActive('/about')).toBe(true);
& && &&&expect($scope.isActive('/contact')).toBe(false);
使用整个基本的结构,你就能设计各种类型的测试。由于我们的测试场景使用了本地的环境来调用controller,你也可以多加上一些属性接着执行一个方法清除这些属性,然后再验证一下属性到底有没有被清除。
$httpBackendIs Cool
那么要是你在调用$httpservice请求或是发送数据到服务端呢?还好,Angular提供了一种
$httpBackend的mock方法。这样的话,你就能自定义服务端的响应内容,又或是确保服务端的响应结果能和单元测试中的预期保持一致。
具体细节如下:
describe('MainCtrl', function() {
& & var $scope, $rootScope, $httpBackend, $timeout, createC
& & beforeEach(inject(function($injector) {
& && &&&$timeout = $injector.get('$timeout');
& && &&&$httpBackend = $injector.get('$httpBackend');
& && &&&$rootScope = $injector.get('$rootScope');
& && &&&$scope = $rootScope.$new();
& && &&&var $controller = $injector.get('$controller');
& && &&&createController = function() {
& && && && &return $controller('MainCtrl', {
& && && && && & '$scope': $scope
& && && && &});
& && &&&};
& & afterEach(function() {
& && &&&$httpBackend.verifyNoOutstandingExpectation();
& && &&&$httpBackend.verifyNoOutstandingRequest();
& & it('should run the Test to get the link data from the go backend', function() {
& && &&&var controller = createController();
& && &&&$scope.urlToScrape = '';
& && &&&$httpBackend.expect('GET', '/slurp?urlToScrape=http:%')
& && && && &.respond({
& && && && && & &success&: true,
& && && && && & &links&: [&&, &http://angularjs.org&, &&]
& && && && &});
& && &&&// have to use $apply to trigger the $digest which will
& && &&&// take care of the HTTP request
& && &&&$scope.$apply(function() {
& && && && &$scope.runTest();
& && &&&});
& && &&&expect($scope.parseOriginalUrlStatus).toEqual('calling');
& && &&&$httpBackend.flush();
& && &&&expect($scope.retrievedUrls).toEqual([&&, &http://angularjs.org&, &&]);
& && &&&expect($scope.parseOriginalUrlStatus).toEqual('waiting');
& && &&&expect($scope.doneScrapingOriginalUrl).toEqual(true);
正如你所见,beforeEach call其实都很类似,唯一不同的是我们是从injector获取$httpBackend而并非直接获取。即使如此,创建不同的测试时还会有一些明显的不同之处。对初学者来说,会有一个afterEachcall 方法来确保$httpBackend在每次用例执行后不会有明显的异常请求。如果你观察一下测试场景的设置和$httpBackend方法的应用就会会发现有那么几点不是那么直观的。
实际上调用$httpBackend的方法也算是简单明了但还不够——我们还得在传值给$scope.$apply的方法中把调用封装到实际测试中的$scope.runTest方法上。这样在$digest被触发后才能处理HTTP请求。而如你所见直到我们调用$httpBackend.flush()方法后$httpBackend才会被解析,这也就保证了我们能在调用过程中去验证返回的结果是否正确(在上面的示例中,controller的$scope.parseOriginalUrlStatusproperty属性将被传递给调用者,我们也因此能实时监控)
接下来的几行代码都是在调用过程中检测$scopethat属性的断言。很酷吧?
提示:在某些单元测试中,用户习惯把没有$的范围标记为变量。这个在Angular文档中并没有强制要求或是过分强调,只是我在使用中为了提高可读性和一致性才使用$scopelike这种方式。
也许这就是我做起来对其他人而言只是自然而然能做到的事情之一,但是学习使用Angular编写单元测试一开始对我而言确实是相当痛苦的。我发现自己对如何开始的理解大多来自互联网上各种博客文章和资源的拼拼凑凑,没有真正一致或明确的最佳实践,而是通过自然而然随意的选择。我想针对我最终得到的成果提供一些文档,以帮助那些也许还在坑里面挣扎的其他人,毕竟他们只是想要编写代码而已,而非不得不去了解Angular和Jasmine中所有的怪异特性和独特用法。因此我希望这篇文章能对你有些许帮助。
本文转自:开源中国社区
英文原文:
本文来自ChinaUnix新闻频道,如果查看原文请点:
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbspAngularJS控制器 - 简书
AngularJS控制器
Controller控制器
控制器的定义
控制器的作用是通过附加模型和和方法来为其扩大作用域,为了随后在视图层能够访问的到。AngularJS中的控制器就是在html文件中AngularJS程序遇到ng-controller指令的时候生成的构造函数。我们将会在下一章中探索AngularJS的作用域。正如我在第一章中提到的,作用域scope就是控制器和视图层之间的胶水,所以控制器能够添加属性,视图层能够访问到这些属性。
为控制器附加属性和方法
下一步,我们写一个控制器,用来通过一个作用域传递现在的时间到视图层。
controller
angular.module('myAPP',[])
.controller('GreetingController',function($scope){
$scope.now=new Data();//把时间模型附加到作用域上
$scope.greeting='Hello';
})通过在angular.module()上调用controller()来注册一个控制器。这个控制器需要两个参数:第一个参数是这个控制器的名字,第二个是当在html中执行到ng-controller指令时被调用的实例方法。能够忽略这个实例方法中的作用域$scope参数吗?当你声明这个$scope作为一个控制器实例的参数,你就表明你的控制器是依赖于这个$scope对象的。这个$scope的参数有特殊意义。AngularJS根据实例方法参数的名字来推断这个控制器的依赖。在这个例子中,当AngularJS在HTML文件中找到ng-controller='GreetingController'这个指令时,AngularJs会为控制器创建一个新的作用域,当实例化这个方法时,AngularJS会传递这个作用域作为一个参数到这个实例方法。这叫做依赖注入,是AngularJS的核心。我们将会在后面的章节中讨论依赖注入的话题。
能够为控制器设置多个依赖关系,并且能够声明这些依赖作为参数传递到构造方法中。为了增加$scope,每个AngularJS应用都有一个一个$rootScope。我们假设你有一个普通的服务用来通过XHR连接后端。你可以声明这两个服务依赖,像如下一样:
angular.module('myApp',[])
.controller(ControllerWithDependency',function($rootScope,customService){
//在这里使用依赖
});当实例化控制器的时候,AngularJS会读取参数列表,并且从这些名字中可以指定哪些需要被依赖注入。你也需要注意到AngularJS自己自带的服务前缀是以$为命名约定的。所以你不应该以$作为你服务的前缀。
&!DOCTYPE html&
&html ng-app="myApp"&
&script type='text/javascript' src="/ajax/libs/angularjs/1.2.16/angular.js" &&/script&&
&script src="app.js"&&/script&
&body ng-controller="GreetingController"&
{{greeting}} User! The current date/time is &span&{{now | date:'medium'}}&/span&
在这里我们把angular的ng-controller指令加入到HTML中,这意味着在&body&&/body&标签之间的所有内容均在控制器的作用域下。每次ng-controller检测,AngularJS为这个特殊的控制器和实例创造一个新的作用域。所以当ng-controller="GreetingController"遇到这个构造方法GreetingController运行的时候,为作用域下的两个模型:greeting和now赋值。在视图中我们可以通过表达式{{}}来获取值。当我们写{{greeting}}AngularJS会用这个已经存在greeting属性的值来替代它。对于now模型来说也是如此。无论在{{}}里写的是什么,都要能和scope对应上。当你在浏览器里运行HTML的时候你应该看到如下Hello, User! The current date/time is &current date & time here&.
添加逻辑到控制器
除了操作用户输入和为控制器赋值,一个控制器也为$scope添加方法,这些方法表现某种类型的逻辑并且和服务一起封装应用的业务数据。为了明白这些,让我们为$scope添加一个方法,并且在一个随机的语言中返回greeting的值。下面是如何定义控制器:
angular.module('myApp',[])
.controller('GreetingController',function($scope){
$scope.now=new Date();
$scope.helloMessages = ['Hello', 'Bonjour', 'Hola', 'Ciao',Hallo'];
$scope.greeting = $scope.helloMessages[0];
$scope.getRandomHelloMessage=function(){
$scope.greeting=$scope.helloMessages[parseInt(Math.random()*$scope.helloMessages.length)]
这里我们把helloMessages字符数组数组添加到$scope当前作用域上,表示hello五种语言的表达。我们也为当前作用域$scope上附加了getRandomHelloMessage()方法随机选择一个信息并且赋值给作用域下greeting模型。作为数据绑定的结果,当$scope.greeting被更新,在视图层表达式{{greeting}}也会被改变。相应的视图是
&!DOCTYPE html&
&html ng-app="myApp"&
&script type='text/javascript' src="/ajax/libs/angularjs/1.2.16/angular.js"&&/script&
&script src="app.js"&&/script&
&body ng-controller="GreetingController"&
{{greeting}} User! The current date/time is &span&{{now | date:'medium'}}&/span&
&button ng-click="getRandomHelloMessage()"&Random Hello Message&/button&
这里我们增加了一个HTML按钮用来通过调用在控制器的作用域上getRandomHelloMessage()方法来响应点击事件。反过来,这个方法改变greeting模型的值。所以这个变化被反射到视图层。诚然,这个例子是很简单的。但是我们学习到了基本的控制器和如何使用它,现在我们来讨论,控制器不该做什么。
不要进行DOM操作,DOM操作应该在指令中进行。
不要在控制器中格式化模型的值,过滤器的作用就在此,我们已经见过内置过滤器的操作。
不要在控制器中写重复的代码。而是要封装到服务中,例如你要在多个地方从服务中获取数据。所以你与其在控制器中重复代码,不如把代码封装在服务中,当需要的时候注入到控制器中。当所有的控制器线程处理完用户输入后,为当前作用域$scope设置属性和方法,并且和服务进行交互来表现业务逻辑。控制器应该做:+通过为控制器附加模型来设定$scope的初始状态,+为作用域附加方法用来处理任务。
为控制器附加实例方法和属性
尽管控制器经常为作用域设置方法和属性,你也可以为控制器创建一个实例方法和属性。记住,AngularJS实例化你的控制器通过调用你提供的构造方法。这意味着你有创建实例属性和实例方法的自由。让我们定义以前的控制器,用实例属性代替作用域模型。
angular.module('myApp',[])
.controller('GreetingController',function($scope){
this.now=new date();
this.helloMessage=['Hello', 'Bonjour', 'Ola', 'Ciao', 'Hallo'];
this.greeting = this.helloMessages[0];
this.getRandomHelloMessage = function() {this.greeting = this.helloMessages[parseInt((Math.random()*this.helloMessages.length))];
在视图层有一个小调整。
&!DOCTYPE html&
&html ng-app="myApp"&
&head&&br&
&script type='text/javascript'src="/ajax/libs/angularjs/1.2.16/angular.min.js"&&/script&
&script src="app.js"&&/script&
&body ng-controller="GreetingController as greetingController"&
{{greetingController.greeting}} User! The current date/time is&span&{{greetingController.now | date: 'medium'}}&/span&
&button ng-click="greetingController.getRandomHelloMessage()"&Random Hello Message&/button&
这个GreetingController作为控制器完成了变换。这个作为关键字给GreetingController的实例设置作用域。所以作为关键字把控制器暴露给视图层。并且作为结果,我们能够通过这个reference参数获取这个实例的变量和方法。不是所有的开发者,支持这个实现方法。让我们看看作为关键字什么是好的什么是坏的。
+在许多案例中暴露整个控制器实例不是一个好的方法。这个作用域对象清楚的存在于控制器和视图层之间。+这个实现方法不是主流的,并且也需要更多的代码。
+当控制器是嵌套的,并且内层和外层控制器作用域对于模型有相同的名字,作为关键字就派上用场了。
&div ng-controller='OuterController as outer'&
&div ng-controller='InnerController as inner'&
Outer={{outer.someModel}} and inner={{inner.someModel}}
在控制器中使用依赖注入
我们已经见过如何把依赖注入到控制器中,AngularJS从构造函数的参数的名字来推断控制器的依赖,当部署代码的时候压缩Javascipt代码,这个参数的名字将会缩短,结果AngularJS将会无法识别出依赖。这种情况,你有两个选择来理清依赖当压缩代码的时候,让我们来检查这两种方法。
function DemoController($rootScope,$scope,$http){
DemoController.$inject=['$rootScope','$scope','$http'];
angular.module('myApp',[]) .controller('DemoController',DemoController)
这个$inject属性表明控制器到构造函数参数之间的依赖关系。第二个选择在开发者中更受欢迎。你可以在angular.module()中第二个参数传递一个数组,而不是传递一个构造函数。这个数组存储依赖的名字,数组中的最后一项就是控制器构造函数的方法。这里我们如何做。
angular.module('myApp',[])
.controller('DemoController',['$rootScope','$scope','$htp',function($rootScope,$scope,$http){
你会喜欢上面简单的代码,注入到构造函数中依赖关系是在数组中按顺序声明的。这些方法达到同样的目的,第一种实现方法代码有点长,相比第二种在行内并且代码要短。第二种实现方法的情况下,参数列表紧密的在一起。结果,如果你需要改变什么,你需要一个单独的位置来编辑。你可以在两行把参数列表对齐,这样你就可以看到他们是不是相互匹配。
双向数据绑定方法概述
现在你已经明白了模块和控制器,是时候往前走一点,介绍AngularJS最基本的特性之一双向数据绑定。尽管本节,给了一个双向数据绑定的概述,重要的是在第三章会讲述的。
什么是数据绑定
数据绑定是在视图层和模型层自动同步数据。当我们说双向的时候,那意味着同步方式两种都进行。在angular中我们创建模型并且为其赋予scope对象。然后我们把UI组件和这些模型绑定在一起。这就建立了双向绑定在视图层组件和模型数据之间。当这个模型数值变化的时候,这个视图层来更新自己反应变化。另一方面,当视图层变化的时候,模型层也会更新自己。如果你有Flex或者Actionscript背景,可能你已经每天使用这种绑定方式,但是这种特性在HTML中还是全新的,让我们把模型层的数据当作数据的唯一来源,反过来,把你从操作DOM结构中解放出来。AngularJS保证你的视图层总是和最新的模型层数据保持更新。所以你仅仅改变这个数据,你的视图层会自动更新没有副作用的。换种说法,没必要使用innerHTML。但是如果你愿意,你仍然能够手动的更新DOM。
AngularJS中的双向绑定
接下来的例子,简单的介绍了双向绑定。简单起见,绕过控制器,集中到控制器
如果你运行上面的代码,你会看到当input输入框中的值发生改变的时候,这个div中的值,也会发生变化。让我们一步一步看看发生了什么:1、第一,这个ng-app指令启动这个应用。我们没有模块,所以我们仅仅写下ng-app没有指定值。有一点需要注意的是,当AngularJS遇到ng-app,它会给HTML产生rootscope根作用域。这个作用域scope是我们存储模型数据的地方这样才能被视图层获取。2、接下来,ng-init指令产生了一个名为name的模型,把它保存在rootscope根作用域。这个模型被初始化为AngularJS的值。3、我们已经把ng-modal指令附加到input元素上。这是基本的双向数据绑定。当你在input输入框输入东西的时候,这个作用域自动更新。所以,这样你就不必要手动为input输入框写一个keyup事件句柄来更新数据。4、 {{name}}被绑定在模型层数据上作为一个表达式被视图层单向知道。这就是双向数据绑定的第二个方面,表达式监听着作用域模型值,并且更新这个DOM,当值变化的时候。5、所以当我们在input输入框输入内容的时候,作用域模型层数据发生变化,在视图层中的表达式{{name}},当name属性发生变化会自动更新。
做一些酷酷的事情
当事情变得复杂一点点时候数据绑定的能力就变得明显了。所以,让我们干点酷酷的事情,比如说,你已经被赋予了以下的任务。1.为使用者提供一个input输入框用来输入facebook账号。2.只要使用者输入完成,显示相应的facebook资料图片。唯一的约束是让代码的数量行数最小化,这个第一个选择是使用纯javascript代码(或者使用jquery)。所以,打开你的编辑器,写下这些代码为了这些功能,然后回到这里。但是我给你一个线索,这个接下来的URL,返回一个facebook的资料图片。Facebook ID:
here]/picture?type=normal.纯javascript代码。
&!doctype html&
&html lang="en"&
&meta charset="utf-8"&
&title&The Plain JS Way&/title&
&script type="text/javascript"&
document.addEventListener('DOMContentLoaded',function(e){
doucument.getElementById('fbID').addEventListener('keyup',function(event){
var fbID=document.getElementById('fbID').
var pictureURL='/' + fbID +'/picture?type=normal';
document.getElementById('profilePic').src=pictureURL;
&input type="text" id="fbID" /&
&span&&img src="" title="fb image" id="profilePic"/&&/span&
在AngularJS中使用双向绑定
&!doctype html&
&html lang="en" ng-app&
&meta charset="utf-8"&
&title&Two way data binding&/title&
&body ng-init="fbID='sandeep.panda92'"&
&input type="text" ng-modal="fbID"&&br/&
&span&![](/{{fbID}}picture?type=normal)&/span&
&script src="lib/angular/angular.js"&&/script&
当你看到angularJS的版本的时候会有一点混乱。那这是如何造成的。数据双向绑定的威力刚刚显示出来。我们先来了解场景背后的内容:1.当我们在input输入的时候,多亏了AngularJS这个fbID模型就会更新。2.当我们在视图中使用表达式{{fbID}},当fbID模型的值会更新它自己,这样会显示出新图标。关于angularJS实现最重要的事情是代码的规模是极具下降。甚至实现这个编码功能不使用javascript。这就是声明绑定的能力。你可以通过在视图层通过作用域绑定不同的UI组件来实现一个特殊的场景。如果你的视图层一些需要更新,你需要更新模型层。它确保你表达什么数据被更新了对于一个特殊的组件。如果其他的开发者看到了你的代码,可以推断接下来的应用。总结一下,双向绑定的特性:1、在视图层和模型层两个方向,同步的保持数据更新。2、提供一个强大的陈述性语法来展现应用来实现什么功能。3、把我们从繁琐的不能测试的DOM操作中解放出来。4、显著的缩减代码规模。
介绍我们的应用DEMO
学习一个新技术而不练习,这不是忍者的方式。我要确信你每一章节正确的学习方式。练习能够给你在现实世界中操作大型angular项目的信心。所以我们要创建一个示例应用,并且加入一些我们计划的特性。让我们看一看这个简单的示例应用。
一个简单的单页面博客
我们将要为所有的单页面爱好者开发一个单页面应用解决方案。想象一下你有一个高能力的博客系统,由单页面系统控制加载。下面是一个关于这个应用的概括:(1)博客的首页展现所有的博客帖子,包括帖子的名称、作者和发布时间。(2)当一个单页面博客被点击的时候,这个实际的条目内容会被异步的加载进这个页面。不会触发整个页面重载。(3)有一个管理员页面,管理员可以进行增删改查操作。所以,这是这个app基础的特性。随着我们往前看书,我们将会往app中添加一些激动人心的功能,比如一个完整的评论系统、认证和授权、facebook或者twitter登录界面。
刚开始,我们要以在前面章节中讨论过的特征模块化我们的代码。我们已经说过如何把AngularJS。seed项目分解成介绍的这种模式。我们需要做的是从AngularJS seed项目中分解出新项目,并且创造一个简略的不同项目结构。只有这个app下的目录结构需要修改,其他的就不变了。我已经分享过这个目录结构,你可以直接下载后开始。快速的看一下图2.1的屏幕截图,来看看什么改变了。
我已经为这个app增加了一个叫做post的模块,现在还没有添加内容。当你下载完这个zip文件,你可以查看这个文件结构和检查模块。我们将在第五章重新开始开发这个app。
每一个忍者水平的开发者喜欢更强大的能力。现在你得到了三个而不是一个能力,模块、控制器、数据绑定。我想他们会帮助你完成这个angularJS旅程。接下来会带给你一些新的内容。但是同时你可以尝试一些双向绑定的技巧,给你的同事留下印象。
前端路上。。。。

我要回帖

更多关于 怎么进行单元测试 的文章

 

随机推荐