Angular 开发学习 04 – 组件和模块

获取本章的源代码,可以使用git clone https://gitee.com/devbean/learning-angular-todomvc.git,然后检出cp04标签:git checkout cp04即可。

我们从最开始的ng new todomvc开始说起。

这个命令是使用 Angular CLI 创建 Angular 项目的语句。它的运行结果是生成一个完整的 Angular 项目开发框架。对于简单的应用,我们完全不需要考虑 webpack 的配置、 测试服务器的热部署等环境搭建的细节,就可以得到一个完善的开发环境。项目创建语句还可以有很多参数,比如,我们想使用 SCSS 而不是原始的 CSS,那么就可以使用下面的语句:

ng new todomvc --style=scss

这样,我们基本零配置就可以在开发时使用 SCSS。不过为简单起见,我们暂时不会使用 SCSS,而是直接使用 CSS 作为 TodoMVC 的样式表语言。

正如前面的章节说过的那样,使用ng serve成功之后即可打开浏览器查看页面的显示。而我们也是介绍过ng new命令到底生成了什么。

下面,我们从 src 中的 app 文件夹开始。app 文件夹是所有项目源代码文件所在地。以后的 ng 命令为我们生成的所有文件都会在这个文件夹中。这也是我们主要关心的文件夹之一。

刚刚通过ng new命令获得的 app 文件夹中有五个文件:

  • app.component.css
  • app.component.html
  • app.component.ts
  • app.component.spec.ts
  • app.module.ts

可以看到,这五个文件分为两类,分别为 component 和 module。

这种命名方式是 Angular CLI 推荐的。文件名分为三部分:第一部分是文件的名字,第二部分是文件的类型,第三部分是文件的后缀名。例如,app.component.css,这个文件隶属于 app,这个 app 是一个 component,这个文件后缀名是 css。

同时还可以看出,一种类型的文件可能包含多个文件。例如这里的 component 就包含了四个文件。我们会在后面详细介绍这四个文件。

component 和 module 是 Angular 中两类最重要的类型。顾名思义,component 即组件,module 即模块。二者的关系是,组件必然隶属于一个模块,一个模块可以包含多个组件。

与 React 或者 Vue 类似,Angular 把页面上所有的元素都看作组件。一个组件可以看作是一段可复用的 HTML 代码片段。在我们做 Java 或者 C++ 开发中,一段可复用的代码通常会抽象为一个函数。函数可以被其它函数调用。在 Angular 中也是类似,一段 HTML 代码片段也可能被复用,例如下面的代码:

<div class="portlet">
	<div class="portlet-header">
		Header
	</div>
	<div class="portlet-body">
		...
	</div>
</div>

这段代码是一个带有标题的 portlet 区域,大致类似下面的样式:

这段 HTML 代码,连同为表现其样式所附加的 CSS,甚至还有其行为的 JavaScript,放在一起即可被另外的页面复用。那么,我们就可以把这些代码放在一起,抽象为一个组件。在使用 Angular 开发页面时,很多时候都是在把页面分解成为一个个独立的组件,最终将这个组件项目组合起来,完成最终页面的开发。

在 Angular 中,每一个组件都必须属于一个模块。可以认为模块是组件的集合(事实上,模块不仅仅包含组件,还可以有其它内容)。同一模块中的组件可以直接相互使用;不同模块中的组件在相互使用时,需要导入组件所在模块。也就是说,如果你想要使用别人提供的组件,应该引入该组件所在的模块,而不是组件本身。这暗示着,模块其实是 Angular 最基本的部件,因为 Angular 的项目是通过相互引用模块,错综复杂地耦合在一起。

在 Angular 中,至少有一个模块作为根模块;根模块中至少有一个组件作为根组件。这个根模块一般命名为AppModule;这个根组件一般命名为AppComponent。按照前面所说 Angular 的命名规则,AppModule文件名为 app.module.ts;AppComponent文件名为 app.component.ts。这就是 Angular CLI 为我们生成的几个文件的来历。

了解到这一点,下面我们打开 app.module.ts 文件:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

后缀名 ts 意味着 Angular 的项目使用 TypeScript 编写。Angular 提供多种语言开发:ES6,Dart,TypeScript。但官方推荐的是使用 TypeScript。几乎所有 Angular 教程都是使用 TypeScript。因此,我们也选择使用 TypeScript 进行学习。在一定程度上说,这是必须的,因为虽然 Angular 提供了多种语言的开发方式,但 Angular 本身就是用 TypeScript 开发的。

TypeScript 是 JavaScript 的超集。任何合法的 JavaScript 都是合法的 TypeScript。TypeScript 提供了比 JavaScript 更多的功能,比如完整的类型系统、修饰器等。有过 Java 或者 C# 开发经验的开发者很容易上手 TypeScript。这里我们不会详细介绍 TypeScript,而是在使用中慢慢接触解释有关 TypeScript 的相关内容。

TypeScript 有类的概念。因此,我们可以很明显地看到代码最后一行export class AppModule { },是如何将一个类导出的。export关键字来自 ES6,意味着这个类AppModule可以在其它模块使用。注意,这里所说的“模块”,不是 Angular 的模块,而是 ES6 系统中的模块。ES6 模块与 Angular 模块有本质区别。ES6 模块是语言级别的,每个文件是一个模块,文件中定义的所有对象都从属于那个模块。 通过export关键字,模块可以把它的某些对象声明为公共的。 其它 JavaScript 模块可以使用import语句访问这些公共对象。

JavaScript,ES,ES6 和 ES2015

ES 是 ECMAScript 的缩写。ECMAScript 是一个标准,由 ECMA 国际(前身为欧洲计算机制造商协会,英文名称 European Computer Manufacturers Association)通过 ECMA-262 标准化的脚本程序设计语言。JavaScript 是 ECMAScript 的一种实现。ECMAScript 还有其它实现,比如 ActionScript。 2015年6月,ECMAScript 6.0(ES6)正式发布,也就是 JavaScript 语言的下一代标准。ECMA 国际为了更频繁地发布包含小规模增量更新的新版本,将于 2016 年发布的新标准命名为 ECMAScript 2016。之后的新版本将按照 ECMAScript + 年份的形式命名发布。因此,按照这个规定,于 2015 年发布的 ES6 被重新命名为 ES2015。因此,ES6 和 ES 2015 仅是同一标准的不同称呼而已。

Angular 自带了一组 JavaScript 模块,可以把它们看成库模块。每个 Angular 库的名称都带有 @angular 前缀。 使用 JavaScript 的 import 语句导入其中的各个部分。比如,

import { BrowserModule } from '@angular/platform-browser';

就是从 JavaScript 模块@angular/platform-browser导入了 Angular 模块BrowserModule

我们希望把 TypeScript 的类作为 Angular 的模块或者组件的基础。这是很自然的想法:面向对象设计思想告诉我们,模块或者组件天然就是一个对象。那么,如果确定一个类是模块还是组件,还是其它什么东西呢?Angular 选择使用装饰器来标明。装饰器( Decorator )这个名词来自 Python,在 Java 中被称为“注解 Annotation”,在 C# 中被称为“属性 Attribute”。

为什么AppModule是一个 Angular 模块呢?因为它使用了@NgModule装饰器。被@NgModule装饰器装饰的类,Angular 就认为是一个模块。而这个装饰器,也是从一个 JavaScript 模块中导入的:

import { NgModule } from '@angular/core';

除了@NgModule,Angular 还提供了很多其它的装饰器。我们会在后面详细介绍这些装饰器。

@NgModule装饰器接受一个 JSON 对象作为参数:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

declarations表示这个模块中声明了哪些组件。前面说过,组件必须隶属于一个模块,也就是说,一个组件必须在某一个模块进行声明。并且,一个组件只能在一个模块中进行声明。上面的代码中,AppModule 声明了 AppComponent 组件。需要指出的是,declarations中不仅仅是组件,还可能有其它元素,比如指令等。我们会在后面再介绍这部分内容。

imports表示这个模块导入了哪些模块。前面说过,模块之间可以相互引用。引用的方式就是导入其它模块。这里,AppModule 导入了 BrowserModule,因此就可以使用 BrowserModule 中公开的组件或其它元素。

providers表示这个模块提供了哪些服务。服务是一般作为逻辑的抽象。组件通过服务实现自己的业务逻辑。比如,有的服务提供了 HTTP 请求,有的服务提供了用户状态管理等。

bootstrap表示这个模块启动哪些组件。应用程序在启动时会引导根模块AppModule,将其作为一个入口组件entryComponent。引导中会创建所有列出在bootstrap中的组件,然后将其插入 DOM,形成一棵组件树。虽然bootstrap是一个数组,但一般一个应用程序只有一棵组件树,也就是只有一个引导组件。这个组件通常被称为 AppComponent。

AppComponent 是 Angular CLI 生成的另外一个重要的部分。与 AppModule 不同的是,AppComponent 由四个文件构成,这四个文件具有类似的命名:通常以 component 作为中间名,后缀名不同。这与 app.module.ts 是一致的。组成 AppComponent 的四个文件分别是:

  • app.component.ts:AppComponent 的类所在文件
  • app.component.spec.ts:AppComponent 单元测试代码文件
  • app.component.html:AppComponent 的 HTML 渲染文件
  • app.component.css:AppComponent 的样式文件

我们从 app.component.ts 开始看起:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'todomvc';
}

AppModule类似,AppComponent同样只是一个普通的 TypeScript 类。区别在于,后者使用@Component装饰器修饰。顾名思义,@Component用于标识一个类是一个组件。这个装饰器,同样是从 JavaScript 模块 @angular/core 导入的。@Component装饰器也接受一个 JSON 对象作为参数。在AppComponent中,@Component使用了下面几个参数:

  • selector:字符串类型的选择器,其使用与 CSS 选择器类似。例如,这里设置selector: 'app-root',那么,我们在 HTML 中使用<app-root></app-root>即可使用这个组件。也就是说,我们创建了一个新的 HTML 标签。同样,我们也可以使用selector: '[appRoot]',注意[appRoot]其实就是 CSS 的属性选择器的语法,类似的,我们就需要使用<div appRoot></div>来使用这个组件。一般而言,我们通常会将组件作为新的 HTML 标签,但某些情况下则需要使用另外的技巧。我们会在实际应用中看到这些内容。
  • templateUrl:模板 URL。如果我们把组件看作新的 HTML 标签,浏览器在遇到这个新的标签时,需要知道要如何渲染这个标签,也就是这个组件的模板。Angular 使用 templateUrl指定组件模板文件的位置。
  • styleUrls:样式表 URL。templateUrl定义了组件模板文件的位置,模板文件确定了组件的结构,组件的样式则需要使用 CSS 文件定义,也就是这里的styleUrls。注意,styleUrls的类型是一个数组,因此,一个组件可以有多个样式表文件。

回到 app.component.ts,我们通过@Component的参数就可以知道,AppComponent 的选择器是app-root,其模板文件路径是 ./app.component.html,其样式表文件路径是 ./app.component.css。后两者都是通过相对路径指定了文件的位置。也就是说,app.component.html 和 app.component.css 与 app.component.ts 是同目录的。

Angular CLI 为我们生成的 AppComponent 类只有一个属性:title,其值被设置为 todomvc。

app.component.html 的内容则是:

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
  </li>
</ul>

这是一个普通的 HTML 片段,除了有一个特殊的{{title}}语法。这里简单说一下,这个语法意味着,这个位置的内容会被绑定到 AppComponent 类的title属性。{{title}}的内容会被自动替换为title属性的值。注意我们说的是“绑定”,这意味着,当title值改变时,{{title}}的值会自动随之改变。我们会在后面详细介绍这部分内容。

app.component.css 的内容是空白的,也就是不添加任何特殊的样式。

好了,现在模块和组件都准备好了,并且组件也被添加到了模块。前面我们说过,这样定义好之后,使用<app-root></app-root>就可以使用AppComponent。这部分代码在哪里呢?现在打开 index.html,我们就会看到:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Todomvc</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Angular CLI 在这里通过<app-root></app-root>使用了AppComponent组件。因此,我们能够在屏幕上看到浏览器渲染出来的页面。

现在,我们大致了解 Angular CLI 生成的文件,以及 Angular 中最重要的组件和模块的含义。之后的章节中,我们将在此基础上,逐步实现 TodoMVC 应用所要求的各种功能。

Leave a Reply