65.9K
CodeProject 正在变化。 阅读更多。
Home

Angular 的构建块

2018 年 8 月 6 日

CPOL

16分钟阅读

viewsIcon

28307

downloadIcon

123

在这里,我们将探讨 Angular 的构建块,如组件、指令、模块、服务、组件模板。我们将探讨如何手动创建组件以及组件在应用程序中的工作方式。我们还将了解 Angular 中的依赖注入。

引言

今天,我们讨论 Angular 的构建块。基本上,Angular 本身是用 Typescript 编写的。因此,在开始学习 Angular 之前,了解 Typescript 是一个先决条件。显然,由于 Angular 是用 Typescript 编写的,如果我们想编写自己的自定义代码,那么我们肯定会用 Typescript 来编写。这是 Angular 系列的路线图

目录

在本文中,我们将涵盖很多内容

Angular 的构建块

Components

在每个 Angular 应用程序的核心,我们都有一个或多个组件。事实上,在实际应用中,我们用数十个组件来开发复杂的应用程序。组件封装了视图背后的数据、HTML 标记和逻辑。Angular 采用基于组件的架构,允许我们处理更小、更易于维护且可在不同地方重用的部分。

每个应用程序都必须有一个组件,我们称之为 appcomponent 或根组件。真实的 Angular 应用本质上是一个组件树,以 appcomponent 为起点。

模块

模块是相关组件的容器。每个 Angular 应用至少有一个模块,我们称之为 app module。随着应用程序的增长,我们可能希望将模块分解成更小、更易于维护的模块。随着应用程序的增长,我们需要将我们的 app module 分解成更小的子模块,每个模块负责特定的部分。它们里面有相关的组件。

组件

让我们从一些实际操作开始。

实际上,我们需要遵循 3 个步骤

  1. 创建组件。
  2. 在模块中注册组件。
  3. 将元素添加到 HTML 标记中。

打开 Visual Studio Code 并构建项目。

PS > ng serve

在浏览器中打开 URL (https://:4200/)。

现在让我们创建组件。

创建组件

在 Visual Studio Code 中打开文件面板,然后在项目目录中,打开 'src' 文件夹 > 'app' 文件夹

在这里,我们想添加一个显示courses的组件。所以,我们创建一个文件并将其命名为 'courses.component.ts'。这是我们使用 Angular 应用程序时的约定。如果组件有多个名称,如 'course form',那么我们将用连字符 'course-form.component.ts' 来分隔它们。

在这里,我们开始在 'courses.component.ts' 中创建一个纯粹的 TypeScript 类。

class CoursesComponent { 

}

所以为了让 Angular 识别这个类,我们需要导出它。

export class CoursesComponent {

}

到目前为止,我们有这个纯粹的 TypeScript 类。它还不是一个组件。为了将其转换为组件,我们需要为其添加一些 Angular 可识别的元数据。我们通过装饰器来实现这一点。在 Angular 中,我们有一个名为 component 的装饰器,可以将其附加到类上,从而使该类成为一个组件。所以我们需要在顶部导入这个装饰器。

正如我们所见,这个@Component()装饰器函数需要 1 个参数。在这里,我们将创建一个对象。在这个对象中,我们将创建 1 个或多个属性来告诉这个组件如何工作。例如,我们经常使用的一个属性是selector,我们将其选择为 CSS 选择器。如果我们想引用一个元素,比如

元素标签 选择符
<courses> “courses”
<div class=”courses”> “.courses”
<div id=”courses”> “#courses”

所以在这里,我们想引用一个名为<courses>的元素,因为通过组件,我们可以扩展 HTML 的词汇。所以我们可以定义新元素,如courses,在其中,我们将有课程列表,或者将来,我们可以定义一个自定义元素,一个自定义 HTML 元素,名为<rating>。所以最终,我们的组件选择器是courses。而模板是我们通过调用该模板在网页上渲染的标记。

// Import Component Decorator
import { Component } from '@angular/core';
// Apply Decorator Function to the Typescript class
@Component({
    selector: 'courses',
    template: '<h2>Angular</h2>'
})
export class CoursesComponent {
}

所以这就是 Angular 中的基本组件。

在模块中注册组件

现在第二步是在模块中注册组件。目前,我们只有一个名为 'appmodule' 的模块。

这里我们有 3 个import语句和底部 1 个export语句。请注意,这个类被另一个名为@NgModule的装饰器函数装饰。现在不要担心装饰器中使用的属性。我们稍后会讨论它们。这里,我们只关注declarations,这是我们添加属于该模块的所有组件的地方。所以默认情况下,当我们生成一个应用程序时,我们有一个名为appcomponent的组件,我们可以在 app module 中看到它。在这里,我们需要添加我们的自定义组件到 declarations 中,如果你使用 VS Code,那么我们有一个扩展(自动导入)。它会在你创建任何类对象时自动导入头部引用语句,并且当你提供引用名称时,就像我们在 declarations 中那样。

import { CoursesComponent } from './courses.component';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

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

将元素添加到 HTML 标记中

现在,是时候在 HTML 文件中使用组件了。从src >app>app.component.html打开app.component.html文件。

这个 HTML 文件实际上是用于在浏览器中打开 localhost:4200 时渲染主页的。现在只需注释掉这个 HTML 代码,然后让我们在 HTML 中尝试我们的组件。

<h1>My First App With</h1>
<courses></courses>

当 Angular 看到这个自定义元素时,它会渲染我们的courses组件的模板。现在只需保存文件,webpack 就会自动执行。你可以在网页上看到区别。

看,它是这样工作的。如果我们检查浏览器中的元素,那么你就会知道 Angular 应用程序的结构以及它在后台如何工作。

现在你可能会想,如果你仔细查看上图,你会看到

<h1 _ngcontent-c0>My First App With</h1>

ngcontent是从哪里来的?所以,现在打开你src文件夹中的index.html页面,你会看到<app-root></app-root>自定义 HTML 元素,它正在使用我们的appcomponent,你可以验证它。只需打开app.component.ts,在这里,你会看到

selector: ‘app-root’

所以每当 Angular 看到这样的元素时,它就会在这个元素内部渲染该组件的模板。

Point

你可能会想到视图,我们有应用程序中的不同 HTML 页面。我们在src文件夹中有一个index.html页面,我们也有自定义组件的 HTML 文件。而且我们也有@Component({ template: ‘’ })装饰器来在其中编写 HTML。实际上,当我们运行应用程序时,我们的主视图实际上是index.html,在那里我们使用了<app-root></app-root> app component。如果你在app.component.ts中,你定义了app.component.htmltemplateUrl,而在app.component.html中,我们使用了CoursesComponent的选择器<courses></courses>

并且我们已经编写了我们的CoursesComponent,并在上面定义了它的选择器,即courses

使用 Angular CLI 生成组件

现在,对于我们之前讨论的创建自定义组件的方法,有两个问题。

  1. 这种方法有点繁琐。我们需要记住很多步骤。
  2. 如果我们忘记了第二步(将组件注册到appmodule),那么我们的应用程序就会崩溃。

我们可以做一个实验,针对appmodule - 只需从app.module.ts中的 declaration 中删除CoursesComponent

现在保存文件并打开 URL (localhost:4200/),你将看到一个空白的白色浏览器屏幕。现在,打开浏览器控制台。

在这里,你会看到错误。

现在让我们以更快、更可靠的方式创建 Angular 组件。在这里,我们使用 Angular CLI 来生成组件。打开 VS Code 终端。

就像我们使用ng new命令创建新应用程序一样,我们也可以使用ng来生成组件

语句语法:ng g c nameofcomponent

g代表生成,c代表组件,然后是组件的名称。让我们创建一个名为course的组件。

PS > ng g c course

看看 Angular CLI 是如何创建名为course的目录,然后在该目录中创建 4 个文件的。(.css)文件用于组件样式,(.html)用于 HTML,(.spec.ts)用于组件单元测试,(.ts)是实际的组件文件。另一件重要的事情是,当我们创建组件时,它会自动更新我们的 app module 并在此处注册我们的新组件。让我们在 app module 中验证自动更新。

现在打开 app module。

看,它已经自动更新了。你可以看到,这大大减少了工作量。如果你打开 course component

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-course',
  templateUrl: './course.component.html',
  styleUrls: ['./course.component.css']
})

export class CourseComponent implements OnInit {
  constructor() { }
  ngOnInit() {
  }
}

这里是所有的样板代码。这就是 Angular CLI 如何为我们节省大量时间和精力。

模板

组件可以封装视图的数据、逻辑、HTML 标记。如果我们打开coursescomponent,这里只有 HTML 标记,但没有数据或逻辑。所以,让我们扩展这个例子。

import { Component } from '@angular/core';
@Component({
    selector: 'courses',
    template: '<h2>Angular</h2>'
})

export class CoursesComponent {
}

现在我们想要的是将数据封装在容器中,并在模板标记中动态显示。在模板中,我们使用双大括号语法来封装数据,当运行时数据发生变化时,Angular 会自动在浏览器视图中更新。

import { Component } from '@angular/core';
@Component({
    selector: 'courses',
    template: '<h2>{{ name }}</h2>'
})

export class CoursesComponent {
    name = "My Name Is Usama";
}

这就是我们所说的数据绑定

我们不仅可以将变量放在 HTML 模板中,还可以编写简单的 JavaScript 表达式,并且还可以调用方法。让我们举个例子。

在 JavaScript 中,我们可以在模板中添加string并连接string

import { Component } from '@angular/core';
@Component({
    selector: 'courses',
    template: '<h2>{{ "My Name Is: " + name }}</h2>'
})

export class CoursesComponent {
    name = "Usama";
}

我们也可以在模板中调用函数。

import { Component } from '@angular/core';
@Component({
    selector: 'courses',
    template: '<h2>{{ "My Name Is: " + myName() }}</h2>'
})

export class CoursesComponent {
    name = "Usama";
    myName(){
        return this.name;
    }
}

这就是我们在模板标记中进行的操作,称为string插值。

指令

现在让我们显示courses列表。

export class CoursesComponent {
    name = "Usama";
    courses = ["BIO", "MTH", "CHE", "PHY"];
}

要在模板中显示这个课程数组,我们需要做一些修改。

  • 将单引号()更改为反引号(`),用于模板。

现在使用反引号的好处是我们可以在模板中将模板分成多行,使其更易读。

现在让我们在模板的ul中打印课程的名称

@Component({
    selector: 'courses',
    template: `
    <h2>{{ name }}</h2>
    <ul>
        <li></li>
    </ul>
    `
})

在这里,我们需要循环遍历我们的courses数组,以便在单个li中打印它们。在这里,我们使用指令。

指令用于操作 DOM。我们可以使用它们来添加 DOM 元素、删除现有 DOM 元素、更改 DOM 元素的类或样式等等。

在这里,我们使用指令ngFor

import { Component } from '@angular/core';
@Component({
    selector: 'courses',
    template: `
    <h2>{{ "List of Courses" }}</h2>
    <ul>
        <li *ngFor="let course of courses">
            {{ course }}
        </li>
    </ul>
    `
})

export class CoursesComponent {
    courses = ["BIO", "MTH", "CHE", "PHY"];
}

这是 Angular 的特殊语法,我们像在 JavaScript 或 C# 中那样迭代 courses 数组foreach循环。然后,我们在string插值中显示数据。

这里,我们在屏幕上显示了数据。但是请记住,它显示在屏幕上是因为我在app.component.html中使用了 courses 组件选择器。

<courses></courses>

服务

在我们实际的应用程序中,数据显然来自服务器。这里是 Angular 中的service,用于了解如何在 Angular 中使用来自服务器的数据。

这里我们有两个选项

  1. 在组件中编写调用 HTTP 服务的逻辑

    但是这种方法有一些问题。第一个问题是,这个逻辑与该组件和该 HTTP 端点是紧密耦合的。将来,当我们在此类中编写单元测试时,我们不想依赖于实时 HTTP 端点,因为这会使执行单元测试变得更加困难。

    所以我们需要制作一个 HTTP 服务的假 HTTP 实现。

    第二个问题是,应用程序中的其他地方可能需要显示课程列表,比如在仪表板、管理面板或主页上。使用这种实现,我们需要在多个地方使用我们的 HTTP 服务。

    第三个问题是,组件不应包含除表示逻辑以外的任何逻辑。细节应该委托给应用程序中的其他地方。

  2. 所以解决方案是,制作一个单独的服务类,我们在其中编写检索数据的逻辑,然后在应用程序的多个地方重用这个类。

    app文件夹中添加新文件courses.service.ts

    再一次,我们导出 TypeScript 类。但是我们没有用于服务类的装饰器。它们只是纯粹的 Angular 类。

    export class CoursesService {
        getCourses() {
            return ["BIO", "MTH", "CHE", "PHY"];
        }
    }

    现在回到组件,这里我们不打算使用我们的 HTTP 服务,这允许我们在不依赖 HTTP 端点的情况下进行单元测试。所以在单元测试这个类时,我们可以提供服务的假实现。但这相当复杂,我们稍后会看到。

依赖注入

现在,我们有了从服务器获取课程列表的服务。我们需要在CoursesComponent中使用这个服务。所以,首先,我们需要在这里的组件类中添加构造函数。通过构造函数,我们初始化一个对象。所以,这里,我们需要在构造函数中创建一个服务的实例。如果你不使用 VS Code 的自动导入插件,你需要手动导入服务文件到CoursesComponent

然后,我们需要在构造函数中的service方法中初始化CoursesComponentcourses

import { Component } from '@angular/core';
import { CoursesService } from './courses.service';
@Component({
    selector: 'courses',
    template: `
    <h2>{{ "List of Courses" }}</h2>
    <ul>
        <li *ngFor="let course of courses">
            {{ course }}
        </li>
    </ul>
    `
})
export class CoursesComponent {
    courses;

    constructor(){
        let service = new CoursesService();
        this.courses = service.getCourses();
    }
}

现在让我们测试应用程序,看看发生了什么。是的,它工作正常。

然而,这种实现存在一个问题,第一个问题是CoursesComponentCoursesService是紧密耦合的,如果它们相互紧密耦合。我们无法对其进行单元测试。主要问题是我们正在构造函数中创建CoursesService()对象。第二个问题是,如果在将来,我们决定向CoursesService的构造函数添加参数,我们就必须回到这里,并且在应用程序中任何使用CoursesService的地方,我们都需要在那里添加一个新的参数。所以每次我们在CoursesServices中添加新参数时,我们都需要在整个应用程序中同时添加其他更改。

我们该怎么做?

与其重新创建CoursesService的实例,不如让 Angular 为我们做。所以删除对象创建行,并这样进行更改。

export class CoursesComponent {
    courses;

    constructor(service: CoursesService){
        this.courses = service.getCourses();
    }
}

通过这样做,Angular 将会创建一个CoursesComponent的实例,它会查看这个构造函数,并且看到这个构造函数依赖于CoursesService类型。所以,它首先会自动创建一个CoursesService的实例并将其传递给这个构造函数。现在,如果你对CoursesService构造函数进行任何更改,我们就不需要修改整个应用程序中的所有更改。第二种实现的优点是,当我们对CoursesComponent进行单元测试时,而不是向该构造函数提供实际的课程,我们可以创建一个服务的假实现,该实现不使用后端的 HTTP 服务。换句话说,我们已经将courses组件与courses服务解耦。

所以教训是,如果我们正在函数或另一个类中创建类的一个实例,那么我们将这个类与那个类紧密耦合。我们无法在运行时更改它,但当你将该依赖项作为构造函数的参数添加时,我们就将该类与该依赖项解耦了。

现在还没完,我们还需要明确指示 Angular 创建CoursesService的实例并将其传递给我们的CoursesComponent。这个概念称为依赖注入。所以我们应该指示 Angular 将组件的依赖注入到其构造函数中。

很多人认为依赖注入非常复杂,但它实际上是一个 25 美元术语,代表 5 美分的概念。所以依赖注入意味着将类的依赖项注入或提供到其构造函数中。

Angular 作为一个内置的 DI(依赖注入)框架。所以当我们创建组件的实例时,它可以注入依赖项,但为了使其正常工作,我们需要注册依赖项。

(service: CoursesService)

在我们模块的某个地方。现在打开app.module.ts,看看这个NgModule声明器,这里我们有一个名为 providers 的属性,它被设置为一个空数组。在这个数组中,我们需要注册该模块中的组件所依赖的所有依赖项,即CoursesComponent依赖于CoursesService,所以我们需要在模块中将CoursesService注册为提供者。

如果你使用自动导入,它会自动将CoursesService类的引用添加到此文件中。

如果你忘记了将引用添加到 providers 数组的这一步,那么它将不起作用。你可以通过注释掉或从 providers 数组中删除此项来自己进行实验,并在浏览器中运行 URL,当你打开控制台时,你会看到错误。

实际上,后台发生了什么?

当你在模块中注册依赖项提供者时,Angular 会为该整个模块创建一个该类的单个实例。所以想象一下,在这个模块中,我们有 100 个组件,其中一半需要CoursesService。在内存中,我们只有一个CoursesService的实例,Angular 会将同一个实例传递给所有这些组件。这就是我们所说的单例模式

所以给定对象的单个实例存在于内存中。

使用 Angular CLI 生成服务

现在让我告诉你使用 Angular CLI 生成服务的快速方法。在 Visual Studio Code 中打开终端窗口。

语句ng g s NameOfService

它为我们生成了 2 个文件,1 个是实际的服务文件,另一个(.spec.ts)包含用于编写该服务的单元测试的样板代码。

这里有一个我们以前没见过的新东西:@Injectable声明器函数。只有当服务在其构造函数中有依赖项时,我们才需要这个装饰器函数,也就是说。

我们在构造函数中有logService的依赖。

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root'
})

export class EmailService {
  constructor(log: LogService) { }
}

在这种情况下,我们需要将这个Injectable()函数应用于该类,这就告诉 Angular,这是一个可注入类,这意味着 Angular 应该能够将其类依赖项注入到其构造函数中。现在,我们在定义组件时没有使用这个装饰器,因为当我们使用组件装饰器时,该装饰器内部包含了 Injectable 装饰器。属性...

providedIn: ‘root’

...意味着该服务应该由根应用程序注入器创建。

我们学到了什么

让我们更实际一些,做一个新的。在这里,我们将通过 CLI 生成的组件和服务的组合来探索这个结果

所以,首先,让我们通过 Angular CLI 创建组件和服务

PM > ng g c author
PM > ng g s author

首先,让我们为 author service 编写代码。

@Injectable({
  providedIn: 'root'
})

export class AuthorService {
  constructor() { }
  getAuthors(){
    return ["Bob", "Adam", "Joff", "Scott"];
  }
}

组件会自动在app.module.ts中注册,但如果我们想注册authorservice,那么我们需要手动在这里注册服务。

@NgModule({
  declarations: [
    AppComponent,
    CoursesComponent,
    CourseComponent,
    AuthorComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [CoursesService, AuthorService],
  bootstrap: [AppComponent]
})

export class AppModule { }

现在是时候在我们的组件中使用author服务了。

@Component({
  selector: 'app-author',
  templateUrl: './author.component.html',
  styleUrls: ['./author.component.css']
})

export class AuthorComponent implements OnInit {
  authors;

  constructor(author: AuthorService) {
    this.authors = author.getAuthors();
  }

  ngOnInit() {
  }
}

这里Component装饰器有templateUrlauthor.component.html,现在进入author.component.html

<h2> {{ authors.length }} Authors</h2>
<ul>
  <li *ngFor="let author of authors">
    {{ author }}
  </li>
</ul>

.length属性是一个 JavaScript 属性,通过它可以获得数组的总数。而在<li>中,我们使用了ngFor装饰器,因为它改变了我们的 DOM,所以它前面加上了星号(*)。

现在,我们知道我们的基础组件是app。我们在基础组件中使用我们的子组件。所以进入app.component.html,然后放置你的author组件选择器来使用它。

<app-author></app-author>

结论

这里,我们讨论了 Angular 的构建块。组件是我们编写逻辑、定义选择器和 HTML 标记的地方,服务只不过是我们获取数据并在组件中使用的东西。这里,我们讨论了如何消除类之间的紧密耦合并在它们之间注入依赖项。我们学习了如何通过 Angular CLI 创建组件和服务,以使我们的代码更少出错,并在几秒钟内准备好一切。这就是我们在 Angular 中的工作方式。

© . All rights reserved.