个性化阅读
专注于IT技术分析

所有的好处,没有麻烦:Angular 9教程

本文概述

俗话说:”每年互联网都会中断, 开发人员通常必须去修复它。对于期待已久的Angular 9版本, 人们可能会认为这将适用, 并且在较早版本上开发的应用程序将需要经历主要的迁移过程。

但是事实并非如此! Angular团队完全重新设计了其编译器, 从而实现了更快的构建, 更快的测试运行, 更小的捆绑包大小以及最重要的是与旧版本的向后兼容性。使用Angular 9, 开发人员基本上可以获得所有好处。

在本Angular 9教程中, 我们将从头开始构建Angular应用程序。我们将使用Angular 9的一些最新功能, 并逐步介绍其他改进。

Angular 9教程:从新的Angular应用程序开始

让我们开始我们的Angular项目示例。首先, 让我们安装最新版本的Angular CLI:

npm install -g @angular/cli

我们可以通过运行ng version来验证Angular CLI版本。

接下来, 让我们创建一个Angular应用程序:

ng new ng9-app --create-application=false --strict

我们在ng new命令中使用了两个参数:

  • –create-application = false将告诉CLI仅生成工作区文件。当我们需要多个应用程序和多个库时, 这将帮助我们更好地组织代码。
  • –strict将添加更严格的规则以强制执行更多的TypeScript输入和代码清洁度。

结果, 我们有了一个基本的工作区文件夹和文件。

IDE的屏幕快照,显示ng9-app文件夹,其中包含node_modules,.editorconfig,.gitignore,angular.json,package-lock.json,package.json,README.md,tsconfig.json和tslint.json。

现在, 让我们添加一个新应用。为此, 我们将运行:

ng generate application tv-show-rating

我们将被提示:

? Would you like to share anonymous usage data about this project with the Angular Team at
Google under Google's Privacy Policy at https://policies.google.com/privacy? For more
details and how to change this setting, see http://angular.io/analytics. No
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

现在, 如果我们运行ng serve, 我们将看到该应用程序以其初始脚手架运行。

Angular 9脚手架的屏幕截图,并显示"电视节目评级应用正在运行!"也有指向资源和下一步的链接。

如果运行ng build –prod, 则可以看到生成文件的列表。

Angular 9的" ng build --prod"输出的屏幕截图。它从"正在生成ES5捆绑软件以进行差异加载..."开始。完成之后,它列出了几个JavaScript文件块(运行时,polyfills和main,每个都有-es2015和-es5版本)和一个CSS文件。最后一行给出了时间戳,哈希值和23,881毫秒的运行时间。

每个文件都有两个版本。一种与旧版浏览器兼容, 另一种针对ES2015进行编译, 该ES2015使用更新的API, 并且需要更少的polyfill才能在浏览器上运行。

Angular 9的一大改进是捆绑包大小。据Angular团队称, 大型应用程序最多可减少40%。

对于新创建的应用, 捆绑包的大小与Angular 8非常相似, 但是随着应用的增长, 捆绑包的大小会比以前的版本缩小。

Angular 9中引入的另一个功能是能够警告我们任何组件样式CSS文件是否大于定义的阈值。

Angular 9 JSON配置文件的"预算"部分的屏幕快照,数组中有两个对象。第一个对象的"类型"设置为"初始","最大警告"设置为" 2mb","最大错误"设置为" 5mb"。第二个对象的"类型"设置为" anyComponentStyle","最大警告"设置为" 6kb","最大错误"设置为" 10kb"。

这将帮助我们捕获不良样式导入或巨大的组件样式文件。

添加表格对电视节目进行评分

接下来, 我们将添加一个表格来对电视节目进行评分。为此, 首先, 我们将安装bootstrap和ng-bootstrap:

npm install bootstrap @ng-bootstrap/ng-bootstrap

Angular 9的另一个改进是i18n(国际化)。以前, 开发人员需要针对应用程序中的每个区域设置运行完整版本。 Angular 9让我们一次构建一个应用程序, 并在构建后的过程中生成所有i18n文件, 从而大大减少了构建时间。由于ng-bootstrap依赖于i18n, 因此我们将新软件包添加到我们的项目中:

ng add @angular/localize

接下来, 我们将Bootstrap主题添加到应用程序的styles.scss中:

@import "~bootstrap/scss/bootstrap";

我们将在app.module.ts的AppModule中包含NgbModule和ReactiveFormsModule:

// ...
import { ReactiveFormsModule } from '@angular/forms';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [
    // ...
    ReactiveFormsModule, NgbModule
  ], })

接下来, 我们将使用表单的基本网格更新app.component.html:

<div class="container">
  <div class="row">
    <div class="col-6">

    </div>
  </div>
</div>

并生成表单组件:

ng g c TvRatingForm

让我们更新tv-rating-form.component.html并添加该表单以对电视节目进行评分。

<form [formGroup]="form"
      (ngSubmit)="submit()"
      class="mt-3">
  <div class="form-group">
    <label>TV SHOW</label>
    <select class="custom-select"
            formControlName="tvShow">
      <option *ngFor="let tvShow of tvShows"
              [value]="tvShow.name">{{tvShow.name}}</option>
    </select>
  </div>
  <div class="form-group">
    <ngb-rating [max]="5"
                formControlName="rating"></ngb-rating>
  </div>

  <button [disabled]="form.invalid || form.disabled" class="btn btn-primary">OK</button>
</form>

tv-rating-form.component.ts看起来像这样:

// ...
export class TvRatingFormComponent implements OnInit {

  tvShows = [
    { name: 'Better call Saul!' }, { name: 'Breaking Bad' }, { name: 'Lost' }, { name: 'Mad men' }
  ];


  form = new FormGroup({
    tvShow: new FormControl('', Validators.required), rating: new FormControl('', Validators.required), });

  submit() {
    alert(JSON.stringify(this.form.value));
    this.form.reset();
  }

}

最后, 让我们将表单添加到app.component.html中:

<!-- ... -->
<div class="col-6">
  <app-tv-rating-form></app-tv-rating-form>
</div>

至此, 我们有了一些基本的UI功能。现在, 如果我们再次执行ng服务, 那么我们可以看到它的作用。

Angular 9教程应用程序的屏幕截图,显示标题为" TV SHOW"的表单,下拉列表列出了少数节目标题,星表和"确定"按钮。在动画中,用户选择一个节目,选择一个等级,然后单击"确定"按钮。

在继续之前, 让我们快速看一下一些有趣的Angular 9新功能, 这些功能是为了帮助调试而添加的。由于这是我们日常工作中非常常见的任务, 因此有必要知道哪些变化使我们的生活更加轻松。

使用Angular 9 Ivy进行调试

Angular 9和Angular Ivy中引入的另一个重大改进是调试体验。编译器现在可以检测更多错误, 并以”更易读”的方式抛出它们。

让我们来看看它的实际效果。首先, 我们将在tsconfig.json中激活模板检查:

{
  // ...
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true
  }
}

现在, 如果我们更新tvShows数组并将名称重命名为title:

  tvShows = [
    { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' }
  ];

…我们会从编译器中得到一个错误。

Angular 9 / Angular Ivy编译器输出的屏幕快照,带有文件名和位置,并显示"错误TS2339:类型'{title:string;}'不存在属性'name'。"它还显示了有问题的代码行并在引用下划线,在这种情况下,在tv-rating-form.component.html文件中提到了tvShow.name。此后,对此HTML文件的引用将追溯到相应的TypeScript文件并类似地突出显示。

这种类型检查将使我们能够防止输入错误和TypeScript类型的错误使用。

@Input()的角常春藤验证

我们获得的另一个很好的验证是使用@Input()。例如, 我们可以将其添加到tv-rating-form.component.ts中:

@Input() title: string;

…并将其绑定到app.component.html中:

<app-tv-rating-form [title]="title"></app-tv-rating-form>

…然后像这样更改app.component.ts:

// ...
export class AppComponent {
  title = null;
}

如果进行这三处更改, 则会从编译器中获得另一种类型的错误。

Angular 9 / Angular Ivy编译器输出的屏幕截图,格式类似于上一个,突出显示了app.component.html并显示"错误TS 2322:类型'null'无法分配给类型'string'。"

如果我们想绕过它, 我们可以在模板上使用$ any()将值强制转换为any并修复错误:

<app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>

解决此问题的正确方法是使表格上的标题可为空:

@Input() title: string | null ;

Angular 9 Ivy中的ExpressionChangedAfterItHasBeenCheckedError

Angular开发中最可怕的错误之一是ExpressionChangedAfterItHaHasBeenCheckedError。幸运的是, Ivy以更清晰的方式输出错误, 从而更容易找到问题的根源。

因此, 我们来介绍一个ExpressionChangedAfterItHaHasBeenCheckedError错误。为此, 首先, 我们将生成一个服务:

ng g s Title

接下来, 我们将添加一个BehaviorSubject以及用于访问Observable并发出新值的方法。

export class TitleService {

  private bs = new BehaviorSubject < string > ('');

  constructor() {}

  get title$() {
    return this.bs.asObservable();
  }

  update(title: string) {
    this.bs.next(title);
  }
}

之后, 我们将其添加到app.component.html中:

      <!-- ... -->
      <div class="col-6">
        <h2>
          {{title$ | async}}
        </h2>
        <app-tv-rating-form [title]="title"></app-tv-rating-form>
      </div>

然后在app.component.ts中, 注入TitleService:

export class AppComponent implements OnInit {

  // ...
  title$: Observable < string > ;

  constructor(
    private titleSvc: TitleService
  ) {}

  ngOnInit() {
    this.title$ = this.titleSvc.title$;
  }
  // ...
}

最后, 在tv-rating-form.component.ts中, 我们将注入TitleService并更新AppComponent的标题, 这将引发ExpressionChangedAfterItHasBeenCheckedError错误。

  // ...

  constructor(
    private titleSvc: TitleService
  ) {

  }

  ngOnInit() {
    this.titleSvc.update('new title!');
  }

现在, 我们可以在浏览器的开发人员控制台中看到详细的错误, 单击app.component.html可以将我们指向错误的位置。

浏览器开发者控制台的屏幕截图,显示了Angular Ivy对ExpressionChangedAfterItHaHasBeenCheckedError错误的报告。红色文本中的堆栈跟踪会给出错误,先前值和当前值以及提示。在堆栈跟踪的中间,是唯一不引用core.js的行。用户单击它,将其带到引起错误的app.component.html行中。

我们可以通过使用setTimeout包装服务调用来解决此错误:

setTimeout(() => {
  this.titleSvc.update('new title!');
});

要了解为什么会发生ExpressionChangedAfterItHaHasBeenCheckedError错误并探索其他可能性, Maxim Koretskyi关于该主题的帖子值得一读。

Angular Ivy使我们能够以更清晰的方式呈现错误, 并帮助强制在代码中键入TypeScript。在以下部分中, 我们将介绍一些利用Ivy和调试的常见方案。

使用组件线束为我们的Angular 9应用编写测试

在Angular 9中, 引入了一个新的测试API, 称为组件线束。其背后的想法是消除与DOM交互所需的所有琐事, 从而使其更易于使用且运行更稳定。

组件线束API包含在@ angular / cdk库中, 因此我们首先将其安装在我们的项目中:

npm install @angular/cdk

现在我们可以写出一个测试并利用组件工具。在tv-rating-form.component.spec.ts中, 设置测试:

import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ReactiveFormsModule } from '@angular/forms';

describe('TvRatingFormComponent', () => {
  let component: TvRatingFormComponent;
  let fixture: ComponentFixture < TvRatingFormComponent > ;

  beforeEach(async (() => {
    TestBed.configureTestingModule({
      imports: [
        NgbModule, ReactiveFormsModule
      ], declarations: [TvRatingFormComponent]
    }).compileComponents();
  }));

  // ...

});

接下来, 让我们为我们的组件实现ComponentHarness。我们将创建两个线束:一个用于TvRatingForm, 另一个用于NgbRating。 ComponentHarness需要一个静态字段hostSelector, 该字段应采用组件选择器的值。

// ...

import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';

class TvRatingFormHarness extends ComponentHarness {
  static hostSelector = 'app-tv-rating-form';
}

class NgbRatingHarness extends ComponentHarness {
  static hostSelector = 'ngb-rating';
}

// ...

对于我们的TvRatingFormHarness, 我们将为”提交”按钮创建一个选择器, 并创建一个触发点击的函数。你会看到实现此操作变得多么容易。

class TvRatingFormHarness extends ComponentHarness {
  // ...
  protected getButton = this.locatorFor('button');

  async submit() {
    const button = await this.getButton();
    await button.click();
  }
}

接下来, 我们将添加设置评分的方法。在这里, 我们使用locatorForAll查找表示用户可以单击的星形的所有<span>元素。评分功能仅获取所有可能的评分星号, 然后单击与发送的值相对应的一个。

class NgbRatingHarness extends ComponentHarness {
  // ...

  protected getRatings = this.locatorForAll('span:not(.sr-only)');

  async rate(value: number) {
    const ratings = await this.getRatings();
    return ratings[value - 1].click();
  }
}

最后遗漏的是将TvRatingFormHarness连接到NgbRatingHarness。为此, 我们只需在TvRatingFormHarness类上添加定位器。

class TvRatingFormHarness extends ComponentHarness {
  // ...
 
  getRating = this.locatorFor(NgbRatingHarness);

  // ...
}

现在, 让我们编写测试:

describe('TvRatingFormComponent', () => {
  // ...

  it('should pop an alert on submit', async () => {
    spyOn(window, 'alert');

    const select = fixture.debugElement.query(By.css('select')).nativeElement;
    select.value = 'Lost';
    select.dispatchEvent(new Event('change'));
    fixture.detectChanges();

    const harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, TvRatingFormHarness);
    const rating = await harness.getRating();
    await rating.rate(1);
    await harness.submit();

    expect(window.alert).toHaveBeenCalledWith('{"tvShow":"Lost", "rating":1}');
  });

});

请注意, 对于我们在表单中进行的选择, 我们并未通过线束实现设置其值。那是因为API仍然不支持选择选项。但是, 这使我们有机会在这里比较在利用组件之前与元素进行交互的方式。

在运行测试之前的最后一件事。我们需要修复app.component.spec.ts, 因为我们将title更新为null。

describe('AppComponent', () => {
  // ...
  it(`should have as title 'tv-show-rating'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual(null);
  });

});

现在, 当我们运行ng test时, 我们的测试通过了。

Karma在我们的Angular 9应用程序上运行测试的屏幕截图。它显示" Ran 2 of 6 specs",并显示消息" Incomplete:找到了fit()或fdescribe(),2个spec,0个失败,随机种子69573"。 TvRatingFormComponent的两个测试突出显示。 AppComponent的三个测试和TitleService的一个测试均为灰色。

返回我们的Angular 9示例应用程序:将数据保存在数据库中

让我们结束Angular 9教程, 方法是添加到Firestore的连接并将评分保存在数据库中。

为此, 我们需要创建一个Firebase项目。然后, 我们将安装所需的依赖项。

npm install @angular/fire firebase

在Firebase控制台的项目设置中, 我们将获取其配置并将其添加到environment.ts和environment.prod.ts中:

export const environment = {
  // ...
  firebase: {
    apiKey: '{your-api-key}', authDomain: '{your-project-id}.firebaseapp.com', databaseURL: 'https://{your-project-id}.firebaseio.com', projectId: '{your-project-id}', storageBucket: '{your-project-id}.appspot.com', messagingSenderId: '{your-messaging-id}', appId: '{your-app-id}'
  }
};

之后, 我们将必要的模块导入app.module.ts中:

import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from '../environments/environment';

@NgModule({
  // ...
  imports: [
    // ...
    AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ], // ...
})

接下来, 在tv-rating-form.component.ts中, 我们将注入AngularFirestore服务, 并在提交表单时保存新的评分:

import { AngularFirestore } from '@angular/fire/firestore';

export class TvRatingFormComponent implements OnInit {

  constructor(
    // ...
    private af: AngularFirestore, ) { }

  async submit(event: any) {
    this.form.disable();
    await this.af.collection('ratings').add(this.form.value);
    this.form.enable();
    this.form.reset();
  }

}
Angular 9教程应用程序的屏幕截图,在较大的页面标题"新标题"下方显示了标题为" TV SHOW"的表单。再次,它有一个下拉列表,列出了几个表演标题,一个星表和一个"确定"按钮,用户再次选择了一个表演,选择了一个评分,然后单击"确定"按钮。

现在, 当我们转到Firebase控制台时, 我们将看到新创建的项目。

Firebase控制台的屏幕截图。左列是joaq-lab,其中包含一些集合:参与者,种族,等级,测试和用户。已选择评级项目,并且该评级项目在中间列中具有选中的ID,这是唯一的文档。右列显示两个字段:"等级"设置为4," tvShow"设置为"疯子"。

最后, 让我们将所有分级都列出到AppComponent中。为此, 请在app.component.ts中, 从集合中获取数据:

import { AngularFirestore } from '@angular/fire/firestore';

export class AppComponent implements OnInit {
  // ... 
  ratings$: Observable<any>;

  constructor(
    // ...
    private af: AngularFirestore
  ) { }

  ngOnInit() {
    // ...
    this.ratings$ = this.af.collection('ratings').valueChanges();
  }
}

…在app.component.html中, 我们将添加一个评分列表:

<div class="container">
  <div class="row">
    // ...
    <div class="col-6">
      <div>
        <p *ngFor="let rating of ratings$ | async">
          {{rating.tvShow}} ({{rating.rating}})
        </p>
      </div>
    </div>
  </div>
</div>

这就是我们Angular 9教学应用的全部外观。

Angular 9教程应用程序的屏幕截图,在较大的页面标题"新标题"下方显示了标题为" TV SHOW"的表单。同样,它还有一个下拉列表,列出了几个节目标题,一个星表和一个"确定"按钮。这次,右边一列已经列出了"疯子(4)",用户将"迷失"评为三颗星,其后再次将"疯子"评级为四颗星。在两个新评级之后,右侧列均按字母顺序排列。

Angular 9和Angular Ivy:更好的开发, 更好的应用程序和更好的兼容性

在本Angular 9教程中, 我们介绍了构建基本表单, 将数据保存到Firebase以及从中检索项目的过程。

一路上, 我们看到了Angular 9和Angular Ivy包括哪些改进和新功能。有关完整列表, 你可以查看Angular官方博客的最新版本。


Google Cloud合作伙伴徽章。

作为Google Cloud合作伙伴, srcmini的Google认证专家可以按需为最重要的项目提供公司服务。

赞(0)
未经允许不得转载:srcmini » 所有的好处,没有麻烦:Angular 9教程

评论 抢沙发

评论前必须登录!