AngularJS控制器

Controller控制器

控制器的定义

控制器的作用是通过附加模型和和方法来为其扩大作用域,为了随后在视图层能够访问的到。AngularJS中的控制器就是在html文件中AngularJS程序遇到ng-controller指令的时候生成的构造函数。我们将会在下一章中探索AngularJS的作用域。正如我在第一章中提到的,作用域scope就是控制器和视图层之间的胶水,所以控制器能够添加属性,视图层能够访问到这些属性。

为控制器附加属性和方法

下一步,我们写一个控制器,用来通过一个作用域传递现在的时间到视图层。

controller

<pre><code>
angular.module('myAPP',[])
.controller('GreetingController',function($scope){
$scope.now=new Data();//把时间模型附加到作用域上
$scope.greeting='Hello';
})
</code></pre>
通过在angular.module()上调用controller()来注册一个控制器。这个控制器需要两个参数:第一个参数是这个控制器的名字,第二个是当在html中执行到ng-controller指令时被调用的实例方法。能够忽略这个实例方法中的作用域$scope参数吗?当你声明这个$scope作为一个控制器实例的参数,你就表明你的控制器是依赖于这个$scope对象的。这个$scope的参数有特殊意义。AngularJS根据实例方法参数的名字来推断这个控制器的依赖。在这个例子中,当AngularJS在HTML文件中找到ng-controller='GreetingController'这个指令时,AngularJs会为控制器创建一个新的作用域,当实例化这个方法时,AngularJS会传递这个作用域作为一个参数到这个实例方法。这叫做依赖注入,是AngularJS的核心。我们将会在后面的章节中讨论依赖注入的话题。

多个依赖

能够为控制器设置多个依赖关系,并且能够声明这些依赖作为参数传递到构造方法中。为了增加$scope,每个AngularJS应用都有一个一个$rootScope。我们假设你有一个普通的服务用来通过XHR连接后端。你可以声明这两个服务依赖,像如下一样:
<pre><code>
angular.module('myApp',[])
.controller(ControllerWithDependency',function($rootScope,customService){
//在这里使用依赖
});
</code></pre>
当实例化控制器的时候,AngularJS会读取参数列表,并且从这些名字中可以指定哪些需要被依赖注入。你也需要注意到AngularJS自己自带的服务前缀是以$为命名约定的。所以你不应该以$作为你服务的前缀。

<!DOCTYPE html> 
<html ng-app="myApp">
<head >
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js" ></script><
<script src="app.js"></script>
</head>
<body ng-controller="GreetingController">
{{greeting}} User! The current date/time is <span>{{now | date:'medium'}}</span>
</body>
</html>

在这里我们把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">
<head>
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="GreetingController">
{{greeting}} User! The current date/time is <span>{{now | date:'medium'}}</span>
<br/>
<button ng-click="getRandomHelloMessage()">Random Hello Message</button>
</body>
</html>

这里我们增加了一个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="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
<script src="app.js">>/script>
</head>
<body ng-controller="GreetingController as greetingController">
{{greetingController.greeting}} User! The current date/time is<span>{{greetingController.now | date: 'medium'}}</span>
<br/>
<button ng-click="greetingController.getRandomHelloMessage()">Random Hello Message</button>
</body >
</html >

这个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: https://graph.facebook.com/[id here]/picture?type=normal.

纯javascript代码。

<!doctype html>
<html lang="en">
<head>
<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').value;
      var pictureURL='https://graph.facebook.com/' + fbID +'/picture?type=normal';
      document.getElementById('profilePic').src=pictureURL;
    })
  })
</script>
</head>
<body>
<input type="text" id="fbID" />
<br/>
<span><img src="" title="fb image" id="profilePic"/></span>
</body>
</html>

在AngularJS中使用双向绑定

<!doctype html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>Two way data binding</title>
</head>
<body ng-init="fbID='sandeep.panda92'">
  <input type="text" ng-modal="fbID"><br/>
  <span>![](https://graph.facebook.com/{{fbID}}picture?type=normal)</span>
  <script src="lib/angular/angular.js"></script>
</body>
</html>

当你看到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旅程。接下来会带给你一些新的内容。但是同时你可以尝试一些双向绑定的技巧,给你的同事留下印象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容