在这一步中,我们将不会在我们的应用中添加任何新功能,相反,我们打算退回一步,重构我们的代码库,移动我们的代码和文件,以此来使我们的应用更具易扩展性和可维护性。

在先前的步骤中,我们已经见识到了如何将我们的应用构建得更具模块性和可测试性。另一种同样重要的思想是,用一种使得查看代码变得容易(无论对我们还是团队中的其他开发者)和能在我们应用中快速指定的某区域的相关代码块的方式来组织我们的代码库。

为此,下面我们将解释为何及如何:

  ·将每一个实体置于他们自己的文件中(own file)。

  ·通过特定区域(feature area)来组织我们的代码,而不是通过函数。

  ·将我们代码模块化以便其他模块可以依赖。

(我们会尽量简洁,不打算深入探讨每一个最佳实践和惯例的细节。这些原则在Angular风格手册中都被深入探讨过了,手册中也包含更多如何有效组织Angular代码库的技巧。)

最重要的不同将会在下面列出,你可以点击这里查看全部的不同。

特性和文件的一一对应

出于简洁性的目的,将所有的东西都放在一个文件中是很诱人的,或者为每种类型创建一个文件;比如:所有的控制器放在一个文件,所有的组件放到另一个文件,所有的服务又放在第三个文件中,诸如此类。这样做在开始时可能运行得不错,但随着我们应用的增长,这将成为维护的一个负担。随着我们添加越来越多的特性,我们的文件将越来越大,最后将会很难检索到我们在寻找的代码。

取而代之,我们应该将每一个特性/实体置于它自己的文件中。每一个独立的控制器会在它自己的文件中被定义,每一个组件将会在它自己的文件中被定义,诸如此类。

幸运的是,我们无需在我们的代码中做任何改变来遵循这条准则,因为我们已经定义了我们的phoneList组件和它自己的phone-list.component.js文件。做的不错!

随着我们增加更多特性,我们会将此牢记于心。

通过特性来组织

所以,既然我们已经学会了将任何特征来置它自己的文件中,我们的app/目录将会被充满大量的文件和规范(请牢记我们将单元测试文件紧靠于我们的源代码文件)。更重要的是,逻辑上相关的文件不会被组织到一起;这会使得定位应用中一个指定区域的所有相关文件,做一些改变,或者修一个bug,这些都变得困难。

那么,我们应该做什么呢?

嗯,我们打算通过特性来在目录中组织我们的文件。比如,由于我们应用中已经有一个用于展示电话的部分,我们会将所有相关文件放到app/目录下的phone-list/。我们马上将会发现一些指定的特征在应用中的不同部分被用到了,我们会把这些放到app/core/目录下。

(一些core目录下其他文件名有sharedcommon和components。最后一个有点思想上的误导,因为它也会包含除组件之外的其他东西。

这主要是由于历史遗留,那时“组件”仅仅是指应用中的通用构建模块。)

基于我们目前所讨论的,下面是我们对phoneList“特征”的目录/文件布局:

app/
phone-list/
phone-list.component.js
phone-list.component.spec.js
app.js

使用模块

正如先前提及的,采用模块化的架构的一个好处是代码复用--不仅仅在一个应用内,也包括跨应用间的复用。还要做一步来减少代码复用的阻力:

  ·每一个特性/区域应该声明其自己的模块并且所有相关的实体都应该在改模块中被注册。

让我们拿phoneList举个例子,之前,phoneList组件会在phonecatApp中被注册:

angular.
module('phonecatApp').
component('phoneList', ...);

相似的,附庸的规范文件phonecatApp会在每次测试前被加载(因为那是我们的组件被注册的地方)。现在,想象一下我们需要在另一个我们开发的项目中引入电话列表。多亏我们模块化的架构,我们不需要再造轮子啦;我们仅仅需要在其他项目中复制phone-list/目录并且在index.html中添加必要的脚本标记就搞定了,对吗?

好吧,没那么快。新项目对phonecatApp一无所知。所以,我们或许不得不把phonecatApp作为这个项目的主模块的名字。正如你想象的那样,这样既生硬,还容易犯错。

没错!想必你已经猜到了,有一种更好的方法!

每一个特征/区域都会声明其自己的模块并会在其中注册相关实体。主模块(phonecatApp)将会在每一个特征/区域声明一个依赖。现在,想要在一个新项目中复用相同代码,要做的就是将特征目录复制过来并且在新项目的主模块中添加特征模块,以此来形成一个依赖。

经过这些改变,下面是我们的phoneList特征看起来的样子:

/:

app/
phone-list/
phone-list.module.js
phone-list.component.js
phone-list.component.spec.js
app.module.js

app/phone-list/phone-list.module.js:

// Define the `phoneList` module
angular.module('phoneList', []);

app/phone-list/phone-list.component.js:

// Register the `phoneList` component on the `phoneList` module,
angular.
module('phoneList').
component('phoneList', {...});

app/app.module.js:

由于app/app.js现在仅仅包含主模块的声明,我们添加一个.module后缀

// Define the `phonecatApp` module
angular.module('phonecatApp', [
// ...which depends on the `phoneList` module
'phoneList'
]);

通过在定义phonecatApp模块时遍历phoneList里的依赖数组,Angular会使所有在phoneList中注册的实体在phonecatApp中也同样可用。

(别忘了更新你的index.html,为我们创建的每一个JavsScript文件添加一个<script>标签,这很无聊,但绝对值得做。

在生产就绪的应用中,你无论如何(考虑到性能因素)都会将你的JavsScript文件串联并压缩),所以这不会再是一个问题。)

(注意到定义模块的文件(比如.module.js)需要被其他添加该模块中特性(比如:组件,控制器,服务,过滤器)提前引入。)

外部模板

既然我们在重构,让我们更进一步。正如我们已经学到的,组件包含模板,模板本质上就是用于阐述我们的代码如何布局和展示给用户的HTML代码片段。在第三步中,我们已经看到了如何使用CDO的template特性来以字符串形式为组件指定模板。将HTML代码写入一个字符串可不怎么优雅,尤其是对于更大的模板来说。如果我们将HTML代码放在.html文件中将会好很多。通过这种方法,我们会得到所有我们的IDE/编辑器提供的支持(比如:HTML-特定的颜色高亮和自动补全),并且将我们的组件定义得更整洁。

所以,保持我们组件模板的内联(使用CDO提供的template特性)是非常好的,我们打算在我们的phoneList组件中使用外部模板。为了表示我们将使用一个外部模板,我们使用templateUrl特性并且指定我们模板将被加载的URL。既然我们想要将我们的模板和组件定义的地方放在一起,我们将它放在app/phone-list/目录下.

我们将template特性中的内容(HTML代码)放到app/phone-list/phone-list.template.html中并且修改我们的CDO,像这样:

app/phone-list/phone-list.component.js:

angular.
module('phoneList').
component('phoneList', {
// Note: The URL is relative to our `index.html` file
templateUrl: 'phone-list/phone-list.template.html',
controller: ...
});

一旦Angular在运行时想要创建一个phoneList组件的实体,它将发送一个HTTP请求来从app/phone-list/phone-list.template.html中获取模板。

(我们通过为外部模板添加.template后缀来和我们的风格保持一致,另一种风格是仅仅添加.html扩展名(比如:phone-list.html)。)

最终的目录/文件布局

最终我们完成了重构,下面是我们的应用从外边看起来的样子:

/:

app/
phone-list/
phone-list.component.js
phone-list.component.spec.js
phone-list.module.js
phone-list.template.html
app.css
app.module.js
index.html

总结 

即使我们没有在我们的应用中添加任何新功能,但我么已经向一个架构优良和可维护的应用迈进了一大步。时间使得事情变得更好玩( Time to spice things up)。让我们进入下一步来学习如何在应用中添加一个全文本搜索吧!

[Angular Tutorial] 4 - Directory and File Organization的更多相关文章

  1. [Angular Tutorial]PhoneCat Tutorial App

    (注:曾经在<不敢止步>一书中看到学到一个观点,作者认为学习一门技术最好的方法就是翻译某部领域书籍.这里我决定做一次尝试,接下来花1个月左右时间,将Angular Tutorial Pho ...

  2. [Angular Tutorial] 0-Bootstraping

    在这一节的tutorial中,您将会逐渐熟悉AngularJS phonecat app的最重要的源代码文件.您也将学到如何将开发服务器与angular-seed绑定到一起,并且在浏览器中运行应用. ...

  3. 解决clang: error: no such file or directory: such file or directory:的问题

    一,详细问题描述 clang: error: no such file or directory: 'xxx/src/GGBaCollectionViewCell.m' clang: error: n ...

  4. Non-Programmer's Tutorial for Python 3/File IO

    File I/O Here is a simple example of file I/O (input/output): # Write a file with open("test.tx ...

  5. [Angular Tutorial] 3-Components

    在先前的步骤中,我们看到了一个控制器和一个模板如何一起工作来将一个静态的HTML文件转化为动态页面(view).一般说来,这在单页应用中一种非常常见的模式(在Angular应用中尤其是这样): ·客户 ...

  6. [Angular Tutorial] 7-XHRs & Dependency Injection

    我们受够了在应用中用硬编码的方法嵌入三部电话!现在让我们用Angular内建的叫做$http的服务来从我们的服务器获取更大的数据集吧.我们将会使用Angular的依赖注入来为PhoneListCtrl ...

  7. [Angular Tutorial] 14 -Animations

    在这一步中,我们将会通过在我们先前创建的模板代码中添加CSS和JavaScript动画效果来扩展我们的web应用. ·我们现在使用ngAnimate模块来允许动画效果贯穿整个应用. ·我们也依赖于自带 ...

  8. [Angular Tutorial] 13 -REST and Custom Services

    在这一步中,我们将会改变我们获取数据的方式. ·我们定义一个代表RESTful客户端的自定义服务.使用这个客户端,我们可以用一种更简单的方法向服务端请求数据,而不用处理更底层的$httpAPI,HTT ...

  9. [Angular Tutorial] 12 -Event Handlers

    在这一步中,您将会在电话细节页面添加一个可点击的电话图片转换器. ·电话细节页面展示了当前电话的一张大图片和几张相对较小的略图.如果我们能仅仅通过点击略图就能把大图片换成略图就好了.让我们看看用Ang ...

随机推荐

  1. C++:bitset类的使用

    #include <iostream> #include <bitset> using namespace std; int main() { //初始化一个bitmap , ...

  2. SpringMVC中获得HttpRequest对象的方法

    1. 使用@autowired注入HttpRequest 2. 在方法中直接声明形参有HttpRequest即可. 3. 使用一个Listener,然后获取.

  3. jq判断元素是否显示

    为了判断元素是否显示,jquery中用is()来实现, $(function(){ $(obj).bind('click',function(){ if(obj.is(:visible)){ //编写 ...

  4. JSP标准标签库(JSTL)--函数标签库 fn

    和String的方法类似,就是对String的一种封装. No. 函数标签名称 描述 1 ${fn:contains()} 查询某字符串是否存在,区分大小写 2 ${fn:containsIgnore ...

  5. css设置层级显示

    效果: 代码: <li id="tabIdcontent4" class="nomal" tabid="content4" style ...

  6. armstrong's programming erlang 2nd

    Re: json handling map functions in erlang 17 I have not read Joes final book on the matter (several ...

  7. 创建表结构相同的表,表结构相同的表之间复制数据,Oracle 中 insert into XXX select from 的用法

    /**1. 用select 创建相同表结构的表*/create table test_tbl2 as select * from test_tbl1 where 1<>1; /**  2. ...

  8. HDU 2412 Party at Hali-Bula

    树形DP水题.判断取法是否唯一,dp的时候记录一下每个状态从下面的子节点推导过来的时候是否唯一即可. #include<cstdio> #include<cstring> #i ...

  9. JAVA基础--代理模式

    interface Network{ public void browse() ; // 浏览 } class Real implements Network{ public void browse( ...

  10. LPC2478的GPIO使用详解

    GPIO使用 LPC2478的GPIO是不能断开时钟的,上电就连接.处理GPIO主要就下面几步 1.      设置为普通IO模式 2.      设置输入输出方向 3.      设置值 以下寄存器 ...