AngularJs Understanding the Controller Component
在angular中,controller是一个javascript函数(type/class),被用作扩展除了rootscope在外的angularscope(https://www.nhooo.com/article/91749.htm)的实例。当我们或者angular通过scope.$newAPI(http://docs.angularjs.org/api/ng.$rootScope.Scope#$new)创建新的childscope时,有一个选项作为方法的参数传入controller(这里没看明白,只知道controller的第一个参数是一个新创建的scope,有绑定parentscope)。这将告诉angular需要联合controller和新的scope,并且扩展它的行为。
controller可以用作:
1.设置scope对象的初始状态。
2.增加行为到scope中。
一、Settinguptheinitialstateofascopeobject(设置scope对象的初始状态)
通常,当我们创建应用的时候,我们需要为angularscope设置初始化状态。
angular将一个新的scope对象应用到controller构造函数(估计是作为参数传进去的意思),建立了初始的scope状态。这意味着angular从不创建controller类型实例(即不对controller的构造函数使用new操作符)。构造函数一直都应用于存在的scope对象。
我们通过创建model属性,建立了scope的初始状态。例如:
functionGreetingCtrl($scope){$scope.greeting=“Hola!”;}
“GreetingCtrl”这个controller创建了一个叫“greeting”的,可以被应用到模版中的model。
二、AddingBehaviortoaScopeObject(在scopeobject中增加行为)
在angularscope对象上的行为,是以scope方法属性的形式,供模版、视图使用。这行为(behavior)可以修改应用的model。
正如指引的model章节(https://www.nhooo.com/article/91777.htm)讨论的那样,任意对象(或者原始的类型)赋值到scope中,成为了model属性。任何附加到scope中的function,对于模版视图来说都是可用的,可以通过angularexpression调用,也可以通过ngeventhandlerdirective调用(如ngClick)。
三、UsingControllersCorrectly
一般而言,controller不应该尝试做太多的事情。它应该仅仅包含单个视图所需要的业务逻辑(还有点没转过弯了,一直认为Controller就是个做转发的……)。
保持Controller的简单性,常见办法是抽出那些不属于controller的工作到service中,在controller通过依赖注入来使用这些service。这些东西会在向导的DependencyInjectionServices章节中讨论。
不要在Controller中做以下的事情:
- 任何类型的DOM操作-controller应该仅仅包含业务逻辑。DOM操作,即应用的表现逻辑,它的测试难度是众所周知的。将任何表现逻辑放到controller中,大大地影响了应用逻辑的可测试性。angular为了自动操作(更新)DOM,提供的数据绑定(http://docs.angularjs.org/guide/dev_guide.templates.databinding)。如果我们希望执行我们自定义的DOM操作,可以把表现逻辑抽取到directive(https://www.nhooo.com/article/91739.htm)中。
- Inputformatting(输入格式化)-使用angularformcontrols(https://www.nhooo.com/article/91744.htm)代替。
- Outputfiltering(输出格式化过滤)-使用angularfilters代替。
- 执行无状态或有状态的、controller共享的代码-使用angularservices代替。
- 实例化或者管理其他组件的生命周期(例如创建一个服务实例)。
四、AssociatingControllerswithAngularScopeObjects
我们可以显式地通过scope.$new关联controller和scope对象,或者隐式地通过ngControllerdirective(http://docs.angularjs.org/api/ng.directive:ngController)或者$routeservice(http://docs.angularjs.org/api/ng.$route)。
1.Controller构造函数和方法的Example
为了说明controller组件是如何在angular中工作的,让我们使用以下组件创建一个小应用:
- 一个有两个按钮和一个简单消息的template。
- 一个由名为”spice”的字符串属性组成的model。
- 一个有两个设置spice属性的方法的controller。
在我们的模版里面的消息,包含一个到spicemodel的绑定,默认设置为”very”。根据被单击按钮,将spicemodel的值设置为”chili”或者”jalapeño”,消息会被数据绑定自动更新。
<!DOCTYPEhtml> <htmlng-app> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/> <title>spicy-controller</title> <metacontent="IE=edge,chrome=1"http-equiv="X-UA-Compatible"> <styletype="text/css"> .ng-cloak{ display:none; } </style> </head> <bodyclass="ng-cloak"> <divng-controller="SpicyCtrl"> <buttonng-click="chiliSpicy()">Chili</button> <buttonng-click="jalapenoSpicy('jalapeño')">Jalapeño</button> <p>Thefoodis{{spice}}spicy!</p> </div> <scriptsrc="../angular-1.0.1.js"type="text/javascript"></script> <scripttype="text/javascript"> functionSpicyCtrl($scope){ $scope.spice="very"; $scope.chiliSpicy=function(){ $scope.spice="chili"; }; $scope.jalapenoSpicy=function(val){ this.spice=val; }; } </script> </body> </html>
在上面例子中需要注意的东东:
- ngControllerdirective被用作为我们的模版(隐式)创建scope,那个scope会称为SpicyCtrl的参数。
- SpicyCtrl只是一个普通的javascriptfunction。作为一个(随意)的命名规则,名称以大写字母开头,并以”Ctrl”或者”Controller”结尾。
- 对属性赋值可以创建或者更新$scope的model。
- controller方法可以通过直接分配到$scope实现创建。(chiliSpicy方法)
- controller的两个方法在template中都是可用的(在ng-controller属性所在的元素以及其子元素中都有效)。
- 注意:之前版本的angular(1.0RC之前)允许我们使用this来代替$scope定义$scope的方法,但这里不再适用。在定义在scope上的方法中,this跟$scope是等价的(angular将this至为scope),但不是在我们的controller构造函数中。
- 注意:之前版本的angular(1.0RC之前),会自动增加controller的prototype方法到scope中,但现在不会了。所有方法都需要人工加入到scope中。(印象中之前有一个guide,有用过这个。还没更新-_-!)
controller方法可以带参数的,正如下面例子所示:
<!DOCTYPEhtml> <htmlng-app> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/> <title>controller-method-aruments</title> <metacontent="IE=edge,chrome=1"http-equiv="X-UA-Compatible"> <styletype="text/css"> .ng-cloak{ display:none; } </style> </head> <bodyclass="ng-cloak"> <divng-controller="SpicyCtrl"> <inputng-model="customSpice"value="wasabi"/> <buttonng-click="spicy(customSpice)">customSpice</button> <br/> <buttonng-click="spicy('Chili')">Chili</button> <p>Thefoodis{{spice}}spicy!</p> </div> <scriptsrc="../angular-1.0.1.js"type="text/javascript"></script> <scripttype="text/javascript"> functionSpicyCtrl($scope){ $scope.spice="very"; $scope.spicy=function(spice){ $scope.spice=spice; }; } </script> </body> </html>
注意那个SpicyCtrlcontroller现在只定义了一个有一个参数”spice”、叫”spicy”的方法。template可以引用controller方法并为它传递常量字符串或model值。
Controller继承在angular是基于scope继承的。让我们看看下面的例子:
<!DOCTYPEhtml> <htmlng-app> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/> <title>controller-inheritance</title> <metacontent="IE=edge,chrome=1"http-equiv="X-UA-Compatible"> <styletype="text/css"> .ng-cloak{ display:none; } </style> </head> <bodyclass="ng-cloak"> <divng-controller="MainCtrl"> <p>Good{{timeOfDay}},{{name}}!</p> <divng-controller="ChildCtrl"> <p>Good{{timeOfDay}},{{name}}!</p> <png-controller="BabyCtrl">Good{{timeOfDay}},{{name}}!</p> </div> </div> <scriptsrc="../angular-1.0.1.js"type="text/javascript"></script> <scripttype="text/javascript"> functionMainCtrl($scope){ $scope.timeOfDay='Main时间'; $scope.name='Main名称'; } functionChildCtrl($scope){ $scope.name='Child名称'; } functionBabyCtrl($scope){ $scope.timeOfDay='Baby时间'; $scope.name='Baby名称'; } </script> </body> </html>
注意我们如何嵌套3个ngControllerdirective到模版中的。为了我们的视图,这模版结构将会导致4个scope被创建:
- rootscope。
- MainCtrlscope,包含timeOfDay和namemodel。
- ChildCtrlscope,覆盖了MainCtrlscope的namemodel,继承了timeOfDaymodel。
- BabyCtrlscope,覆盖了MainCtrlscope的timeOfDay以及ChildCtrlscope的name。
继承的工作,在controller和model中是一样的。所以我们前一个例子中,所有model可以通过controller被重写。
注意:在两个Controller之间标准原型继承不是如我们所想地那样工作的,因为正如我们之前提到的,controller不是通过angular直接初始化的,但相反地,apply了那个scope对象。(controllersarenotinstantiateddirectlybyangular,butratherareappliedtothescopeobject,这里跟之前一样,我还是没理解。)
五、TestingController
虽然有很多方法去测试controller,最好的公约之一,如下面所示,需要注入$rootScope和$controller。(测试需要配合jasmine.js)
<!DOCTYPEhtml> <htmlng-app> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/> <title>controller-test</title> <metacontent="IE=edge,chrome=1"http-equiv="X-UA-Compatible"> <linkrel="stylesheet"href="../jasmine.css"> <styletype="text/css"> .ng-cloak{ display:none; } </style> </head> <bodyclass="ng-cloak"> <scriptsrc="../angular-1.0.1.js"type="text/javascript"></script> <scriptsrc="../angular-scenario-1.0.1.js"type="text/javascript"></script> <scriptsrc="../jasmine.js"type="text/javascript"></script> <scriptsrc="../jasmine-html.js"type="text/javascript"></script> <scriptsrc="../angular-mocks-1.0.1.js"type="text/javascript"></script> <scripttype="text/javascript"> functionMyController($scope){ $scope.spices=[ {"name":"pasilla","spiciness":"mild"}, {"name":"jalapeno","spiceiness":"hothothot!"}, {"name":"habanero","spiceness":"LAVAHOT!!"} ]; $scope.spice="habanero"; } describe("MyControllerfunction",function(){ describe("MyController",function(){ varscope; beforeEach(inject(function($rootScope,$controller){ scope=$rootScope.$new(); varctrl=$controller(MyController,{$scope:scope}); })); it('shouldcreate"cpices"modelwith3spices',function(){ expect(scope.spices.length).toBe(3); }); it('shouldsetthedefaultvalueofspice',function(){ expect(scope.spice).toBe("habanero"); }); }); }); (function(){ varjasmineEnv=jasmine.getEnv(); jasmineEnv.updateInterval=1000; vartrivialReporter=newjasmine.TrivialReporter(); jasmineEnv.addReporter(trivialReporter); jasmineEnv.specFilter=function(spec){ returntrivialReporter.specFilter(spec); }; varcurrentWindowOnload=window.onload; window.onload=function(){ if(currentWindowOnload){ currentWindowOnload(); } execJasmine(); }; functionexecJasmine(){ jasmineEnv.execute(); } })(); </script> </body> </html>
如果我们需要测试嵌套的controller,我们需要在test中创建与DOM里面相同的scope继承关系。
<!DOCTYPEhtml> <htmlng-app> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/> <title>controller-test</title> <metacontent="IE=edge,chrome=1"http-equiv="X-UA-Compatible"> <linkrel="stylesheet"href="../jasmine.css"> <styletype="text/css"> .ng-cloak{ display:none; } </style> </head> <bodyclass="ng-cloak"> <scriptsrc="../angular-1.0.1.js"type="text/javascript"></script> <scriptsrc="../angular-scenario-1.0.1.js"type="text/javascript"></script> <scriptsrc="../jasmine.js"type="text/javascript"></script> <scriptsrc="../jasmine-html.js"type="text/javascript"></script> <scriptsrc="../angular-mocks-1.0.1.js"type="text/javascript"></script> <scripttype="text/javascript"> functionMainCtrl($scope){ $scope.timeOfDay='Main时间'; $scope.name='Main名称'; } functionChildCtrl($scope){ $scope.name='Child名称'; } functionBabyCtrl($scope){ $scope.timeOfDay='Baby时间'; $scope.name='Baby名称'; } describe("MyController",function(){ varmainScope,childScope,babyScope; beforeEach(inject(function($rootScope,$controller){ mainScope=$rootScope.$new(); varmainCtrl=$controller(MainCtrl,{$scope:mainScope}); childScope=mainScope.$new(); varchildCtrl=$controller(ChildCtrl,{$scope:childScope}); babyScope=childScope.$new(); varbabyCtrl=$controller(BabyCtrl,{$scope:babyScope}); })); it('shouldhaveoverandselected',function(){ expect(mainScope.timeOfDay).toBe("Main时间"); expect(mainScope.name).toBe("Main名称"); expect(childScope.timeOfDay).toBe("Main时间"); expect(childScope.name).toBe("Child名称"); expect(babyScope.timeOfDay).toBe("Baby时间"); expect(babyScope.name).toBe("Baby名称"); }); }); (function(){ varjasmineEnv=jasmine.getEnv(); jasmineEnv.updateInterval=1000; vartrivialReporter=newjasmine.TrivialReporter(); jasmineEnv.addReporter(trivialReporter); jasmineEnv.specFilter=function(spec){ returntrivialReporter.specFilter(spec); }; varcurrentWindowOnload=window.onload; window.onload=function(){ if(currentWindowOnload){ currentWindowOnload(); } execJasmine(); }; functionexecJasmine(){ jasmineEnv.execute(); } })(); </script> </body> </html>
以上就是关于AngularJsUnderstandingtheControllerComponent的资料整理,后续继续补充相关资料,谢谢大家的支持!