Angular-和-BootStrap-Web-开发第三版-四-

44 阅读1小时+

Angular 和 BootStrap Web 开发第三版(四)

原文:zh.annas-archive.org/md5/C3E0BC11B26050B30F3DD95AAA2C59BD

译者:飞龙

协议:CC BY-NC-SA 4.0

第十三章:单元测试

您可能已经为传统的服务器端代码编写了单元测试,比如 Java、Python 或 C#。当然,在客户端,单元测试同样重要,在本章中,您将了解 Angular 测试,包括 Jasmine 和 Karma 框架,这两个优秀的工具用于对客户端代码进行单元测试。

我们将一起探讨如何对 Angular 应用的各个部分进行单元测试,例如组件、路由和依赖注入(DI)。

本章将涵盖以下主题:

  • Jasmine 和 Karma 简介

  • 测试指令

  • 测试组件

  • 测试路由

  • 测试依赖注入

  • 测试 HTTP

测试框架简介

在本节中,我们将学习两个重要的测试框架,即 Jasmine 和 Karma。

测试和开发本身一样重要。这是一个备受争议的话题,一些专家认为测试驱动开发(TDD)非常重要,这意味着在编写开发代码之前编写测试脚本非常重要。

Angular 框架的美妙之处在于它原生支持测试框架,并提供了许多测试工具,使开发人员的工作变得轻松愉快。我们一点也不抱怨。

Angular 为我们提供了一个核心测试模块,其中有很多我们可以利用的优秀类,并且原生支持两个重要的测试框架,即 Jasmine 和 Karma:

  • 我们使用 Jasmine 框架编写我们的测试脚本。

  • 我们使用 Karma 框架来执行测试脚本。

关于 Jasmine 框架

Jasmine 是一个领先的开源测试框架,用于编写和测试现代 Web 框架的自动化测试脚本。

当然,对于 Angular 来说,Jasmine 已经成为事实上的首选框架。以下摘自官方网站:

"Jasmine 是一个用于测试 JavaScript 代码的行为驱动开发框架。它不依赖于任何其他 JavaScript 框架。它不需要 DOM。它有一个清晰明了的语法,让您可以轻松编写测试。"

编写 Jasmine 测试脚本的理念是基于行为和功能驱动的。测试脚本有两个重要的元素——describe和规范(it):

  • describe函数用于将相关的规范分组在一起。

  • 规范是通过调用it函数来定义的。

以下是一个用 Jasmine 编写的示例测试脚本:

describe("Test suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

在编写测试规范的过程中,我们必须使用大量的条件检查来匹配数据、元素、结果、断言条件等等。Jasmine 框架提供了许多匹配器,我们可以在编写测试规范时方便地使用。在前面的示例代码中,toBe 就是一个匹配器的例子。

以下是 Jasmine 中最常用的匹配器列表:

  • 等于

  • 为真

  • 为假

  • 大于或等于

  • 小于或等于

  • 已调用

  • 具有类

  • 匹配

我们将在接下来的几节中学习如何使用这些匹配器。好的,我们已经编写了我们的测试规范,那么现在怎么办?我们如何运行它们?谁会为我们运行它们?答案可以在下一节找到。

关于 Karma 框架

Karma 是一个测试运行器框架,用于在服务器上执行测试脚本并生成报告。

以下内容来自官方网站:

“Karma 本质上是一个工具,它生成一个 Web 服务器,针对每个连接的浏览器执行源代码与测试代码。针对每个浏览器的每个测试的结果都会被检查,并通过命令行显示给开发人员,以便他们可以看到哪些浏览器和测试通过或失败。”

Karma 框架被添加到我们的依赖列表中,因为它包含在 Angular CLI 安装中。在我们继续编写和执行测试脚本之前,验证我们是否已在package.json文件中正确安装了 Jasmine 和 Karma 是一个良好的实践。我们还可以验证正在使用的库的版本号。

我敢打赌你已经猜到这也是指定要使用的 Jasmine 和 Karma 的特定版本的地方。

在下面的截图中,我们可以验证我们已将 Jasmine 和 Karma 添加到package.json文件中的devDependencies列表中:

太好了。现在,是时候深入了解 Angular 测试概念并编写一些测试脚本了。

Angular 测试自动化

我相信你会同意测试自动化是产品开发中最重要的方面之一。在前面的部分中,我们探讨了 Jasmine 和 Karma 框架。在接下来的部分中,我们将通过一些实际示例来学习如何自动化各种 Angular 框架构建模块。我们将学习如何测试 Angular 组件、指令、路由等等。让我们开始吧。

测试 Angular 组件

在使用 Angular CLI 的过程中,我们已经生成了多个组件和服务。暂停一下,查看文件和文件夹结构。您会注意到,对于每个组件和服务,都生成了一个.spec.ts文件。

恍然大悟!Angular CLI 一直在为相应的组件和服务生成所需的外壳测试脚本。让我们在这里进行一个快速的实践练习。让我们生成一个名为auto-list的组件:

ng g component auto-list

Angular CLI 会自动生成所需的文件,并在所需的文件(AppModuleAngular.json等)中进行条目。

以下截图描述了 CLI 生成的测试规格:

仔细看一下生成的文件。您会看到为组件生成了以下文件:

  • auto-list.component.html

  • auto-list.component.spec.ts

  • auto-list.component.ts

  • auto-list.component.scss

我们对 Angular CLI 生成的 spec 文件感兴趣。spec 文件是为相应组件生成的测试脚本。spec 文件将导入基本所需的模块,以及Component类。spec 文件还将包含一些基本的测试规格,可以用作起点,或者作为我们的动力。

让我们更仔细地看一下在 spec 文件中生成的代码:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AutoListComponent } from './auto-list.component';

在上面的代码中,您会注意到所需的模块是从 Angular 测试核心导入的。这当然不是我们将使用的模块的最终列表,而只是基本的起始模块。您还会注意到新创建的组件AutoListComponent也被导入到我们的 spec 文件中,这意味着我们可以在 spec 文件中创建我们类的一个实例,并开始模拟测试目的的对象。很酷,对吧?继续看代码行,我们可以看到以下内容:

describe('AutoListComponent', () => {
    let component: AutoListComponent;
    let fixture: ComponentFixture<AutoListComponent>;
beforeEach(async(() => {
    TestBed.configureTestingModule({
    declarations: [ AutoListComponent]
 })
 .compileComponents();
 }));

beforeEach(() => {
    fixture = TestBed.createComponent(AutoListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
});

在上面的代码中,您会注意到一些关键点。有一个describe语句,用于将相关的测试规格分组在一起。我们将在describe函数内创建测试规格。在 spec 文件中定义了两个beforeEach方法。

第一个beforeEach方法是一个异步 promise,它将设置我们的TestBed,这意味着在继续之前必须解决其中声明的所有内容;否则,我们的测试将无法工作。第二个beforeEach方法将为测试创建一个AutoList组件的实例。您会注意到调用fixture.detectChanges(),这会强制 Angular 的变更检测运行并影响测试中的元素。

现在,是时候了解实际的测试规范了,这是在规范文件中生成的:

it('should create', () => {
 expect(component).toBeTruthy();
 });

正如我们之前提到的,Jasmine 测试规范是写在it语句内的,这种情况下,只是一个简单的断言,用于检查组件是否存在并且为真,使用toBeTruthy匹配器。

这就是我们的规范文件。乐趣在于看到它的工作。让我们运行 Angular 为我们生成的默认测试。要运行 Angular 应用程序中编写的测试,我们在命令行界面上使用ng test命令:

ng test

如果你看到一个新窗口被打开,不要惊慌。您会注意到 Karma 运行器打开了一个新的浏览器窗口来执行测试,并生成了测试执行报告。以下截图显示了为我们的组件生成的测试规范的报告:

测试通过了。现在,让我们稍微修改一下脚本。我们将在组件中创建一个名为title的变量并赋值。在我们的测试规范中,我们将验证该值是否匹配。这是一个直接的用例,相信我,这也是您在应用程序中实现的最常见的用例。让我们打开app.component.spec.ts文件并在测试脚本中进行更改:

it(`should have as title 'testing-app'`, () => {
 const fixture = TestBed.createComponent(AppComponent);
 const app = fixture.debugElement.componentInstance;
 expect(app.title).toEqual('AutoStop');
});

在上面的代码中,我们正在编写一个测试规范,并使用TestBed创建了AppComponent的 fixture 元素。使用 fixture 元素的debugElement接口,我们获取了componentInstance属性。接下来,我们编写了一个expect语句来断言title变量的值是否等于AutoStop。很整洁。让我们尝试再写一个测试规范。我们要解决的用例是:我们有一个H1元素,并且我们想要断言它,如果H1标签内的值等于Welcome to Autostop。以下是相关的示例代码:

it('should render title in a h1 tag', () => {
 const fixture = TestBed.createComponent(AppComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;
 expect(compiled.querySelector('h1').textContent).toContain('Welcome to 
  AutoStop');
});

在上述代码中,我们断言h1元素的textContent是否包含文本Welcome to AutoStop。请注意,在以前的测试规范中,我们使用了componentInstance接口,在这个测试规范中,我们使用了nativeElement属性。再次使用ng test命令运行测试。以下屏幕截图显示了生成的测试报告:

到目前为止,我们已经概述了 Jasmine 和 Karma 框架,还学习了如何运行我们的测试脚本。我们还了解了 Angular 为我们生成的默认 spec 文件,并学习了如何修改测试规范。

在接下来的章节中,我们将学习如何编写测试规范和脚本,以测试 Angular 内置指令、服务、路由等等。

测试指令

Angular 提供了许多内置的强大指令,如ngForngIf等,可以用于扩展原生 HTML 元素的行为和功能。我们在第七章中学习了关于 Angular 模板和指令的知识,快速回顾从未有过害处。Angular 为我们提供了两种类型的指令,我们可以用来开发和扩展元素的行为:

  • 内置指令

  • 自定义指令

本节的重点是学习如何编写用于内置 Angular 指令(如ngIfngForngSwitchngModel)的测试脚本。在开始编写测试用例之前,我们需要做一些准备工作,以更新我们的组件,以便我们可以开始编写测试用例。我们将编写一些变量,用于保存各种类型的数据。我们将使用ngFor在模板中显示数据,并使用ngIf编写一些条件检查。

如果您想快速复习 Angular 模板和指令,请参阅第七章 Templates, Directives, and Pipes

我们将继续使用在上一节中创建的相同组件AutoListComponent。让我们开始吧。我们的起点将是AutoListComponent类,所以让我们修改auto-list.component.ts文件:

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

@Component({
 selector: 'app-auto-list',
 templateUrl: './auto-list.component.html',
 styleUrls: ['./auto-list.component.scss']
})
export class AutoListComponent implements OnInit {

cars = [
 { 'id': '1', 'name': 'BMW' },
 { 'id': '2', 'name': 'Force Motors' },
 { 'id': '3', 'name': 'Audi' }
 ];

 tab = "1";

 constructor() { }

 ngOnInit() {
 }

 findAuto() {
     console.log("Method findAuto has been called");
  }

}

在上面的代码中,我们添加了一个名为cars的 JSON 对象类型的变量,并为其分配了数据。我们将通过在模板中显示数据来使用这些数据。我们还声明了一个名为tab的变量,并分配了一个值1。我们将在模板中使用tab变量进行条件检查。最后,我们添加了一个名为findAuto的方法,并在控制台中显示输出。

我们已经修改了我们的组件类。我们还需要更新我们的模板文件,以便在组件内部处理数据。以下是我们将在模板文件auto-list.component.html中添加的示例代码:

<h4 class="c2">ngFor directive</h4>
<ul class="cars-list">
 <li *ngFor="let car of cars">
 <a [routerLink]="[car.id]">{{ car.name }}</a>
 </li>
</ul>

<h4 class="c1">ngIf directive</h4>
<div *ngIf="cars.length" id="carLength">
 <p>You have {{cars.length}} vehicles</p>
</div>

<h4 class="c3">ngSwitch directive</h4>
<div [ngSwitch]="tab" class="data-tab">
 <p>This is ngSwitch example</p>
 <div *ngSwitchCase="1">ngSwitch Case 1</div>
 <div *ngSwitchCase="2">ngSwitch Case 2</div>
</div>
<hr>

<button (click)="findAuto()" id="btn">Click to findAutoDealers</button>

在上面的代码中,我们正在对模板文件进行更改。首先,我们使用ngFor指令循环行并显示汽车。接下来,我们添加了一个ngIf条件来检查汽车的长度是否大于 0,然后我们将显示carLength元素的计数。我们已经添加了一个ngSwitch指令来检查tab变量的值是否设置,并根据选项卡的值来相应地显示相应的选项卡。在我们的情况下,由于选项卡分配的值为1,我们将显示第一个选项卡。最后,我们添加了一个按钮,并将findAuto方法与单击事件相关联。

很好。我们的组件和模板已经准备好了,现在是时候编写一些良好的测试脚本来测试前面的逻辑,特别是 Angular 内置指令。我们将测试的一些用例包括测试 UI 中显示的汽车数量,测试哪个选项卡是活动的,验证元素内的内容等等。以下是一些用例,并且我们将学习如何为这些用例编写测试脚本:

用例#1:我们有一列汽车,我们想要验证总数为3

// ngFor test case to test the count is 4
 it('Should have 3 Brands coming from ngFor directive', async(() => {
 const fixture = TestBed.createComponent(AutoListComponent);
 fixture.detectChanges();
 const el = fixture.debugElement.queryAll(By.css('.cars-list > li'));
 expect(el.length).toBe(3);
 }));

在上面的代码中,我们正在创建AutoListComponent组件的 fixture。我们已经学会了如何使用debugElement来定位元素,并且在这个测试规范中,我们使用queryAll方法来获取具有className .cars-list > li的元素列表。最后,我们编写了一个expect语句来断言总数是否等于3

使用ng test命令运行测试。我们应该看到以下输出:

用例#2:我们要验证 HTML 元素内的文本是否包含vehicles键盘:

// ngIf test script
 it('Test ngIf directive in component', async(() => {
 const fixture = TestBed.createComponent(AutoListComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;
 const el = compiled.querySelector('#carLength');
 fixture.detectChanges();
 const content = el.textContent;
 expect(content).toContain('vehicles', 'vehicles');
 }));

在上述代码中有一些重要的事情需要注意。我们继续使用组件AutoListComponent的相同装置元素。这一次,我们使用debugElement接口,使用querySelector方法来查找具有标识符carLength的元素。最后,我们编写一个expect语句来断言文本内容是否包含vehicles关键字。

让我们再次使用ng test命令运行测试。我们应该看到以下输出:

**用例#3:**我们想使用ngSwitch来验证是否选择了tab1,如果是,则显示相应的 div:

// ngSwitch test script
 it('Test ngSwitch directive in component', async(() => {
 const fixture = TestBed.createComponent(AutoListComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;
 const el = compiled.querySelector('.data-tab > div');
 const content = el.textContent;
 expect(content).toContain('ngSwitch Case 1');
 }));

在上述代码中,我们继续使用AutoListComponent组件的 fixture 元素。使用debugElementquerySelector方法,我们正在使用className '.data-tab > div'来定位元素。我们断言ngSwitch条件是否为true,并显示相应的div。由于我们在组件中将选项卡的值设置为1,因此选项卡 1 显示在屏幕上,并且测试规范通过:

**用例#4:**测试AutoListComponent中定义的方法,并断言该方法是否已被调用:

// Test button is clicked
 it('should test the custom directive', async(() => {
 const fixture = TestBed.createComponent(AutoListComponent);
 component = fixture.componentInstance;
 fixture.detectChanges();
 spyOn(component, 'findAuto');
 component.findAuto();
 expect(component.findAuto).toHaveBeenCalled();

}));

在上述代码中,我们正在创建AutoListComponent组件的 fixture。我们使用spyOn方法来监听组件实例。我们正在调用findAuto()方法。最后,我们编写一个expect语句来断言findAuto方法是否已被调用,使用toHaveBeenCalled

使用ng test命令运行测试。我们应该看到以下输出:

在本节中,我们学习了如何编写单元测试脚本来测试 Angular 内置指令,例如ngForngIfngSwitch,最后,断言方法是否被点击和调用。

在下一节中,我们将学习有关测试 Angular 路由的知识。

测试 Angular 路由

很可能,您的应用程序中会有多个链接,以导航菜单或深链接的形式存在。这些链接在 Angular 中被视为路由,并且通常在您的app-routing.module.ts文件中定义。

我们在第四章中学习并掌握了如何使用 Angular 路由。在本节中,我们将学习如何编写用于测试 Angular 路由和测试应用程序中的链接和导航的测试脚本。

我们的应用程序需要一个漂亮的menu组件。使用ng generate component menu命令,我们将生成menu组件。现在,让我们转到menu.component.html并创建一个名为navbar的菜单,其中包含两个链接:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
 <a class="navbar-brand" href="#">AutoStop </a>
 <button class="navbar-toggler" type="button" data-toggle="collapse" 
    data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" 
    aria-expanded="false" aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
 </button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
 <ul class="navbar-nav mr-auto">
 <li class="nav-item active">
 <a class="nav-link" routerLink="/list-cars">Cars <span class="sr-only">
   (current)</span></a>
 </li>
 <li class="nav-item">
 <a class="nav-link" routerLink="/list-trucks">Trucks</a>
 </li>
 </ul>
 </div>
</nav>

前面的代码并不花哨,至少目前还不是。这是使用 Bootstrap 生成navbar组件的标准代码。仔细看,你会发现我们在菜单栏中定义了两个链接,list-carslist-trucks,它们的类是nav-link

现在我们可以围绕菜单功能编写一些测试规范,以测试navbar组件,其中将涵盖导航、链接计数等。

用例#1:我们需要测试navbar菜单是否恰好有两个链接。

以下是检查是否有确切两个链接的代码:

// Check the app has 2 links
 it('should check routerlink', () => {
 const fixture = TestBed.createComponent(MenuComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;

let linkDes = fixture.debugElement.queryAll(By.css('.nav-link'));
 expect(linkDes.length).toBe(2);

});

在前面的代码中,我们正在为我们的MenuComponent组件创建一个固定装置。由于我们分配了nav-link类,因此很容易定位组件中对应的链接。使用debugElementqueryAll方法,我们正在查找所有类名为nav-link的链接。最后,我们正在编写一个expect语句来断言返回的链接数组的长度是否等于2

使用ng test命令运行测试。我们应该会看到以下输出:

这是测试我们菜单功能的一个良好开端。现在我们知道我们的菜单中有两个链接,我们想要测试的下一个用例是第一个链接是否为list-cars

以下是测试链接数组中第一个链接是否为list-cars的代码:

// Check the app has first link as "List Cars"
 it('should check that the first link is list-cars ', () => {
 const fixture = TestBed.createComponent(MenuComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;

 let linkDes = fixture.debugElement.queryAll(By.css('.nav-link'));

 expect(linkDes[0].properties.href).toBe('/list-cars', '1st link should  
    go to Dashboard');
 });

在前面的代码中,我们正在为我们的MenuComponent组件创建一个固定装置。使用debugElementqueryAll方法,我们正在查找所有类名为nav-link的链接。我们将获得所有具有类名nav-link的链接。菜单中可能有多个链接,但我们感兴趣的是通过index [0]读取第一个元素的href属性,并断言该值是否匹配/list-cars

再次运行ng test命令。我们应该会看到我们的测试报告已更新,如下图所示:

好的,公平的。我们得到了一个线索,即list-cars菜单链接是菜单列表中的第一个。如果我们不知道我们正在搜索的链接的索引或位置会怎么样?让我们也解决这个用例。

看一下以下代码片段:

// Check the app if "List Cars" link exist
 it('should have a link to /list-cars', () => {
 const fixture = TestBed.createComponent(AppComponent);
 fixture.detectChanges();
 const compiled = fixture.debugElement.nativeElement;
 let linkDes = fixture.debugElement.queryAll(By.css('.nav-link'));
 const index = linkDes.findIndex(de => {
 return de.properties['href'] === '/list-cars';
 });
 expect(index).toBeGreaterThan(-1);
 });

需要注意的一些事情是,我们正在查找路由路径/list-cars的索引,并且我们还在使用分配的类nav-link,并使用queryAll方法获取所有匹配元素的数组。使用findIndex方法,我们正在循环数组元素以找到匹配href/list-cars的索引。

再次使用ng test命令运行测试,更新后的测试报告应如下所示:

在本节中,我们学习了各种方法来定位路由链接。同样的原则也适用于查找深链接或子链接。

这就是你的作业。

测试依赖注入

在之前的章节中,我们学习了如何编写测试脚本来测试 Angular 组件和路由。在本节中,我们将学习如何测试依赖注入以及如何测试 Angular 应用程序中的服务。我们还将学习如何将服务注入到 Angular 组件中,并编写测试脚本来测试它们。

什么是依赖注入?

依赖注入DI)在 Angular 框架中是一个重要的设计模式,它允许在运行时将服务、接口和对象注入到类中,从而实现灵活性。

DI 模式有助于编写高效、灵活、可维护的可测试和易于扩展的代码。

如果你需要快速回顾,请转到第十一章,依赖注入和服务,其中深入介绍和解释了 DI 机制。

测试 Angular 服务

在本节中,我们将学习如何通过服务和接口测试 Angular 依赖注入。为了测试一个 Angular 服务,我们首先需要在我们的应用程序中创建一个服务!

在 Angular CLI 中使用ng generate命令,我们将在项目文件夹中生成服务:

ng generate service services/dealers

成功执行后,我们应该看到以下文件已被创建:

  • services/dealers.service.spec.ts

  • services/dealers.service.ts

现在我们已经生成了我们的经销商服务和相应的测试规范文件,我们将在服务中添加一些方法和变量,以便在我们的测试规范中使用它们。导航到我们的服务类并更新dealers.service.ts文件。更新后的代码应如下所示:

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

@Injectable({
  providedIn: 'root'
})
export class DealersService {
  dealers: any;

  constructor(private http : HttpClient) { }

  getDealers(){
    this.dealers = [
      { id: 1, name: 'North Auto'},
      { id: 2, name: 'South Auto'},
      { id: 3, name: 'East Auto'},
      { id: 4, name: 'West Auto'},
    ];

    return this.dealers;
  }

}

在上述代码中,我们进行了简单的更改,以便我们可以围绕经销商服务编写一些测试规范。我们定义了一个any类型的变量。我们正在定义一个getDealers方法,它将返回一个带有idname键对的 JSON 响应。好了,现在让我们想出一些用例来编写我们的测试脚本,比如获取经销商的数量,查找匹配的经销商等。

使用案例#1:当调用getDealers方法时,它应返回经销商列表,计数应等于4

以下是此测试规范:

it('Test Dependency Injection to get 4 dealers', () => {
const service: DealersService = TestBed.get(DealersService);
let dealers = service.getDealers();
expect(dealers.length).toBe(4);
});

使用案例#2:我们想要检查第一个经销商的名称是否为North Auto

以下是此测试规范:

it('Test if the first Dealer is North Auto', () => {
const service: DealersService = TestBed.get(DealersService);
let dealers = service.getDealers();
expect(dealers[0].name).toBe('North Auto');
});

太棒了!到目前为止,一切顺利。因此,我们已经学会了如何为我们新创建的经销商服务编写测试规范。这只是依赖注入的一部分。作为依赖注入的一部分,我们可能需要在运行时将其他所需的类注入到服务中。

让我们快速创建一个名为Dealers的类,并在其中定义两个变量,即usernamename。现在,让我们将此文件保存为dealers.ts

export class Dealers {

 constructor(
  public username: string = '',
  public name: string = ''
 ) {};

}

我们现在将在我们的经销商服务中包含新创建的类,并创建一个方法来初始化该类并创建一个对象来返回一些数据:

getDealerObject()
 {
 this.dealerObj= new Dealers('World','Auto');
 return this.dealerObj;
 }

这将引出我们下一个要测试的用例。

使用案例#3:测试通过已注入到服务中的类进行依赖注入。

看一下以下代码:

 it('Test if the dealer returned from object is World Auto', () => {
 const service: DealersService = TestBed.get(DealersService);
 let dealerObj = service.getDealerObject();
 expect(dealerObj.name).toBe('Auto');
 });

在上述代码中,我们创建了我们服务的一个实例并调用了getDealerObject()方法。我们断言返回的值是否与响应的name属性匹配Auto

我们正在调用服务中定义的方法,该方法在内部依赖于Dealers类。

使用案例#4:如果我们只想测试Dealers类的属性怎么办?

我们也可以测试。以下是此示例代码:


it('should return the correct properties', () => {
var dealer = new Dealers();
dealer.username = 'NorthWest';
dealer.name = 'Auto';

expect(dealer.username).toBe('NorthWest');
expect(dealer.name).toBe('Auto');

});

现在,让我们运行ng test命令。我们应该看到以下输出:

在同一行上,您可以编写测试脚本来测试您的服务、依赖类或接口类。

用例#5:在组件内测试 Angular 服务。

我们将继续测试 Angular 依赖注入。这一次,我们将把我们的服务导入到组件中,并验证它是否按预期工作。

为了实现这个用例,我们需要对AutoListComponent进行更改。

看一下我们将在auto-list.component.ts文件中进行的更改:

import { DealersService } from '../services/dealers.service';
constructor(private _dealersService : DealersService) { }
findAuto() {
 this.dealers = this._dealersService.getDealers();
 return this.dealers;
 }

在上面的代码中,我们将服务商服务导入到组件中。我们在构造方法中创建了服务的实例。我们添加了一个findAuto方法,它使用class _dealersService服务的实例调用getDealers方法。为了在我们的组件中测试服务,让我们通过添加以下代码修改auto-list.component.spec.ts文件:

import { DealersService } from '../services/dealers.service';
beforeEach(() => {
 fixture = TestBed.createComponent(AutoListComponent);
 component = fixture.componentInstance;
 fixture.detectChanges();
 service = TestBed.get(DealersService);
 });

在上面的代码中,我们已经将我们的服务商导入到AutoListComponent的测试规范文件中。我们在beforeEach方法中使用TestBed创建了服务的实例。现在我们可以开始编写我们的测试规范,以测试服务。在auto-list.component.spec.ts中添加以下代码:

it('should click a button and call method findAuto', async(() => {
    const fixture = TestBed.createComponent(AutoListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    spyOn(component, 'findAuto');
    let dealers = component.findAuto();
    expect(dealers.length).toEqual(4);

  }));

在上面的代码中,使用组件的实例,我们调用findAuto方法,它将从服务返回数据。它期望计数等于4

使用ng test命令运行测试。我们应该看到以下输出:

在本节中,我们学习了各种测试 Angular 依赖注入的技术,包括服务、依赖类和在 Angular 组件内测试服务。

测试 HTTP

在第十二章中,集成后端数据服务,我们学习了如何集成后端服务,还学习了HTTPModuleHTTPClient。我们还学习了如何向服务器发出 HTTP 请求并处理响应。

在本节中,我们将学习如何编写测试脚本来测试 HTTP 请求和响应。我们将继续使用本章中创建的同一个项目——AutoStop 项目。在我们进一步进行之前,有必要准备好 REST API 端点,以便我们可以在我们的应用程序中使用它们。

我们将学习如何使用公共 API https://jsonplaceholder.typicode.com/,这在互联网上是免费的。我们还将创建一个本地服务器,从本地静态 JSON 文件返回模拟的 JSON 响应。

我们必须将HttpClientModuleHttpClientTestingModule导入到我们的app.module.ts文件中。

在我们继续编写用于测试 Angular HTTP 的测试脚本之前,我们需要更新我们在本章中一直使用的经销商服务。我们将实现一些方法,这些方法将进行 HTTP 调用 - POST/GET 以处理数据到 REST API 端点。

我们正在按照以下方式处理dealers.service.ts文件:

import { HttpClient } from '@angular/common/http';
import { HttpHeaders, HttpParams, HttpErrorResponse } from '@angular/common/http';
readonly REST_ENDPOINT = 'https://jsonplaceholder.typicode.com/users';
readonly DEALER_REST_ENDPOINT = 'https://jsonplaceholder.typicode.com/users/1';
private _carurl = 'http://localhost:3000/cars';

在上述代码中,我们正在导入所需的 HTTP 模块;即HttpClientHttpHeadersHttpParamsHttpErrorResponse,并定义了两个具有用户 API URL 和特定用户的 REST 端点。

我们也可以启动本地服务器。您可以使用 JSON 服务器创建本地 API。您可以在github.com/typicode/json-server了解更多信息。

是时候添加一些方法了,通过这些方法我们将对 REST 端点进行 HTTP 调用:

getAllDealers()
{
this.allDealers = this.http.get(this.REST_ENDPOINT,
{
headers: new HttpHeaders().set('Accept', 'aplication/json')
});
return this.allDealers;
}

getDealerById(){
let params = new HttpParams().set('id', '1');
this.dealerDetails = this.http.get(this.REST_ENDPOINT, {params});
return this.dealerDetails;
}

在上述代码中,我们正在创建两个方法,它们进行 HTTP GET 请求。第一个方法getAllDealers进行调用,并期望获得用户的 JSON 响应。第二个方法getDealerById将传递id1,并期望获得单个用户数据的响应。在getDealerById方法中,我们使用HttpParams来设置要发送到端点的参数。我们还将修改我们的autoListComponent组件,向我们的Component类中添加一些方法。

我们将向我们的auto-list.component.ts文件添加以下代码:

findAuto() {
 this.dealers = this._dealersService.getDealers();
 return this.dealers;
 }

listAllDealers(){
 this.allDealers = this._dealersService.getAllDealers();
 }

listDealerById(){
 this.showDealerInfo = true;
 this.dealerDetail = this._dealersService.getDealerById();
 return this.dealerDetail;
 }

getCarList() {
 this.carList = this.http.get<Cars[]>(this._carurl);
 }

在上述代码中,我们正在添加一些方法,即findAutolistDealerByIdgetCarList,它们进行了 HTTP 调用并调用了经销商服务中的方法。

好了,现在我们已经设置好了进行 HTTP 调用的组件和服务,我们可以开始编写我们的 HTTP 测试了。

用例#1:我们要测试是否对特定 URL 进行了GET调用。

我们将向auto-list.component.spec.ts文件添加以下代码:

// Test HTTP Request From Component
 it('Test HTTP Request Method', async(() => {
 const fixture = TestBed.createComponent(AutoListComponent);

 component = fixture.componentInstance; 
 httpMock = TestBed.get(HttpTestingController);

 let carList = component.getCarList();

 fixture.detectChanges();
 const req = httpMock.expectOne('http://localhost:3000/cars');

 expect(req.request.method).toBe('GET');
 req.flush({});

 }));

在上述代码中,我们正在创建AutoListComponent的实例,使用它来调用getCarList方法。在getCarList方法中,我们正在调用http://localhost:3000/cars的 URL 来检索数据。我们创建了一个名为httpMockHttpTestingController类的实例。使用httpMock实例,我们断言至少应该对该 URL 进行一次调用。

用例#2:我们希望期望结果返回的数据多于1

it('Test HTTP Request GET Method With subscribe', async(() => {
const fixture = TestBed.createComponent(AutoListComponent);
component = fixture.componentInstance;
component.listDealerById().subscribe(result => 
expect(result.length).toBeGreaterThan(0));

}));

在上述代码中,我们使用AutoListComponent的实例调用listDealerById方法。使用subscribe,我们正在映射结果并验证结果数据长度是否大于0

用例#3:我们想要验证从 HTTP 调用返回的数据是否匹配数据。以下是此用例场景的示例代码。

it('Test if the first Dealer is North Auto', () => {
const service: DealersService = TestBed.get(DealersService);
let dealers = service.getDealers();
expect(dealers[0].name).toBe('North Auto');
});

在上述代码中,我们使用DealersService实例调用getDealers方法。我们断言第一个索引属性名称的数据应为North Auto

使用ng test命令运行测试。我们应该看到以下输出,如下面的截图所示:

如果您看到了上述输出,那太棒了。

在本节中,我们学习了如何测试进行 HTTP 请求调用的组件、服务和方法。

摘要

测试是应用程序生命周期中的重要方面,编写测试脚本对于应用程序开发成功至关重要。我们首先概述了 Angular 支持的框架,即 Jasmine 和 Karma。我们学习了如何使用ng test命令运行测试。然后,我们学习了如何使用 Angular 自动生成的 spec 文件来为所有组件和服务编写测试脚本。

我们学习了如何编写测试脚本来测试 Angular 组件、内置指令、服务和路由。我们为内置指令编写了测试脚本,例如ngForngIfngSwitchngModel。我们还涵盖了用于测试 Angular 路由的用例。然后,我们创建了一个menu组件,并编写了测试脚本来测试menu组件的各种用例。

我们还探讨了测试依赖注入和服务。我们学习了各种用例,并为 Angular 服务和 HTTP 调用编写了测试脚本。

在下一章中,我们将探讨高级的 Angular 主题,如自定义指令和自定义表单验证。

继续阅读!

第十四章:高级 Angular 主题

在之前的章节中,我们学习了如何使用指令和表单验证器。在本章中,我们将通过自定义指令和自定义验证器来扩展我们的知识。我们还将学习如何使用 Angular 构建单页应用(SPA)。

此外,我们将探讨如何将身份验证集成到我们的 Angular 应用程序中,使用两个流行的身份验证提供者:Google Firebase 身份验证和 Auth0。

本章将涵盖以下主题:

  • 自定义指令

  • 自定义表单验证器

  • 构建 SPA

  • 用户身份验证

  • 使用 Firebase 身份验证进行身份验证

  • 使用 Auth0 进行身份验证

  • 客户端的连接

自定义指令

在本节中,我们将学习如何创建自定义指令。

首先,让我们了解什么是 Angular 指令。

Angular 指令是扩展 HTML 功能和元素行为的一种方式。

在之前的章节中,我们学习了并实现了许多内置指令,比如*ngIf*ngFor*ngSwitchngModel

在本节中,我们将学习如何创建我们自己的自定义指令来扩展 HTML 元素的功能。

**用例:**我们想为表单元素和onfocus创建一个自定义指令。背景颜色应设置为浅蓝色,边框为深蓝色,onblur事件应以红色突出显示。所以,让我们开始:

  1. 让我们使用ng命令生成指令:
 ng g directive onFocusBlur

运行上述命令后,屏幕上会显示以下内容:

请注意,指令文件已经生成,并且我们的app.module.ts文件也已更新,这意味着该指令可以在整个应用程序中使用,在任何组件中使用。

  1. 在指令文件on-focus-blur.directive.ts中,添加以下代码行:
      import { Directive } from '@angular/core';
      import { HostListener, HostBinding } from '@angular/core';

      @Directive({
      selector: '[appOnFocusBlur]'
      })
      export class OnFocusBlurDirective {

      constructor() { }

      @HostBinding("style.background-color") backgroundColor;

      @HostListener('focus') onFocus() {
        this.backgroundColor = '#19ffe4';
      }

      @HostListener('blur') onBlur() {
        this.backgroundColor = '#ff1934';
      }

      }

在上面的代码中,应注意以下重要事项:

  • 我们正在导入所需的模块,即DirectiveHostListenerHostBinding

  • 使用@directive装饰器,我们通过选择器定义指令的名称。

  • @HostBinding用于在元素上设置属性。

  • @HostListener用于监听宿主元素上的事件。

  • 在上面的示例中,我们绑定了样式背景颜色属性。我们可以在宿主元素上绑定任何样式、类或事件属性。

  • 使用@HostListener,我们监听事件,并使用onFocus改变背景颜色。通过使用onBlur,我们重置颜色。

现在,我们可以在应用程序的任何地方使用这个装饰器。

  1. 我们将在app.component.html文件中的表单控件输入元素中使用它:
      <input type="text" appOnFocusBlur class="nav-search" >
  1. 使用ng serve命令运行应用程序,并单击Input button。我们应该看到以下截图中显示的输出和行为:

很好。现在我们知道如何编写自定义指令,我们将继续尝试创建我们自己的自定义指令。

在下一节中,我们将学习如何编写自定义表单验证。

自定义表单验证

在之前的章节中,我们学习了表单和实现表单验证。我们使用了内置的表单验证或 HTML5 属性验证。但是,在更复杂的场景中,我们将需要实现自定义表单验证。这些验证因应用程序而异。在本节中,我们将学习自定义表单验证。简而言之,Angular 通过Validators模块为我们提供了各种选项,通过它们我们可以在 Angular 表单中实现表单验证。

以下代码示例中展示了使用验证器:

loginForm = new FormGroup({
 firstName: new FormControl('',[Validators.required, 
 Validators.maxLength(15)]),
 lastName: new FormControl('',[Validators.required]),
 });

在上述代码中,我们使用Validators模块应用了requiredmaxLength等验证。

现在,让我们学习如何创建我们自己的自定义表单验证。首先,我们将生成一个组件,在其中我们将实现一个表单和一些元素,以便我们可以应用我们新创建的指令:

ng g c customFormValidation

成功运行上述命令后,我们应该看到以下输出:

现在我们已经生成了我们的组件,让我们生成一个指令,在其中我们将实现自定义表单验证。

我们将实现一个自定义指令来检查 ISBN 字段。

什么是 ISBN? ISBN 是每本出版书籍的唯一标识符。

以下是 ISBN 号码所需的条件:

  • ISBN 号码应该正好是 16 个字符

  • 只允许使用整数作为 ISBN。

现在,使用ng命令,我们将生成我们的指令:

ng g directive validISBN

成功执行上述命令后,我们应该看到以下截图中显示的输出

valid-isbn.directive.ts文件中,添加以下代码行:

import { Directive } from  '@angular/core'; import { NG_VALIDATORS, ValidationErrors, Validator, FormControl } from  '@angular/forms'; 
@Directive({
    selector: '[validISBN]',
    providers: [
         { provide: NG_VALIDATORS, 
            useExisting: ValidISBNDirective, multi: true }
    ]
})  
export  class ValidISBNDirective implements Validator { static validateISBN(control: FormControl): ValidationErrors | null {       
 if (control.value.length <  13) {
 return { isbn: 'ISBN number must be 13 digit long' };        }
 if (!control.value.startsWith('Packt')) {
 return { isbn: 'Value should start with Packt' };        }
 return  null;
    }

    validate(c: FormControl): ValidationErrors | null {        return ValidISBNDirective.validateISBN(c);    }
}

让我们详细分析上面的代码片段。首先,使用ng CLI 命令,我们生成了一个名为validISBN的指令。Angular CLI 将自动生成所需的文件,并预填充基本语法。我们正在导入所需的模块,即NG_VALIDATORSValidationErrorsValidatorFormControl。我们正在将所需的模块作为我们的提供者的一部分注入。接下来,我们实现了一个名为validateISBN的方法,它接受FormControl类型的参数。我们将我们的表单控件字段传递给这个方法,它将验证表单控件的值是否与方法中实现的条件匹配。最后,我们在validate方法中调用validateISBN方法。

现在,我们可以在任意数量的地方使用这个自定义表单验证,也就是说,无论我们需要验证或验证 ISBN 号码的地方。让我们使用ng serve命令运行应用程序。我们应该看到以下输出:

到目前为止,在本章中,我们已经在一些情况下应用了一些开箱即用的想法,并学习了如何构建我们自定义的指令和自定义表单验证。我们还学会了如何轻松地将它们集成到现有或任何新的应用程序中。所有这些也可以成为单页应用的一部分。等等。什么?单页应用?那是什么?在下一节中,我们将学习关于单页应用的一切,并构建我们自己的单页应用。

构建单页应用

在本节中,我们将学习构建单页应用。

什么是单页应用?

单页应用是一种与用户交互的 Web 应用程序或网站,它通过动态重写当前页面与用户交互,而不是从服务器加载全新的页面。

把它想象成一个只有一个 HTML 文件的应用程序,页面的内容根据用户的请求动态加载。我们只创建在运行时动态渲染在浏览器中的模板。

让我给你一个很好的例子。

在第十五章中,部署 Angular 应用程序,使用ng build命令,我们生成了 Angular 应用程序的编译代码。

查看由 Angular 生成的编译源代码:

在上面的截图中,你将只看到一个名为index的 HTML 文件。

继续打开文件 - 您会发现它是空白的。这是因为 Angular 应用程序是单页面应用程序,这意味着内容和数据将根据用户操作动态生成。

可以说所有的 Angular 应用程序都是单页面应用程序。

以下是构建单页面应用程序的一些优势:

  • 页面是动态呈现的,因此我们的应用程序源代码是安全的。

  • 由于编译后的源代码在用户的浏览器中呈现,页面加载速度比传统的请求和响应模型快得多。

  • 由于页面加载速度更快,这导致了更好的用户体验。

  • 使用Router组件,我们只加载特定功能所需的组件和模块,而不是一次性加载所有模块和组件。

在本书的整个过程中,我们创建了许多 Angular 应用程序,每个应用程序都是单页面应用程序。

用户认证

在本节中,我们将学习如何在我们的 Angular 应用程序中实现用户认证。

在广义上,用户认证包括安全地将用户登录到我们的应用程序中,用户应该能够在安全页面上查看、编辑和创建数据,最后从应用程序中注销!

在现实世界的应用程序中,需要进行大量的额外检查和安全实施,以清理用户输入,并检查他们是否是有效用户,或验证会话超时的身份验证令牌,以及其他数据检查,以确保不良元素不会进入应用程序。

以下是一些重要的用户认证模块:

  • 注册新用户

  • 现有用户的登录

  • 密码重置

  • 已登录用户的会话管理

  • 一次性密码或双重认证

  • 注销已登录的用户

在接下来的章节中,我们将学习如何使用 Firebase 和 Auth0 框架实现上述功能。

使用 Firebase 进行用户认证

在本节中,我们将学习如何使用 Firebase 实现用户认证。

什么是 Firebase?

Firebase 是由 Google 提供的托管服务。Firebase 为我们提供了诸如分析、数据库、消息传递和崩溃报告等功能,使我们能够快速移动并专注于我们的用户。您可以在firebase.com了解更多有关该服务的信息。现在,让我们立即开始在我们的 Angular 应用程序中实现 Firebase。

第一步是创建一个谷歌账户来使用 Firebase 服务。您可以使用您的谷歌账户登录 Firebase。一旦您成功创建了 Firebase 账户,您应该会看到以下输出:

要创建一个新项目,请点击“添加项目”链接。

您将看到以下对话框窗口,提示您输入项目名称;在我们的情况下,我们正在将我们的项目命名为 AutoStop:

请注意,谷歌将为您的项目分配一个唯一的项目 ID。

现在,点击左侧菜单上的认证链接,设置用户认证功能,我们可以在我们的 Angular 应用程序中嵌入和设置:

我们可以在这里做很多其他很酷的事情,但现在我们将专注于认证模块。

现在,点击登录方法选项卡,设置如何允许用户登录到我们的 Angular 应用程序的选项:

在上述截图中,您将注意到以下重要事项:

  • 谷歌 Firebase 提供了各种选项,我们可以启用这些选项,通过这些选项,我们希望我们应用程序的用户登录。

  • 我们需要单独启用每个提供者选项。

  • 我们已在我们的应用程序中启用了电子邮件/密码和谷歌选项。

  • 为了启用 Facebook、Twitter 和其他应用程序,我们需要输入各自服务提供的开发者 API 密钥。

现在,在页面上向下滚动一点,您将看到一个名为授权域的设置选项。

我们将看到 Firebase 应用程序上设置了两个默认值,即 localhost 和一个唯一的子域,在下面的截图中显示:

我们已经做出了必要的更改。现在,我们需要设置 Google Firebase 的应用设置。现在是在我们的 Angular 应用程序中实现用户认证的时候了。

**先决条件:**我们期望用户已经有一个正在运行的 Angular 应用程序。

打开 Angular CLI 命令提示符;我们需要安装一些模块。我们需要先安装 Angular Fire2 和 Firebase:

请注意,Angular Fire2 现在是 Angular Fire。

我们需要运行以下命令在我们的应用程序中安装 Angular Fire:

npm install angularfire2 

在成功执行上述命令后,我们应该看到以下截图中显示的输出:

一切就绪。现在,我们需要创建一个处理身份验证功能的服务。

ng g service appAuth

使用ng命令,我们正在生成一个名为appAuth的新服务:

现在,是时候修改appAuth.service.ts文件并添加以下代码了:

import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { auth } from 'firebase/app';
import { Router } from '@angular/router';

@Injectable({
providedIn: 'root'
})
export class AppAuthService {

    private authUser:any;
    private authState:any;
    private loggedInUser = false;
    private userToken ='';

constructor(public afAuth: AngularFireAuth, private router :Router) { }

login() {
this.afAuth.auth.signInWithPopup(new auth.GoogleAuthProvider());

this.loggedInUser = true;

this.afAuth.currentUser.getIdToken(true).then(token => this.userToken = token);

this.afAuth.authState.subscribe((auth) => {
this.authState = auth;
});

this.router.navigate(['/profile']);
}

isLoggedInUser(){
if(this.userToken != '')
return true;
else 
return false;
}

logout() {
this.afAuth.auth.signOut();
this.loggedInUser = false;
this.userToken = '';
}

}

在上述代码中,我们正在对app-auth.service.ts文件进行更改。应注意以下重要点:

  • 我们正在将所需的类,即AngularFireAuthAuthRouter,导入到服务中。

  • 使用@Injectable,我们指定该服务在 Angular 树结构中的根级别注入。

  • 我们正在定义一些私有变量,我们将在整个应用程序中使用。

  • 在构造函数方法中,我们正在注入AngularFireAuthRouter类。

  • 我们正在定义三种方法:LoginLogoutisLoggedInUser

  • login方法中,我们正在使用this.afAuth实例,调用signInWithPopup方法,并传递auth.GoogleAuthProvider参数,该参数来自我们在本地安装的 Firebase 应用程序:

this.afAuth.auth.signInWithPopup(new auth.GoogleAuthProvider());
  • 当调用此方法时,将打开一个新窗口,在其中我们可以看到谷歌登录选项,使用它我们可以登录到应用程序。

  • 我们正在将this.loggedInUser变量设置为true

  • 我们将已登录用户的令牌设置为this.userToken变量。

  • 我们还订阅以获取authState响应。

  • 最后,使用路由器实例和使用navigate方法,我们将用户重定向到个人资料页面。

  • isLoggedInUser方法中,我们正在验证userToken是否已设置。如果用户已正确登录,userToken将被设置;否则,该方法将返回false

  • logout方法中,再次使用afauth的实例,我们正在调用signout方法,这将注销用户。

  • 最后,我们将userToken设置为empty

太棒了。我们已经在app-auth.service.ts文件中完成了所有繁重的工作。现在,是时候在我们的组件中调用这些方法了:loginprofilelog out

login.component.html文件中,我们将添加以下登录表单:

<div *ngIf="!_appAuthService.loggedInUser">
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">

<label>
First Name:
<input type="text" formControlName="firstName">
</label>

<label>
Last Name:
<input type="text" formControlName="lastName">
</label>

<button>Login</button>

</form>
</div>

在上述代码中,我们只是使用FormGroupFormControllers添加了一个 Angular 响应式登录表单。

登录表单的输出显示在以下截图中:

profile.component.ts文件中,我们只是调用了login方法:

onSubmit(){
 this._appAuthService.login();
 console.warn(this.loginForm.value);
 }

现在,在profile.component.ts文件中,我们添加了一个检查,以查看用户是否已登录:

<div *ngIf="_appAuthService.isLoggedInUser">
<p>
profile works!
</p>

User Token is {{_appAuthService.userToken}}
</div>

当用户导航到个人资料页面时,如果他们已登录,他们将看到详细信息;否则,用户将被重定向到登录页面。

现在,进入最后一部分;我们将在我们的app.component.html文件中有一个注销链接:

<nav>
 <a routerLink='/login' *ngIf="!_appAuthService.isLoggedInUser()">Login</a>
 <a routerLink='/register'>Register</a>
 <a routerLink='/logout' *ngIf="_appAuthService.isLoggedInUser()">Logout</a>
</nav>

我们正在添加带有*ngIf条件的链接,以在用户已登录或未登录时显示相应的链接:

 ngOnInit() {
 this._appAuthService.logout();
 this.router.navigate(['/login']);
 }

当用户点击注销链接时,我们调用appAuthService的注销方法,并在成功注销后将用户重定向回登录页面。

现在,让我们使用ng serve命令来运行应用程序。我们应该看到以下输出:

使用 Auth0 进行用户身份验证

在本节中,我们将学习如何使用 Auth0 实现用户身份验证。在我们继续在我们的 Angular 应用程序中实现 Auth0 之前,我们需要实现一些先决条件。让我们开始吧:

  1. 首先,我们需要在 Auth0.com 上创建一个帐户。成功登录到帐户后,我们应该看到以下仪表板屏幕:

我们将不得不注册我们的应用程序,以便我们可以创建所需的设置来在我们的应用程序中实现Auth0

  1. 点击左侧菜单上的“应用程序”链接:

  1. 现在,点击“创建应用”按钮创建一个应用:

  1. 我们需要输入应用程序的名称并选择我们正在构建的应用程序类型。在我们的情况下,这是一个单页 Web 应用程序,所以请继续选择该选项并点击“创建”按钮。

  2. 我们需要做的下一件事是更新应用程序的重要设置。因此,点击应用程序名称并导航到“设置”选项卡:

以下是一些需要牢记的重要事项:

  • 我们需要更新允许的回调 URL、允许的 Web 起源和允许的起源(CORS)。

  • 如果我们更新了允许的 Web 起源和允许的起源的详细信息,我们将收到跨源请求(CORS)错误。

我们已经在 Auth0 中调整了所需的设置,所以现在可以在我们的应用程序中实现 Auth0 了。

为了在我们的应用程序中实现 Auth0,我们需要安装一些模块,即auth0-jsauth0-lockangular2-jwt

在上述截图中,使用npm install命令,我们安装了所需的Auth0模块。现在,是时候为我们的应用程序生成服务和组件了。

首先,我们需要生成我们的服务;让我们称之为authService。我们需要运行以下命令来生成我们的服务:

ng g service services/auth

在成功执行上述命令后,我们应该看到以下输出:

我们可以验证并确认我们的服务已经生成,以及规范文件(用于编写我们的测试规范的文件)。现在我们已经创建了我们的服务,是时候生成组件了。我们将使用ng CLI 运行以下命令以生成所需的组件:

ng g c login
ng g c profile

在成功执行上述命令后,我们应该看到以下输出:

在上述截图中,我们可以验证并确认我们的所需组件,即loginprofile,已成功生成。现在,我们可以继续实现我们组件的功能了。

为了使我们的应用程序更美观,让我们也安装bootstrap CSS 框架:

npm i bootstrap 

我们还需要安装jquery模块:

npm i jquery 

在成功执行上述命令后,我们应该看到以下输出:

太酷了。现在,是时候在Nav组件中添加一些链接了:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
 <a class="navbar-brand" href="#">Auth0</a>
 <button class="navbar-toggler" type="button" 
    data-toggle="collapse" data-target="#navbarSupportedContent" 
    aria-controls="navbarSupportedContent" aria-expanded="false" 
    aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
 </button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
 <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home 
         <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" *ngIf="!authService.isLoggedIn();" 
           (click)="authService.login()">Login</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" *ngIf="authService.isLoggedIn();" >Profile</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" *ngIf="!authService.isLoggedIn();"
           href="#">Register</a>
      </li>
       <li class="nav-item">
        <a class="nav-link" *ngIf="authService.isLoggedIn()" 
           (click)="authService.logout()">Logout</a>
      </li>
    </ul>
 </div>
</nav>

在上述代码中,应该注意以下重要点:

  • 我们正在使用 Bootstrap 的nav组件。

  • 我们正在添加一些链接并附加点击事件,例如根据用户状态登录和注销。如果用户已登录,我们将显示注销链接,否则我们将显示注册链接。

  • 我们将在我们的 nav.component.ts 文件中实现这些方法。

  • 我们正在使用*ngIf来检查用户是否已登录,并相应地切换登录和注销链接。

上述代码的输出如下截图所示:

现在我们需要在我们生成的auth服务上工作。在services/auth.service.ts文件中,我们需要首先导入所需的模块,然后添加我们的方法loginlogout

import { tokenNotExpired } from 'angular-jwt';
import { Auth0Lock} from 'auth0-lock';

一旦我们导入了Auth0LockTokenNotExpired类,我们将创建实例以便我们可以使用它们。

看一下基本的Auth0Lock对象实例创建代码:

var lock = new Auth0Lock( 'YOUR_CLIENT_ID', 'YOUR_AUTH0_DOMAIN' );

为了创建一个Lock类的新对象,我们需要将客户端 ID 和域名传递给实例。

让我们在我们的auth.service.ts文件中实现这个:

public _idToken: string;
private _accessToken: string;
private _expiresAt: number;

 lock = new Auth0Lock('XvVLuuMQr3kKAR3ECAmBZOiPPyVYehvU','srinix.auth0.com',{
 allowedConnections: ["Username-Password-Authentication","google-oauth2"],
 rememberLastLogin: false,
 socialButtonStyle: "big",
 languageDictionary: {"title":"Auth0"},
 language: "en",
 responseType: 'token id_token',
 theme: {}
 });

在上述代码中,应该注意以下重要点:

  • 我们创建了三个变量,分别是_idToken_accessToken_expiresAt

  • 我们正在创建一个Auth0Lock的实例,并且需要向对象传递参数。

  • Auth0Lock对象将需要传递两个必需的参数。第一个参数是ClientId,第二个是域名。

  • 第三个参数包括allowedConnections、主题等选项,因为它说它们是可选的。

  • 客户端 ID 和域名可以从 Auth0 应用程序设置中获取,如下面的截图所示:

我们现在可以监听附加到lock对象的事件:

constructor(private router: Router) {

this.lock.on('authenticated', (authResult: any) => {
localStorage.setItem("userToken", authResult.accessToken);
this.router.navigate(['/profile']); 
});

this.lock.on('authorization_error', error => {
console.log('something went wrong', error);
});

}

在上述代码中,我们正在执行以下步骤:

  1. constructor方法中,我们正在监听authenticatedauthorization_error状态的on事件。

  2. 当我们从lock实例获得认证消息时,我们正在存储一个名为userTokenlocalStorage项目,并将accessToken设置为其值。

  3. 我们还在监听错误消息并将消息记录在控制台中。

现在,是时候实现loginlogout方法了:

login() {
 this.lock.show(function(err, profile, token){
 console.log(err);
 console.log(profile);
 console.log(token);
 });
 }

login方法中,我们正在调用lock对象的show方法。这将带您进入 Auth0 的对话框,其中有登录、注册或忘记密码的选项。如果您选择了任何社交选项,登录对话框将包含社交选项。

对于logout方法,我们只需清除用户登录时设置的userToken,并将用户重定向回主页登录页面。

logout(){
localStorage.setItem('userToken','');
this.router.navigate(['/']);
}

清除userToken后,应用程序将知道用户未登录。

我们已经实现了loginlogout方法,但我们还需要一个方法来检查用户是否已登录:

 isLoggedIn() {
 var token = localStorage.getItem('userToken');
 if(token != '')
 {
 return true;
 }
 else {
 return false;
 }
 }

isLoggedIn方法中,我们正在检查本地存储中userToken变量的值是否设置。如果设置了值,这意味着用户已登录;否则,用户未登录。

只需将服务导入到我们的app.component.ts文件中,并将其注入到构造函数中:

import { Component } from '@angular/core';
import { AuthService } from './services/auth.service';

@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.scss']
})
export class AppComponent {
 title = 'Auth0 Tutorial';
 userToken:string;

 constructor(private authService: AuthService) {}
}

就是这样。是不是很简单?

我们应该看到以下输出:

如果我们点击登录链接,我们应该看到 Auth0 对话框弹出窗口:

现在,继续点击“注册”选项卡创建一个账户,一旦成功注册,您应该看到该用户也已添加到 Auth0 仪表板中:

成功登录后,我们应该只能看到注销链接,如下面的屏幕截图所示:

当我们点击注销链接时,用户应该被带回默认的登陆页面,并应该看到登录和注册选项。还要注意 URL 中提供的参数,如access_token expires_in等。

太棒了!我们刚刚在我们的应用程序中使用 Auth0 实现了整个用户身份验证。

总结

在本章中,我们学习了一些高级的 Angular 主题,从创建自定义指令到扩展原生 HTML 元素的行为。我们还创建了自定义表单验证,这在开发具有许多验证和合规性要求的复杂应用程序时非常有用。我们深入研究了 Angular 单页应用程序,并了解了它们的工作和行为。我们通过原生代码在我们的 Angular 应用程序中实现了用户身份验证。

我们还学习了如何使用现有框架构建和实现安全的用户身份验证管理系统,即 Firebase 和 Auth0。然后,我们学习了如何实现登录、注册和注销功能,以确保我们可以保护应用程序的数据和功能。现在我们已经掌握了前面的概念,可以实现一个完整的、有线的端到端 Angular 应用程序了。

现在我们已经学会了如何开发我们的 Angular 应用程序,唯一隔我们的应用程序和真实用户之间的就是部署我们的应用程序。这是我们下一章的重点。在本书的下一章和最后一章中,我们将学习如何部署我们的 Angular 应用程序。

第十五章:部署 Angular 应用程序

一旦您完成了构建应用程序,它就必须部署到测试环境,供测试团队在将应用程序部署到生产环境供用户使用之前进行测试。虽然您可以在几乎任何地方托管您的应用程序,但有三种主要方式可以打包和部署您的 Angular 应用程序。我们将在本章中探讨这些方法:

  • 部署 Angular 应用程序

  • 部署复合 Angular 应用程序

  • 部署到 GitHub 页面

部署 Angular 应用程序

部署我们的应用程序和构建应用程序本身一样重要。毕竟,我们的用户需要访问它;否则,构建它就没有意义,对吧?

在我们详细学习和探索如何部署应用程序之前,有一个运行的服务器是前提条件。服务器可以托管在任何操作系统上,无论是 Windows 还是 Linux,并且可以在任何应用程序服务器上运行,比如 Apache Tomcat 或 IIS。或者,我们可以选择任何可靠的云提供商,比如 AWS、Azure 或 Bluehost,它们提供托管能力。

技术栈可以因项目而异;一些客户更喜欢基于 Java 的微服务,一些可能更喜欢.NET,其他人可能更喜欢 Ruby on Rails。我们需要将我们的 Angular 应用程序与后端 API 集成。客户端代码大部分将是 Angular,这基本上意味着 Angular 应用程序可以部署和运行在任何带有任何后端 API 服务的服务器上。

在本章中,我们将使用 XAMPP 服务器。XAMPP 是 Apache、MySQL 的免费分发版,可以轻松快速地设置我们的本地服务器。您可以在www.apachefriends.org/download.html下载它。

Angular 应用程序的编译选项

我相信您现在已经意识到,我们为 Angular 编写的所有代码都是 TypeScript,并且我们需要使用ng命令进行编译和生成可部署文件:ng build。这个命令将生成相应的等效 JavaScript 代码,可以直接复制到我们要部署的环境中。

部署 Angular 应用程序非常简单和容易。在实时场景中,构建和部署命令已经集成到构建管道中。一个常见的做法是在一个存储库中运行一个单一的 Angular 项目。然而,我们也可以在一个存储库中运行多个项目。

在本节中,我们将首先了解我们可以考虑用于部署 Angular 应用程序的各种编译选项。在接下来的章节中,我们将学习如何部署独立应用程序,以及如何部署复合 Angular 应用程序。在学习如何部署我们的应用程序之前,了解构建应用程序源代码时会发生什么是很重要的。

Angular 有两种编译选项,根据我们使用的命令和元标志来应用:

  • 即时编译

  • 提前编译

什么是即时编译?

Angular 的即时JIT)编译是指在运行时在浏览器中编译代码。这是每当我们运行ng build命令时的默认行为:

ng build

这种机制会增加请求和引导时间。更改会在我们的浏览器中反映出来,这在开发应用程序时非常好。这个选项允许开发人员在开发过程中快速测试更改。

什么是提前编译?

Angular 的提前AOT)编译意味着将源 TypeScript 代码、组件、Angular HTML、库和模块编译成本机 JavaScript,以便它可以在任何浏览器上平稳运行。换句话说,Angular 会在代码被浏览器下载之前进行转换。

让我们来看看 AOT 的一些好处:

  • 更好的安全性

  • 更快的渲染

  • 更小的框架和应用程序大小

  • 提前发现错误

提前编译或只是 AOT 编译在运行ng build --prod元标志时会默认应用:

ng build --prod

现在我们已经了解了 Angular 提供的不同类型的编译,现在终于是时候实际部署 Angular 应用程序了。在下一节中,我们将学习如何部署 Angular 应用程序。

部署独立的 Angular 应用程序

掌握了部署和编译策略的知识,现在是时候部署我们的 Angular 应用程序了。当我们运行ng buildng build --prod命令时,会生成本机 JavaScript 文件,我们可以部署到我们的服务器上。如果我们要部署单个项目应用程序,这是很好的。

在本节中,我们将学习如何部署更复杂的用例,比如当我们的 Angular 应用程序中有多个项目时。

为了使我们的读者能够轻松跟随这些步骤,我们将保持我们的应用程序简单。但是,您可以通过部署到目前为止开发的 Angular 项目来练习部署命令。让我们开始创建一个新的 Angular 应用程序:

  1. 要安装 Angular CLI,让我们快速使用以下命令:
 npm i -g @angular/cli

上述运行命令的输出如下所示。我们刚刚安装了 Angular CLI,我们将使用它来生成我们的应用程序:

  1. 既然我们已经成功安装了 Angular CLI,现在是时候创建一个名为 prod-ready 的 Angular 应用程序了:
 ng new prod-ready

使用上述命令,我们已经生成了一个新项目。以下截图显示了生成的输出:

太棒了!我们有了新生成的应用程序。

  1. 现在,让我们转到 prod-ready 应用程序文件夹,如下所示:
 cd prod-ready
  1. 全部完成。我们现在不打算更改或添加任何新组件。现在,我希望您了解部署应用程序的最简单方法。现在,使用 ng serve 命令启动应用程序:
 ng serve

上述命令将启动应用程序,我们应该看到以下截图中显示的输出:

  1. 启动浏览器,然后输入 http://localhost:4200。默认的原始应用程序应该显示如下:

太棒了。到目前为止一切顺利。我们在本地环境中让我们的应用程序正常工作,现在是时候将其部署到我们的应用程序中了!

为了让您对整个部署过程感到舒适,我们将部署原始应用程序,而不进行任何更改。

  1. 要部署,请运行以下 ng 命令:
 ng build --prod

一旦命令成功运行,您应该看到以下文件夹和文件已被创建。让我们看一下一些重要的注意事项:

  • 您应该注意到一个名为 dist/<defaultProject> 的新文件夹。

  • 您还应该注意到 dist 文件夹中创建的以下文件:

  • 运行时

  • 主要

  • 填充

  • 样式

上述 build 命令的输出如下。输出将位于 dist 文件夹中,与应用程序名称相同:

  1. 我们不一定要使用默认的文件夹名称;也就是说,我们可以将输出路径和文件夹名称作为参数提供,Angular 将在该文件夹中生成代码。很容易定制我们希望生成文件的输出目录:
 ng build --prod --output-path dist/compiled

运行上述命令,我们应该看到我们的自定义文件夹和文件在我们的文件夹中生成。在上述命令中,我们指定了我们希望我们的文件生成在名为compiled的文件夹中,并提供了路径。以下是命令成功运行后的屏幕截图:

这就是我们需要做的来生成和部署我们的 Angular 应用程序。只需将所有文件复制到服务器的根目录,就完成了。

在下一节中,我们将学习如何部署一个更复杂的 Angular 应用程序架构,然后我们将以多种方式部署复合应用程序。

部署复合 Angular 应用程序

在上一节中,我们学习了如何部署一个独立的 Angular 应用程序,这是相当简单的。然而,我们可能会遇到需要构建和部署多个应用程序并运行在单个存储库中的情况。这是可能的吗?当然可以。在本节中,我们将创建一个具有多个项目的 Angular 存储库,并学习如何部署一个复合应用程序。

创建和部署多个 Angular 应用程序

在更现实的实际应用程序中,我们将需要运行多个 Angular 应用程序,这些应用程序将由多个项目、库、模块和微服务组成,如下图所示:

在上图中,一些重要的事项如下所述:

  • 有多个 Angular 项目和应用程序。

  • 库 #1库 #2 可以通过导入库在多个项目中重复使用。

  • 在开发阶段,我们将创建多个模块,这些模块也可以在多个项目中重复使用。

因此,让我们立即开始创建多个项目、库和模块。最后,我们将以不同的方式打包应用程序。所以,让我们开始让我们的 Angular 应用程序运行起来:

  1. 首先要做的是,我们需要生成一个应用程序,我们将使用 Angular CLI 来生成应用程序。我们首先需要使用以下命令安装 Angular CLI:
 npm install @angular/cli

在成功执行上述命令后,我们应该看到以下输出:

  1. 现在我们已经安装了 Angular CLI,让我们使用以下命令创建应用程序。我们将其称为shopping-cart。现在,运行以下ng命令生成新项目:
 ng new shopping-cart

使用上述命令,我们生成了一个名为shopping-cart的新应用程序。上述命令的输出如下:

  1. 我们现在创建了一个名为shopping cart的新应用程序。让我们修改app.component.html并添加两个名为list-jacketslist-vendorsrouterLink超链接:
      <div style="text-align:center">
       <h1>
       Welcome to {{ title }}!
       </h1>
      </div>
      <ul>
       <li>
       <h2><a routerLink="/list-jackets" class="nav-link">List 
         Jackets</a></h2>
       </li>
       <li>
       <h2><a routerLink="/list-vendors" class="nav-link">List 
         Vendors</a></h2>
       </li>
      </ul><router-outlet></router-outlet>

在上述代码中,我们在app.component.html文件中创建了两个链接。结果显示如下:

到目前为止,一切都很好。基本上,我们已经有了一个正在运行的 Angular 应用程序。现在,我们将学习如何在同一个存储库中运行和部署多个 Angular 项目。为了做到这一点,我们将按照以下步骤进行:

  1. 让我们使用以下命令在同一个存储库中创建一个新应用程序。我们正在生成一个名为jackets的新应用程序:
 ng g application jackets

我们使用ng命令创建一个名为jackets的新应用程序。我们应该看到以下输出:

  1. 哇哦!使用 Angular CLI 的 schematics,很容易在同一个应用程序中创建多个项目。看一下自动生成的文件以及 Angular CLI 为我们更新的一些文件:

如果您仔细观察,您会注意到以下是我们应用程序结构和文件发生的一些重要变化:

  • 一个名为Projects的新文件夹被自动生成,并且在angular.json文件中生成了相应的条目。

  • Projects文件夹中,我们将看到具有相同默认 vanilla 应用程序文件的新Jackets项目已生成。

  1. 现在,为了验证是否已添加新的Jackets项目,请查看Angular.json文件:

您会注意到在Angular.json文件中,我们有针对 shopping-cart、shopping-cart-e2e、jackets 和 jackets-e2e 的项目特定条目。很棒。从技术上讲,我们现在在同一个存储库中运行两个应用程序。

  1. 现在是时候通过添加一些组件、库和模块来扩展我们的应用程序了。首先,我们需要在我们的jackets项目中创建一个组件。运行以下ng命令来生成组件:
 ng g c jacket-list --skip-import

运行上述命令,我们应该看到生成的组件和相应文件。我们应该看到以下输出:

  1. 现在我们已经在Jackets项目中创建了一个新的组件,是时候将其添加到app-routing.module.ts中,以便在Jackets项目中可以使用。

在以下代码片段中,我们在app-routing.module.ts文件中导入了新创建的组件:

      import { NgModule } from '@angular/core';
      import { Routes, RouterModule } from '@angular/router';
      import { AppComponent } from './app.component';
 import { JacketListComponent } from 
      '../../projects/jackets/src/app/jacket-list/jacket-list.component';
      import { VendorsComponent } from 
      '../../projects/vendors/src/lib/vendors.component';
  1. 导入组件后,是时候为我们的组件创建一个路由了:
      const routes: Routes = [
       {
       path:'home',
       component:AppComponent
       },
       {
       path:'list-jackets',
       component:JacketListComponent
       },
       {
       path:'list-vendors',
       component:VendorsComponent
       }
      ];

在上述代码片段中,我们创建了list-jacketslist-vendors路由,它们分别映射到相应的JacketListComponentVendorsComponent组件。在上述代码片段中有两个重要的事项需要注意:

  • 我们正在运行多个 Angular 项目。

  • 我们正在在各个项目中相互链接组件。

  1. 我们已经将路由链接添加到app.component.html。现在,让我们通过运行ng serve命令启动我们的应用程序:
 ng serve 
  1. 在浏览器中输入http://localhost:4200,我们应该看到以下输出显示:

所以,现在我们有两个运行的应用程序,并且我们有跨不同项目共享的组件。

很好。现在,为什么我们不添加一些可以在多个项目之间共享的库呢?让我们开始吧:

  1. 我们将创建一个名为vendors的新的 Angular 库。我们将使用ng命令并将库命名为vendors。让我们运行以下命令来生成库:
 ng g library vendors --prefix=my

成功运行上述命令后,我们应该看到以下输出:

  1. 一旦库被生成,Angular CLI 将创建以下文件夹和文件:

  1. 以下是一些重要的事项,一旦命令成功运行:
  • Projects下创建一个新的Vendors库项目。

  • Angular 还将在Angular.json文件中进行必要的更改和条目。

  • 请注意,projecTypelibrary类型。

以下截图显示了新创建的库项目的显示数据:

  1. 现在,打开vendors文件夹,在src/lib下编辑vendors.component.ts文件并添加一些花哨的文本:
      import { Component, OnInit } from '@angular/core';

      @Component({
       selector: 'my-vendors',
       template: `
       <p>
       vendors works!
       </p>
       `,
       styles: []
      })
      export class VendorsComponent implements OnInit {

      constructor() { }

      ngOnInit() {
       }

      }
  1. 记住,我们之前为vendor组件创建了路由链接,所以我们应该在应用程序中看到反映出的更改:

现在我们已经构建了一个具有多个项目、库和路由系统以共享不同组件的 Angular 应用程序,是时候部署应用程序了。

部署很简单,就像我们为独立应用程序所做的一样:

ng build --prod --base-href "http://localhost/deploy-angular-app/"

运行命令后,将发生一些重要的事情:

  • 为了生成最终部署文件,我们正在运行ng build命令。

  • 我们正在使用--prod元标志,编译时将应用 AOT 编译。

  • 最重要的是,我们需要传递--base-href元标志,它将指向服务器的根文件夹/路径。

没有适当的--base-href值,Angular 应用程序将无法正常工作,并会给您链接生成的文件的错误。

从前面的部分,我们已经知道运行build命令后,Angular 将生成编译后的文件夹和文件,如下截图所示:

从上面的截图中需要注意的一些重要点:

  • 该命令将生成编译文件的输出,其中包含多个项目、库和组件。

  • 仔细考虑我们设置的--base-href值。我们在本地运行 XAMPP,因此路径指向localhost

现在,让我们将所有代码从dist文件夹复制并粘贴到我们的XAMPP文件夹中。

使用本地服务器启动 Angular 应用程序,您应该看到以下显示的输出:

这真的很酷!即便如此,我们还可以大大改进。在更现实的设置中,任何大型的 Angular 实现都将拥有特性团队,由一个团队开发的库或模块应该很容易地与其他团队共享作为一个模块。这就是可重用模块的编写方式。我们将学习如何将 Angular 模块分发为npm模块。

将 Angular 项目打包为 npm 包

现在,让我们学习如何将我们的 Angular 项目导出为npm模块。我们将继续使用在上一个示例中创建的vendors库:

  1. 请注意,我们希望部署整个应用程序,而是只想部署vendors库。我们将使用相同的ng build命令来构建vendorsAngular 项目:
 ng build vendors
  1. 一旦命令成功执行,我们将看到 Angular 将在dist文件夹下为我们的vendors项目生成编译文件,如下所示:

  1. 转到dist/vendors文件夹并运行以下命令:
 npm pack

我们正在使用npm pack命令从当前文件夹生成一个包,其中包含来自vendors项目的文件。我们应该看到以下输出:

  1. 成功执行后,我们将在文件夹中看到创建的vendors-0.01.tgz文件。现在我们可以将此文件作为npm包进行分发,可以在任何项目中重复使用:

  1. 现在让我们进行测试,通过将新生成的npm模块安装到我们的应用程序中来进行测试。要安装该包,请运行npm install命令,指向vendors-0.0.1.tgz
 npm install dist\vendors\vendors-0.0.1.tgz
  1. 完成后,我们应该看到以下输出,通知我们已添加了该包:

  1. 我们还可以验证包是否成功添加到package.json文件中。我们应该看到package.json中显示如下条目:

太棒了!在本节中,我们学习了如何将 Angular 应用程序部署为独立应用程序,也学习了如何将其部署为复合应用程序。

我们还学习了如何创建一个 Angular 项目的包,可以在多个 Angular 项目中进行分发和使用。

将 Angular 应用程序部署到 GitHub Pages

在之前的部分中,我们学习了如何部署我们的独立应用程序,以及通过导出应用程序的编译源文件将复合应用程序部署到任何服务器。

在本节中,我们将学习如何将我们的 Angular 应用程序部署到 GitHub Pages。

在整本书中,我们创建了许多 Angular 项目,现在是时候免费托管它们了!

在 GitHub Pages 中创建和部署应用程序

GitHub Pages 是托管在 GitHub 上的项目的网站。我们说了免费吗?当然,GitHub Pages 是免费的!只需编辑、推送,就可以在您的免费网站上实时查看更改。

让我们逐步看看如何在 GitHub Pages 上创建和托管我们的应用程序:

  1. 让我们通过使用npm install命令来安装 Angular CLI:
 npm install @angular/cli
  1. 命令完成后,是时候创建一个新的 Angular 项目了。让我们称之为deploying-angular
 ng new deploying-angular

成功执行命令后,我们应该看到以下截图:

  1. 现在是时候初始化一个 Git 仓库了。我们可以通过执行以下命令来做到这一点:
 git init
  1. 成功执行后,您将看到仓库已初始化,或者在以下情况下,如果仓库已存在,则将重新初始化如下:

  1. 随意对app.component.html或任何要修改的文件进行任何更改。然后,一旦准备部署,通过执行commit Git 命令来首先提交代码/更改。我们还可以传递-m元标志并向提交添加消息:
 git commit -m "deploying angular"
  1. 接下来,我们需要将origin设置为仓库。以下命令将远程origin设置为仓库:
 git remote add origin      
      https://<token>@github.com/<username>/<repo-name>

好的。一切准备就绪。

  1. 现在,超级能力来了。要直接将您的 Angular 应用程序部署到 GitHub,我们需要安装一个名为angular-cli-ghpages的软件包。这是一个官方分发,可直接将 Angular 应用程序部署到 GitHub Pages:
 npm install -g angular-cli-ghpages

这是我们在运行上述代码后将得到的输出:

现在我们已经安装了angular-cli-ghpages,是时候构建我们的应用程序并获取编译后的源文件了。

  1. 让我们使用--prod元标志运行ng build命令,并设置--base-href
 ng build --prod --base-href  
      "https://<username>.github.io/deploying-angular"

--base-href标志指向 GitHub 上的源仓库。您需要在 GitHub 上注册并获取授权令牌,以便托管您的应用程序。

  1. 这是base href URL,作者的 GitHub 主页,以及相应的deploying-angular仓库:
 ng build --prod --base-href  
      "https://srinixrao.github.io/deploying-angular"
  1. 构建 Angular 应用程序后,我们将看到编译后的源代码生成在dist/<defaultProject> -defaultProject下。编译后的源代码通常是我们指定的应用程序名称作为文件夹名称:

  1. 现在我们已经生成了编译文件,是时候将应用程序部署到 GitHub Pages 了。我们通过运行npx ngh --no-silent命令来实现这一点:
 npx ngh --no-silent --dir=dist/deploying-angular
  1. 请记住,可选地,我们需要提到我们想要部署的相应dist文件夹:

  1. 成功执行命令后,我们安装的用于将 Angular 应用程序部署到 GitHub Pages 的包将运行所需的作业,例如清理、获取原始代码、检出代码,最后将最新代码推送到存储库,并准备在 GitHub Pages 上托管:

  1. 一旦命令执行完毕,请转到您的 GitHub 帐户,并在存储库下点击“设置”。您将看到网站发布到以下网址:

  1. 点击存储库下显示的链接,我们应该看到我们的应用程序正在运行!

恭喜!我们刚刚将我们的第一个 Angular 应用程序发布到了 GitHub Pages:

在前面的一系列步骤中,我们学会了如何将我们的 Angular 应用程序部署到 GitHub Pages。在更现实的情况下,我们还需要将 API 或后端服务部署到我们的服务器上。我们可以通过将 API 部署到 Firebase 或自托管服务器来实现。

现在,继续为到目前为止创建的所有项目和应用程序重复相同的步骤。

总结

部署应用程序非常重要:我们的所有辛勤工作将在网站上线后展现出来。

部署 Angular 应用程序非常简单,只需生成所需的编译源代码,而且在最新版本的 Angular 中,AOT 编译默认为使用--prod meta标志生成的任何构建。我们了解了 AOT 的重要性以及对整体应用程序性能和安全性的关键性。我们学会了部署独立的 Angular 应用程序,以及由多个项目、库和组件组成的复合 Angular 应用程序。

最后,我们学会了如何使用官方的angular-cli-ghpages包将我们的 Angular 应用程序部署到 GitHub Pages。

这就是我们在本书的最后一章的结论。在学习过程中,我们从理解 TypeScript 语言的基础知识到学习如何通过实现 Angular 框架的组件、路由系统、指令、管道、表单、后端服务等来构建我们的 Angular 应用程序,我们走了很长一段路。

我们还学习了如何在我们的 Angular 应用中实现各种 CSS 框架,比如 Bootstrap、Angular Material 和 Flex 布局。此外,我们还学会了如何设计和使我们应用的用户界面更具吸引力和互动性。

我们探讨了使用 Jasmine 和 Karma 框架进行单元测试,这确保我们的应用经过了充分测试,并且实现非常稳固。

作为学习 Angular 高级主题的一部分,我们还实现了使用 Auth0 和 Firebase 的用户认证机制。最后,我们讨论了 Angular 应用的部署。

这是使用 Angular 框架开发应用的所有方面的 360 度概述。我们希望你现在有能力使用 Angular 框架构建世界一流的产品。

祝你一切顺利,并期待很快听到你的成功故事。

祝你好运!继续向前,不断进步。