Angular 教程 - 第 2 部分: 理解模块和控制器






4.80/5 (46投票s)
理解和实现Angular.js中的模块和控制器
引言
在本文中,我们将尝试理解和实现Angular.js中的模块和控制器。我们将从Angular模块开始,了解它为何以及如何有用。然后,我们将为测试应用程序定义一个简单的Angular模块。接下来,我们将研究Angular中控制器的概念,并尝试实现一个简单的控制器来理解这些概念。我们还将研究一个名为IIFE(立即调用的函数表达式)的JavaScript设计模式,因为它是在创建Angular应用程序时避免全局变量的一个非常有用的模式。
完整系列链接
- AngularJS 教程 - 第一部分:AngularJS 简介
- Angular 教程 -
第 2 部分: 理解模块和控制器 - AngularJS 教程 - 第三部分:理解和使用指令
- Angular 教程 -
第 4 部分: 理解和实现过滤器 - Angular 教程 - 第 5 部分:
理解和实现服务 - Angular 教程 -
第 6 部分: 构建和验证数据录入表单 - Angular 教程 -
第 7 部分: 理解单页应用程序和 Angular 路由
背景
在本系列的前一篇文章中,我们讨论了Angular.js为何如此流行,以及JavaScript框架如何帮助以结构化的方式创建客户端单页应用程序。我们还看了一个小示例,其中我们尝试在Angular.js中编写“Hello World
”示例。我们创建了一个简单的模块、控制器,并在视图中放置了一些指令来创建我们的示例。在本文中,我们将深入探讨Angular模块和控制器的细节,而在下一篇文章中,我们将详细讨论指令的重要性。
Angular.Js中的模块
让我们从Angular模块开始讨论。Angular中的模块可以被认为是应用程序其他所有部分(如控制器、服务和指令)的容器。在软件开发中,我们一直使用“模块”这个词来表示应用程序的逻辑部分,它可以独立于其他模块进行开发,并且可以被其他模块使用/重用。Angular完全采纳了这个概念用于Angular模块。如果我们能想到一个包含紧密耦合组件(控制器、服务、指令和过滤器)的逻辑功能块,那么我们就可以将其打包成一个Angular模块。
对于小型应用程序,一个模块可能就足够了。在这种情况下,模块可以被认为是定义我们应用程序的东西。对于大型应用程序,我们可能需要创建多个模块。模块最好的地方在于,一个模块可以轻松地传递给另一个模块,并且其功能可以被其他模块轻松使用。
让我们看看如何在Angular中创建一个简单的模块。通常,我们会创建一个单独的.js文件并在其中定义我们的模块。这个文件将负责创建和配置我们应用程序的模块。
在上图中,我创建了一个只有index.html的简单网站。我们正在使用域风格来组织代码。我还将angular.js文件添加到了libs文件夹中。现在,让我们在app文件夹中创建一个app.js文件,并在index.html中包含angular和我们的app.js引用。
既然我们已经有了结构,让我们开始创建一个Angular模块。要创建模块,我们需要使用angular.module
函数。让我们创建一个名为“myAngularApplication
”的简单模块。
var myModule = angular.module('myAngularApplication', []);
上面代码的作用是创建一个名为“myAngularApplication
”的Angular模块。大多数人经常问,我们作为第二个参数传递的空数组有什么意义。还记得我们谈到模块可以依赖于其他模块吗?第二个数组参数是为了将依赖项传递给其他模块。所以,假设我们有一个名为“myAnotherApplication
”的另一个模块,它想要使用上面定义的模块。我们将不得不将myAngularApplication
传递到数组参数中,如下面的代码所示。
var myAnotherModule = angular.module('myAnotherApplication', ["myAngularApplication"]);
我们可以在这个数组中传递任意数量的模块,这将使其他模块能够在模块组件内部访问已传递(注入)的模块。
现在这是有趣的部分,如果我们忘记传递第二个数组参数,它的含义将完全改变。带有2个参数的函数,即第二个参数是数组(空或其他)的函数,是一个setter函数。它会为我们创建一个Angular模块。而如果我们省略第二个数组参数,该函数就是一个getter函数,即它将返回已创建的同名模块。因此,理解这个微妙但非常重要的区别非常重要。
- 模块Setter:
var myModule = angular.module('myAngularApplication', []);
- 模块Getter:
var myModule = angular.module('myAngularApplication');
现在,一旦我们定义了模块,我们就必须在这个模块内部创建所有组件。我们将使用类似myModule.controller(........)
的代码来在此模块中创建控制器。其他组件也以类似的方式与模块一起创建。
最后但同样重要的是,如果我们希望任何视图使用这个Angular模块,我们需要在该视图中放置ng-app="myAngularApplication"
。让我们在index页面的HTML标签中这样做,这样整个页面就可以访问这个Angular模块。
既然我们已经完成了应用程序的创建,我们如何快速检查我们所做的是否有效呢?我们可以通过在HTML body中编写一个简单的Angular表达式来测试我们的应用程序。表达式是Angular在HTML视图中嵌入JavaScript代码的方式。表达式可以使用双大括号{{ }}
放在视图中。为了测试我们的应用程序,让我们在body中放入{{ 2 + 2 }}
。如果我们打开HTML并在页面上看到4,我们可以保证Angular模块已创建并正在工作。如果没有,我们将在浏览器控制台中收到错误。
Angular.Js中的控制器
既然我们的模块已经准备好了,让我们将注意力转移到控制器上。简单来说,控制器是一个JavaScript对象,它允许我们控制要在视图上渲染的数据。它还允许我们处理来自视图的数据,并根据应用程序的需求对其采取行动。每个视图背后都有一个控制器,负责所有核心业务逻辑,并负责控制应用程序中的数据流。
Angular通过一个称为$scope
的神奇对象来通过控制器类控制视图数据。$scope
本身是一个非常庞大的主题——现在绝对不是开始讨论$scope
的作用域及其细节(如继承)的时候。我们将在以后的文章中详细讨论$scope
的细节,但现在,让我们将$scope
视为控制器和视图之间共享的对象。我们可以将其视为一个公共盒子,视图和控制器会将它们的所有物品放在里面,并在需要时拾取所需的东西。
现在我们知道了控制器和$scope
的基本定义和目的,让我们在应用程序中定义一个简单的控制器。让我们创建一个显示书籍列表的控制器。我们称之为booksController
。由于我们使用域风格进行组织,让我们首先为booksController
添加.js文件,即booksController.js。让我们在index.html中的app.js之后添加对该文件的引用。
既然我们已经设置好了代码,让我们看看如何创建一个控制器函数。控制器是我们要在Angular模块对象上创建的一个简单函数。现在,由于我们已经创建了模块并将其分配给全局变量myModule
,让我们在它上面创建我们的控制器函数。
var booksController = function ($scope) {
$scope.message = "Hello from booksController";
}
myModule.controller('booksController', booksController);
在上面的代码中,我们首先创建一个名为booksController
的函数。我们将使用此函数作为我们的控制器类,并将$scope
的实例传递给它。然后,我们使用myModule.controller
函数将此函数与我们的模块关联起来。完成此操作后,我们的控制器类就可以与视图一起使用了。
注意:我们刚才编写的代码主要是为了解释。它存在一些问题,我将在下一节讨论最小化安全性和IIFE时立即讨论。
既然我们的控制器已经准备好了,让我们看看如何将其附加到视图。要将控制器与视图关联起来,我们需要在要使用此控制器的HTML元素上使用ng-controller
属性。让我们在视图的body
元素上添加ng-controller
。另外,由于我们知道控制器在$scope
对象上添加了一个名为message
的属性,我们可以使用Angular表达式来访问该属性。
有了这段代码,如果我们运行应用程序,我们就可以看到消息通过$scope
对象从控制器传递到视图。
注意:我们在上一篇文章中简要讨论过单向绑定和双向绑定,我们将在以后的文章中详细讨论它们,但目前,在视图中使用Angular表达式访问控制器属性已经足够了。
编写更健壮的代码
我们已经创建了第一个Angular模块和控制器。我们是否已准备好开始进行一些严肃的Angular开发。这个问题的答案既是肯定的也是否定的。肯定的是,因为我们已经理解了模块和控制器背后的概念;否定的是,我们的代码不符合最佳实践,并且信不信由你,如果我们对其进行最小化处理并开始使用,它甚至无法正常工作。让我们看看我们有什么问题以及如何解决它们。
理解IIFE(即时调用函数表达式)
到目前为止,我们一直在使用自由代码来创建我们的Angular模块。主要原因是,我们希望代码在文件加载时立即执行。我们还将创建的模块放入全局变量,这也不是一个好主意。所以,让我们看看如何通过IIFE解决这些问题。
首先,让我们将自由代码移到一个函数中,然后调用这个函数。这实际上与我们当前的代码相同。
var myModule = null;
function CreateModule(){
myModule = angular.module('myAngularApplication', []);
}
CreateModule();
接下来,与其显式地调用这个函数,不如在其定义时就调用它。这可以按以下方式完成。
var myModule = null;
(function CreateModule(){
myModule = angular.module('myAngularApplication', []);
}());
现在,由于没有人会调用这个函数,因为我们在定义时就调用了它,所以我们也可以去掉函数名。结果代码如下所示。
var myModule = null;
(function(){
myModule = angular.module('myAngularApplication', []);
}());
上面的表达式被称为立即调用的函数表达式(IIFE),因为当.js文件加载时,函数定义将立即调用自身。
IIFE有效的主要原因是,我们可以立即执行所有代码,而无需使用全局变量和函数。我们仍然将myModule
变量声明为全局变量,所以我们也去掉它。
(function(){
myModule = angular.module('myAngularApplication', []);
}());
现在,当我们这样做时,我们的控制器创建将失败,因为我们使用了全局变量来创建带模块的控制器。为了解决这个问题,让我们使用getter函数angular.module
将控制器与模块关联起来。同时,为什么不也将控制器放入IIFE中呢?
(function () {
var booksController = function ($scope) {
$scope.message = "Hello from booksController";
}
angular.module('myAngularApplication').controller('booksController', booksController);
}());
使我们的控制器最小化安全
现在我们的控制器和模块看起来不错,并且我们没有使用任何全局变量。现在,当我们准备将此代码部署到生产环境时,我们将首先最小化代码,然后将其发送到生产环境(出于各种优化和其他原因)。最小化所做的是更改缩短变量名。这意味着我们传递给booksController
的$scope
将不再是$scope
,而是某个短变量名。
问题在于Angular仍然期望一个$scope
变量来将控制器与视图数据传递连接起来。但是$scope
变量在最小化文件中不存在,从而导致错误。所有注入到控制器函数的参数都会出现同样的问题。因此,我们需要某种方式来指示Angular传递给控制器的变量实际上是$scope
,以便在最小化后仍可使用它。
为此,我们需要稍微修改控制器创建代码。我们需要做的是将第二个参数作为数组传递,我们可以在其中将控制器参数作为string
字面量放入。最终的控制器代码将如下所示。
angular.module('myAngularApplication').controller('booksController', ["$scope", booksController]);
同样,我们可以在此函数调用中将所有控制器参数作为字符串字面量传递,Angular将知道即使在最小化之后,这些参数也应该如何处理。
使用Controller as语法
在我们结束本文之前,让我们也看看另一种从控制器向视图传递数据的方法。如果我们的控制器有一个未附加到scope的成员变量,则可以通过使用Controller as语法从视图访问它。假设我们的控制器有一个属性greeting
定义如下:
(function () {
var booksController = function ($scope) {
$scope.message = "Hello from booksController";
this.greeting = "This is a greeting message using controller as syntax";
}
angular.module('myAngularApplication').controller('booksController', ["$scope", booksController]);
}());
如果我们想从视图访问此属性,我们可以使用Controller as语法,如下所示。
注意:使用此方法的优点是我们的代码与$scope
对象解耦。在这个阶段比较$scope
与Controller as语法可能会有点令人困惑,所以我们将在以后的文章中进行比较,并使本文避免跑题。
Using the Code
让我们不要在不编写实质性功能的情况下结束本文。让我们修改我们的控制器来跟踪书籍列表,我们的视图将显示这个书籍列表。让我们在$scope
上定义一个书籍数组,它最初将是空的。然后,我们将创建一个名为fetchBooks
的函数,该函数将用一些虚拟书籍数据填充此列表。在实际应用程序中,这些数据将从服务器检索。现在让我们看看带有这些更改的控制器代码。
(function () {
var booksController = function ($scope) {
$scope.message = "Hello from booksController";
this.greeting = "This is a greeting message using controller as syntax";
$scope.books = [];
$scope.fetchBooks = function () {
$scope.books = [
{ ID: 1, BookName: "Test Books 1", AuthorName: "Test Author 1", ISBN: "TEST1" },
{ ID: 2, BookName: "Test Books 2", AuthorName: "Test Author 2", ISBN: "TEST2"},
{ ID: 3, BookName: "Test Books 3", AuthorName: "Test Author 3", ISBN: "TEST3"},
{ ID: 4, BookName: "Test Books 4", AuthorName: "Test Author 4", ISBN: "TEST4"},
{ ID: 5, BookName: "Test Books 5", AuthorName: "Test Author 5", ISBN: "TEST5"}
];
}
}
angular.module('myAngularApplication').controller('booksController', ["$scope", booksController]);
}());
现在来看视图部分。我为视图添加了一些bootstrap样式,使其看起来更漂亮,所以视图代码现在看起来像这样。
除了bootstrap类之外,我们所做的是创建了一个按钮,该按钮在ng-click
指令上调用fetchBooks
函数。ng-click
指令可以与HTML元素关联,并在单击事件上调用控制器函数。
在此之下,我们有一个使用ng-repeat
指令渲染行的表。ng-repeat
是一个指令,它可以遍历集合中的项,并为所有项渲染指定的HTML元素。我们正在为books
集合中的每本书渲染一行。
当我们运行代码并单击fetch按钮时,我们可以在屏幕上看到结果如下。
注意:我们将在后续文章中详细介绍ng-click
和ng-repeat
指令,这里只是一个预览,让您了解事物是如何连接起来的,以及我们如何轻松地让视图渲染书籍列表。
现在,我们有了一个基本的Angular应用程序,可以渲染一个硬编码的书籍列表。
看点
在本文中,我们研究了Angular中模块和控制器的概念。我们研究了如何使用IIFE来更好地管理我们的代码,以及如何使我们的控制器最小化安全。本文是从初学者的角度编写的,希望对您有所帮助。
历史
- 2015年5月27日:初版