发表于: 2017-06-01 21:11:22
1 1106
今天完成的事情:
控制器和作用域关系。从整个HTML页面创建一个ng-controller到,多个HTML标签复用同一个ng-controller,到使用控制器继承以及会出现的一些问题。最后创建多个独立的控制器。
明天计划的事情:
找翻页插件
遇到的问题:
暂无
收获:
表单
使用控制器和作用域
介绍控制器和作用域之间的关系并演示如何将两者结合使用以达到最佳效果。作用域组成了一个能够用于在控制器之间形成通信的体系结构。还有如何创建不需要使用作用域的控制器。以及如何使用作用域将AngularJS与其他JavaScript框架相集成。
工厂函数能够使用依赖注入特性来声明对AngularJS服务的依赖。几乎每个控制器都需要使用$scope服务,用于向视图提供作用域,定义可被视图使用的数据和逻辑。
tip:严格来说,$scope并不是一个服务,而不是由一个叫做$rootScope的服务所提供的一个对象,这一点将会在后面介绍。在实际应用上,$scope的使用与服务机器相像,所以简单起见将其称为一个服务。
你不仅需要创建控制器,还需要区别控制器所支持的视图,这一过程是通过ng-controller指令来完成的,该指令所指定的值必须与创建的控制器同名,在上面例子中即是simpleCtrl。在AngularJS的惯例中,经常使用后缀Ctrl来命名控制器,但并不是必须的。这时所定义的控制器还没有做任何事情,还没有向视图提供数据或逻辑。
设置作用域
正如上面例子那样,当控制器声明了对与$scope服务的依赖时,就可以使得控制器通过其对应的作用域向视图提供各种能力。作用域不仅定义了控制器和视图之间的关系,而且对许多最重要的AngularJS特性提供了运转机制,比如数据绑定。
有两种方法通过控制器使用作用域。可以定义数据,也可以定义行为,也就是可以在视图的绑定表达式或指令中调用的JavaScript函数。
创建数据和建立行为是简单的。只需在$scope对象上创建属性变量,就可传递给控制器的工厂函数,然后将数据值或函数赋给他们
控制器的作用域中定义了一个名为city的变量,并将一个字符串赋给它,同时定义了一个名为getCountry的行为,这是一个简单的函数,能够接受city作为参数并根据city的值返回country。然后通过数据绑定来使用这个变量值以及这个行为。可以通过变量名直接访问任何数据变量,以及通过方法名调用任何行为,就像调用常规的JavaScript函数一样。
向控制器行为中传递参数
在上面的例子中getCountry行为,能够接受city作为参数,然后经过处理后生产相关联的country。这么做也许会令你觉得有点奇怪因为在数据绑定中是这样调用该行为的:
这里传递了作用域里的city属性值作为参数给该行为,而该行为本身也是作用域的一部分,于是可以这样重写该行为:
在这个行为的实现代码里,移除了参数,该为你直接从作用域中获取city属性,于是可以让数据绑定得到简化,类似这样:
在例子中那么写的原因有两个,一是因为这样意味着我的行为能够被任何city值所用,而不是仅仅能够被同一个作用域里定义的那个city值所用。这在涉及控制器继承时尤为有用,另一个原因是因为接受参数能够使单元测试变得更为简单一些,因为这样该行为就是自包含的。(这在介绍AngularJS对单元测试的支持。对控制器行为使用参数并不是必需的,而且如果你不使用参数时也没有什么可怕的,并且也推荐你使用,一种习惯罢了)
修改作用域
关于作用域最重要的一点是修改会传播下去,自动更新所有相依赖的数据值,即使是通过行为产生的。下面演示一个使用ng-model指令的修改是如何引起其数据绑定被更新的。
在本例中增添了一个城市名字构成的数组,并且在select元素的ng-options属性中使用该数组来生成一组选项元素。ng-moedl指令指定了作用域中的city模型属性将会在用户从select元素中个选择一个值时被更新。
这里注意修改了ng-controller指令所作用的位置,比比啊能够使他同时包含select元素和那些数据绑定。每一个控制器实例都会有自己独有的作用域,通过确保将所有的指令和绑定放在同一个视图中(也就是说要在ng-controller指令所应用到的元素的所有子元素中使用),能够确保工作在同一个数据集上。这里的"每一个控制器实例对应一个作用域"的概念很重要!!
添加了select元素后的效果就像在前面的例子中所期望的那样:当从select元素中选择一个值时,就会引起数据绑定中所使用的值被更新。
值得特别注意的是,不只是显示所选city名称的数据绑定被改变了。显示控制器行为调用结果的数据绑定也被更新了。这是AngularJS的突出优点之一。
组织控制器
在上面的例子中,使用了一个但酷爱的控制器,能够应用于支持body元素的所有内容。对于程序这是完全合理的,但当项目的复杂性增长时将会变得越来越笨拙不便,而且将会使得某些功能的使用变得困难,比如局部视图(例如ng-include)。在程序组织控制器有许多种不同的方法。
(tip:对于某个程序来说也许不易确定使用哪种组织控制器的途径,所以此处提供了关于每种方法的一般介绍。当时,介绍了这么多的AngularKS开发知识后,建议你从本书的推荐方法开始,然后一一尝试每种方法以便找到最适合你和你的项目的方法。)
使用一个单块控制器
第一种途径是在上面的例子中使用的那种:通过在body元素上(或者至少在哪些包含了所有数据绑定的指令的元素上)使用ng-controller指令,使用一个应用于程序中所有HTML元素的控制器。
这种方法有一些优点:简单,无需担心各个作用域之间的通信问题,而且行为将可以被整个HTML所用。当你使用一个单块控制器时,实际上你会对整个应用程序创建一个单独的视图如:
这种方法也有缺点:对于简答程序来说不错-比如我们用来掩饰AngularJS功能的哪些小例子-但是当为了交付程序功能而不断增添所需行为时,最终你会毫无疑问地得到一大堆代码。这将会使得项目变得难以维护而且使测试更加复杂。并且这也和AnguJS的设计哲学相违背,即应该构建一堆小而内聚的积木式的模块,当这只是一种风格问题,而不是技术上必须的。在下面例子中,可以看到一个使用单块控制器和单块视图的例子,用于保存简单的运输和账单信息。在本例中对于每个地址只包含了一个数据字段,因为重点是在控制器和视图的关系上。
在本例中控制器定义了一个名为address的对象,用于收集所输入的邮编(zip),以及setAddress和copyAddress行为(真的么?我怎么觉得删了也没事)。setAddress行为将会打印出一个邮编,而copeAddress将会把一个隐式定义的邮编变量拷贝到另一个上。通过在HTML袁术上应用标准AngularJS指令和模型绑定,数据和行为就这样被联系起来了。
可以在input元素中直接输入邮编,在单击"Use Billing"按钮后可以将billingZip code拷贝到shipping input元素中去。像这样拷贝数据是非常容易的,因为我们只有一个组用于,所有数据是可以直接使用的。
当你对于AngularJS刚刚入门时,或者在创建一个简单的应用程序时,或是当开始并没有很清晰的设计思路时,这是一种可以用于起步的控制器组织方式。你可以很快起步并上手,在不断推进时也可以采用所介绍的其他组织方式之一。
复用控制器
你可以在同一个应用程序中创建多个视图并复用同一个控制器。AngularJS将会调用每个应用到控制器的工厂函数,结果是每个控制器实例将会拥有自己的控制器。这看起来也许有点奇怪,但是这种方法能够简化控制器,因为所需管理的只是在单块控制器下需要处理的数据值的一个子集。这样能够工作的原因是MVC模式下能够分离职能,意味着不同的视图能够以不同的方式对同一份数据和功能进行展示。下面例子可以看到是如何修改这个例子以便简化控制器并让它能够和两个不同的视图一起工作的。
在本例中,从body元素上移除了ng-controller指令,取而代之的是将其应用于内容中两个相同的区域,每一个都是支持simpleCtrl控制器的。这样的效果是创建了两个控制器和两个识图。AngularJS对每个视图都调用控制器的工厂函数,结果是给每个视图赋予自己的作用域。
在这个应用中,每个控制器向其作用域提供的数据和行为都是与另外一个控制器相互独立的,这样可以允许我们简化控制器和视图。每个控制器只需关心收集单独一个邮编,这样可以允许我们简化代码(虽然这已经是一个很简单的例子,在实际的应用程序中将会更容易看到效果)。
作用于之间的通信
这方法的负面作用是copyAddress从未再也不起作用了,因为每个右边都是存储在一个叫做zip的变量中的,而这一变量被存放在不同的作用域中。幸运的是,AngularJS提供用于在作用域之间共享数据的机制。然而,首先必须承认的是上图中存在更简洁的方法来表达作用域的工作方式。
作用域实际上是以层级结构的形式组织起来的,顶层是根作用域(root scope)。每个控制器都会被赋予一个新的作用域,该作用域是根作用域的一个子作用域,如下图所示:
根作用域提供了在各个作用域之间发送事件的方法,这暗示着允许在各个控制器之间进行通信。
根作用域可以作为一个服务被使用,所以在控制器中使用$rootScope名称声明了对他的依赖(这是AngularJS的内建服务之一)。所以的作用域,包括$rootScope服务,定义了若干可用于发送和接受事件的方法。
$broadcast和$emit事件都是具有方向性的,他们沿着作用域的层级结构向上发送事件直到根作用域,或者向下发送直至每一个子作用域。现在看起来这稍微有点过度,但是你将会看到,不同的控制器组织方式将会产生更加复杂的作用域层级结构。
在本例中,在当前作用域中调用了$on方法,从来对ZipCodeUpdated事件创建一个处理函数。这个事件处理函数接受一个event对象以及一个参数对象-本例中对改参数对象定义了type和ZipCode属性,然后使用它们在本地作用域上定义一个属性类似如下
tip:这里使用了数组风格的记号在$scope对象上定义属性。$scope属性的名称被设置为功函数参数args.type属性中取得的值。将args.type放在[和]字符之间将会促使args.type属性被计算值,就算所得的值被用作作用域属性的名称。
使用这个事件来保持两个作用域都得到同步,这样每个作用域都可以拥有用户输入的邮编值。另一方面通过在$rootScope对象上调用$broadcast方法来实现同步,传入一个拥有type和zipCode属性的对象,这个对象正是事件处理函数所期望得到的。
向$scope.zip设置值将会更新与该属性通过ng-model指令进行绑定的输入框元素
使用服务调解作用域时间
AngularJS中的习惯是使用服务来调解作用域之间的通信。
这样的惯用法不会对本例造成多大影响,因为这里只是用了一个单独的控制器,但是如果有多个需要发送同一类事件的控制器时,该方法可以减少重复。下面例子可以看到如何使用module.service方法创建一个服务对象的,该服务可被控制器用来发送和接受事件,而无需直接与作用域中的时间方法产生交互。
ZipCodes服务中声明了对$rootScope服务的依赖,并在setZipCode方法种是用来调用$broadcast事件。控制器中声明了对ZipCodes服务的依赖,并调用setZipCode方法,而不是直接在$rootScope上进行操作。在功能上并无变化-这种惯用法将可能被不同的控制器所需使用的代码放到同一个地方,达到了减少重复的目的。
评论