本文概述
- 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输入和代码清洁度。
结果, 我们有了一个基本的工作区文件夹和文件。
现在, 让我们添加一个新应用。为此, 我们将运行:
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, 我们将看到该应用程序以其初始脚手架运行。
如果运行ng build –prod, 则可以看到生成文件的列表。
每个文件都有两个版本。一种与旧版浏览器兼容, 另一种针对ES2015进行编译, 该ES2015使用更新的API, 并且需要更少的polyfill才能在浏览器上运行。
Angular 9的一大改进是捆绑包大小。据Angular团队称, 大型应用程序最多可减少40%。
对于新创建的应用, 捆绑包的大小与Angular 8非常相似, 但是随着应用的增长, 捆绑包的大小会比以前的版本缩小。
Angular 9中引入的另一个功能是能够警告我们任何组件样式CSS文件是否大于定义的阈值。
这将帮助我们捕获不良样式导入或巨大的组件样式文件。
添加表格对电视节目进行评分
接下来, 我们将添加一个表格来对电视节目进行评分。为此, 首先, 我们将安装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 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' }
];
…我们会从编译器中得到一个错误。
这种类型检查将使我们能够防止输入错误和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;
}
如果进行这三处更改, 则会从编译器中获得另一种类型的错误。
如果我们想绕过它, 我们可以在模板上使用$ 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可以将我们指向错误的位置。
我们可以通过使用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时, 我们的测试通过了。
返回我们的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();
}
}
现在, 当我们转到Firebase控制台时, 我们将看到新创建的项目。
最后, 让我们将所有分级都列出到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 Ivy:更好的开发, 更好的应用程序和更好的兼容性
在本Angular 9教程中, 我们介绍了构建基本表单, 将数据保存到Firebase以及从中检索项目的过程。
一路上, 我们看到了Angular 9和Angular Ivy包括哪些改进和新功能。有关完整列表, 你可以查看Angular官方博客的最新版本。
作为Google Cloud合作伙伴, srcmini的Google认证专家可以按需为最重要的项目提供公司服务。
评论前必须登录!
注册