6

上一篇:【译】《精通使用AngularJS开发Web App》(三)
下一篇:

书名:Mastering Web Application Development with AngularJS

Chapter 1

视图

我们已经见了这许多AngularJS模版的例子,应该认识到他并不是“又出现一个模板语言”,但确实还是有许多的不同(跟其他模板语言相比)。不仅是说框架的模板语法依赖于HTML,还允许我们来扩展HTML的词汇,而且还有可以在没有任何人工干预的情况下刷新局部视图这种的独一无二的能力!

事实上,AngularJS 与 HTML 和 DOM 的联系更为密切,因为它就是依赖浏览器来解析模版内容的(就跟浏览器会对任何其他的HTML文档所做的那样)。在浏览器把标记文本转化为 DOM 树之后,AngularJS就会进入这个解析好的DOM结构。然后,每当遇到一个指令,AngularJS就会执行它的逻辑,并将指令变为动态的内容。

既然AngularJS 依赖于浏览器来解析模板,我们就需要确保提供的模板是合格的HTML内容。要特别注意HTML标签的正确闭合(否则的话,不会输出错误信息,但视图却不会被正确的渲染出来)。AngularJS 必须在正确的DOM树上工作。

AngularJS 使得丰富HTML的词汇表成为可能(我们可以添加新的属性或HTML元素,并教会浏览器应该如何解析他们)。这就像在 HTML 基础上创建一门特定领域语言(domain-specific language),并指导浏览器如何理解新的指令。你可能经常会听到AngularJS“教了浏览器一些新的技巧”。

声明式模板视图 - 势在必行的控制器逻辑

AngularJS自带了很多方便的指令,我们将会在后面的章节中涵盖其中的大部分。最重要的事情,其实不是去了解单个指令的语法和功能,而是 AnulgarJS 构建 UI 的背后的哲学思想。

AngularJS 推广了一套声明式的构建 UI 的方式。在实践中这意味着,模板会把重心放在如何描述一个期望的效果,而不是如何具体实现它。听起来可能有点困惑,那就看个例子吧。

让我们想象一下这种情况,我们需要创建一个form,用户可以输入一些简短的文字,然后可以点击按钮发送表单。当然这还涉及到一些用户体验的事情,比如文字长度需要限制在100字以内,如果超出范围就禁用 发送 按钮。用户需要在他们输入的时候知道还剩多少个字。如果剩余的字数小于10,那么提示文字就要改变样式为警告的状态。也需要能够清除已经存在的文字。完成后的form可能看起来如下图:
示例表单
上面这个需求不是特别有挑战性,也只是一个普通的文本表单,尽管如此,这里还是由于多需要整合的UI元素,比如,要确保按钮的 disabled 禁用状态可以正常工作,剩余字数要用恰当的样式来准确的显示等等。首次尝试的实现代码如下:

<div class="container" ng-controller="TextAreaWithLimitCtrl">
           <div class="row">
               <textarea ng-model="message">{{message}}</textarea>
           </div>
           <div class="row">
             <button ng-click="send()">Send</button>
             <button ng-click="clear()">Clear</button>
           </div>
   </div>

我们还是使用前面的代码作为起点,在那个基础上继续。首先,我们需要显示剩余字数,这个相当容易,如下所示:

<span>Remaining: {{remaining()}}</span>

remaining() 函数定义在 TextAreaWithLimitCtrl 控制器中的 $scope 上,如下:

$scope.remaining = function () {
    return MAX_LEN - $scope.message.length;
};

然后,我们需要在文本不符合字数限制的时候禁用 Send 按钮。这个可以用 ng-disabled 指令很容易的实现,如下:

<button ng-disabled="!hasValidLength()"...>Send</button>

这里使用了一个固定模式。要操作UI的话,只需接触模板的一小部分,然后根据模型的状态(这里就是文本的长度)描述一个期望的输出结果(显示剩余字数,禁用按钮等)即可。最有意思的地方在于,我们不需在 JavaScript 代码中持有任何对DOM元素的引用,我们也不需要准确的操控DOM元素。现在我们只需关注模型的变化,让 AngularJS 去做那些枯燥繁重的工作去吧。我们所需要做的只是在这种形式的指令中提供一些建议。

继续回到我们的例子中来,我们还要确保剩余字数的提示要在只剩下很少字数的时候改变样式。这是一个可以可实战中学习另一个声明式UI的例子的好机会。如下所示:

<span ng-class="{'text-warning' : shouldWarn()}"> 
Remaining: {{remaining()}}
</span>

shouldWarn() 方法的实现如下所示:

$scope.shouldWarn = function () {
    return $scope.remaining() < WARN_THRESHOLD; 
};

CSS class 的变化是根据模型的变化而来的,但是我们并没有在 JavaScript 中写任何的操作 DOM 的代码!UI 会基于以声明的方式表达的“想要干嘛”而重绘。我们用 ng-class 指令所表达的意思是:“每次字数超出限制,都要通过把 text-warning 加到 <span> 元素的 class 上面 来给用户一个警示”。这跟下面这种说法是相当不同的 “当输入一个新的字符,并且字数超出了限制,就去找到 <span> 元素,然后改变这个元素的 CSS class text-warning”。(译注:两种不同的思路,前一种是描述你所希望的状态,后一种是完全的把你要实现这种结果的过程描述出来)。

我们这里说的好像只是有点微妙的不同,但其实,声明式的和命令式的是两种相当对立的实现方式。命令式的编程,重点在于描述每一个单独的过程,并将结果导向希望得到的结果。声明式的方式,重点在于希望得到的结果。至于要达到这个结果的那些独立的步骤是由支持这种方式的框架所关心的。这就像是在说“亲爱的 AngularJS,这就是当模型到某个状态之后,我所希望的UI能够展示的状态,现在呢,就请开始想想什么时候以及如何来重绘这些UI吧”。

声明式的编程通常更富有表达力,因为把开发人员从非常细微的、底层的指令(编码)中解放出来了。产出的代码通常都非常的简洁、易于阅读。但若要在工作中使用声明的方式,就必须有装备工具能够正确的理解更高级的指令。我们的程序开始依赖于这些机器的决定,并且我们需要放弃一些底层的控制权。使用命令式的方式,我们拥有完全的控制权,也能很好的协调好每一个单独的操作。我们得到了更多的控制权,这种“负责任”的代价就是要写非常多的底层的、重复的代码。

熟悉 SQL 语言的读者会觉得这些听起来都非常的熟悉(SQL 是一个针对请求临时(adhoc)数据的非常富有表达力的声明式的语言)。我们简单的描述一下需要得到的结果(去fetch的数据),之后的就是去让(关系型)数据库去想该怎么去拿到特定的数据了。绝大多数情况下,这个过程都能完美的工作,我们也能很快速的拿到我们所想得到的。当然也有一些情况需要给出更多的提示(索引(indexes),查询计划提示(query planner hints)等等),或者手动控制数据检索过程,以便优化性能。

AngularJS模板中的指令声明式的描述了所期望的结果,所以呢,我们得以从一步步的给出如果改变DOM元素的各个属性(基于 jQuery 的应用通常需要如此)中解脱出来。AngularJS 极力推广在模板上使用声明式的编程,而在javascript代码中是用命令式的(控制器和业务逻辑)。使用 AngularJS的话,我们很少会使用到低级的、命令式的指令来操作DOM(唯一的例外是在指令里面中的代码)。

就经验而言,千万不要在 AngularJS控制器中操作 DOM 元素。在控制器中获取一个DOM的引用,并操作DOM的属性,是在用命令式的方式控制UI -- 这是跟 AugularJS 构建 UI 的思想相悖的。

使用 AngularJS 指令写出来的声明式的 UI 模板可以很迅速的描述出复杂的、交互的UI。AngularJS 会自主做出所有这些底层决定何时以及如何操作DOM树的部分。绝大多数时候 AngularJS 会做出“正确的事情”,并更新UI为所预期的(也非常及时的)。不过,理解 AngularJS 的内部工作原理也非常的重要,这样的话我们就可以在需要的时候给框架一些适当的提示。这里的情况跟 SQL 又非常的相似,大多数情况下,我们都不需要为查询计划的工作情况操心。但当我们遇到性能问题的时候,了解查询计划是如何下决定的就非常有价值了,这样一来我们就可以给它提供更多的意见。这同样适用于 AngularJS 的UI 管理:我们需要了解背后的机制,以便于更高效的使用模板和指令。

模块和依赖注入

细心的读者可能已经发现了,到目前为止所用到的例子都是使用的全局的构造函数来定义控制器的。但是,全局的状态是邪恶的,它危及了应用的结构,让代码更难以维护,测试,和阅读。AngularJS 绝不会建议使用全局状态的。相反,它提供了一整套的 API,可以很方便的定义模块,在这些模块中注册对象。

AngularJS中的模块

我么一起来看看怎么把一个丑陋的,全局定义的控制器转换为同等的模块化的定义。之前的控制器是这么声明的:

var HelloCtrl = function ($scope) { 
    $scope.name = 'World';
}

模块化之后是这样的:

angular.module('hello', []) 
    .controller('HelloCtrl', function($scope){
        $scope.name = 'World';
    });

AngularJS 自身定义了一个全局的 angular 名字空间。这个名字空间提供了很多不同类型的工具和方便的方法,module 就是这些方法的其中一个。module 会扮演一个其他的AngularJS需要管理的对象(控制器、服务等)的容器。正如我们之后就会看到的,除了简单的名称空间和代码组织之外,模块还有非常多的东西需要学习。

要定义一个新的模块,我们需要给 module 方法的第一个参数提供模块的名字。第二个参数指定所需要依赖的模块(在之前的模块中,我们没有依赖与任何其他模块)。

angular.module 方法的调用会返回一个新创建的模块的实例。一旦我们是用这个实例,就可以开始定义新的控制器了。这非常的简单,只需使用如下参数来调用 controller 即可:
- 控制器的名字(字符串类型)
- 控制器的构造函数

全局定义的控制器构造函数只适用于快速示例和原型开发。永远不要在大型的、真实的应用中使用全局定义的控制器。

现在已经定义了一个模块,但我们还需要通知 AngularJS 它的存在。这是通过给 ng-app 属性提供一个值来完成的,如下:

<body ng-app="hello">

一个经常犯的错误就是忘记了在 ng-app 属性中指定模块的名字,这也会造成一些常见的困惑。在 ng-app 属性中省略模块名称会引起错误,说明有未定义的控制器。

转载请注明来自[超2真人]
本文链接:http://www.peichao01.com/static_content/doc/html/Mastering_Web_Application_Development_with_AngularJS_4.html


超2真神
1.1k 声望104 粉丝

关注设计、漫画、动画,前端、javascript、iOS还有任何好玩的事情