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

本文概述

  • Angular 9教程:从新的Angular应用程序开始
  • 添加表格对电视节目进行评分
  • 使用Angular 9 Ivy进行调试
  • 使用组件线束为我们的Angular 9应用编写测试
  • 返回我们的Angular 9示例应用程序:将数据保存在数据库中
  • Angular 9和Angular Ivy:更好的开发, 更好的应用程序和更好的兼容性
俗话说:” 每年互联网都会中断, 开发人员通常必须去修复它。对于期待已久的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输入和代码清洁度。
结果, 我们有了一个基本的工作区文件夹和文件。
所有的好处,没有麻烦(Angular 9教程)

文章图片
现在, 让我们添加一个新应用。为此, 我们将运行:
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教程)

文章图片
每个文件都有两个版本。一种与旧版浏览器兼容, 另一种针对ES2015进行编译, 该ES2015使用更新的API, 并且需要更少的polyfill才能在浏览器上运行。
Angular 9的一大改进是捆绑包大小。据Angular团队称, 大型应用程序最多可减少40%。
对于新创建的应用, 捆绑包的大小与Angular 8非常相似, 但是随着应用的增长, 捆绑包的大小会比以前的版本缩小。
Angular 9中引入的另一个功能是能够警告我们任何组件样式CSS文件是否大于定义的阈值。
所有的好处,没有麻烦(Angular 9教程)

文章图片
这将帮助我们捕获不良样式导入或巨大的组件样式文件。
添加表格对电视节目进行评分 接下来, 我们将添加一个表格来对电视节目进行评分。为此, 首先, 我们将安装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教程)

文章图片
在继续之前, 让我们快速看一下一些有趣的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教程)

文章图片
这种类型检查将使我们能够防止输入错误和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教程)

文章图片
如果我们想绕过它, 我们可以在模板上使用$ 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 9教程)

文章图片
我们可以通过使用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 = 'http://www.srcmini.com/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时, 我们的测试通过了。
所有的好处,没有麻烦(Angular 9教程)

文章图片
返回我们的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教程)

文章图片
现在, 当我们转到Firebase控制台时, 我们将看到新创建的项目。
所有的好处,没有麻烦(Angular 9教程)

文章图片
最后, 让我们将所有分级都列出到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教程)

文章图片
Angular 9和Angular Ivy:更好的开发, 更好的应用程序和更好的兼容性 在本Angular 9教程中, 我们介绍了构建基本表单, 将数据保存到Firebase以及从中检索项目的过程。
一路上, 我们看到了Angular 9和Angular Ivy包括哪些改进和新功能。有关完整列表, 你可以查看Angular官方博客的最新版本。
所有的好处,没有麻烦(Angular 9教程)

文章图片
【所有的好处,没有麻烦(Angular 9教程)】作为Google Cloud合作伙伴, srcmini的Google认证专家可以按需为最重要的项目提供公司服务。

    推荐阅读