作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Joaquin Cid
Verified Expert in Engineering

Joaquin是一名全栈和混合移动应用程序开发人员,在WebMD和Getty Images等公司工作了13年以上.

Read More

PREVIOUSLY AT

Velocity Partners
Share

俗话说,“每年互联网都会崩溃”,而开发者通常不得不去修复它. 与期待已久的 Angular version 9, 有人可能认为这是适用的, 在早期版本上开发的应用程序需要经历一个重大的迁移过程.

但事实并非如此! Angular团队完全重新设计了它的编译器, 导致更快的构建, faster test runs, 更小的包装尺寸, 最重要的是, 与旧版本的向后兼容性. 有了Angular 9,开发人员基本上可以获得所有的特权,而没有任何麻烦.

在这篇Angular 9教程中,我们将从头开始构建一个Angular应用程序. 我们将使用Angular 9的一些最新特性,并在此过程中介绍其他改进.

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

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

NPM install -g @angular/cli

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

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

Ng new ng9-app——create-application=false——strict

我们使用了两个参数 ng new command:

  • ——创建应用程序= false 会告诉CLI只生成工作空间文件吗. 当我们需要一个以上的应用程序和多个库时,这将帮助我们更好地组织代码.
  • --strict 会添加更严格的规则来强制TypeScript类型和代码清洁度吗.

因此,我们有了一个基本的工作空间文件夹和文件.

显示ng9-app文件夹的IDE截图,其中包含node_modules, .editorconfig, .gitignore,角.json, package-lock.json, package.json, README.md, tsconfig.json, and tslint.json.

现在,让我们添加一个新的应用程序. 为此,我们将运行:

生成应用程序tv-show-rating

我们会被提示:

? 你是否愿意与Angular团队分享这个项目的匿名使用数据
谷歌的隐私政策,网址是http://policies.google.com/privacy? For more
详细信息以及如何更改此设置,请参见http://angular.io/analytics. No
? 你想添加Angular路由吗? Yes
? 您希望使用哪种样式表格式? SCSS

Now, if we run ng serve,我们将看到应用程序在初始框架下运行.

A screenshot of Angular 9's scaffolding, with a notice that "tv-show-rating app is running!" There are also links to resources and next steps.

If we run ng build --prod,我们可以看到生成的文件列表.

A screenshot of Angular 9's "ng build --prod" output. It starts with "Generating ES5 bundles for differential loading..." After that's done, 它列出了几个JavaScript文件块-运行时, polyfills, and main, 每个都有-es2015和-es5版本,以及一个CSS文件. 最后一行给出了时间戳、散列和23,881毫秒的运行时间.

每个文件都有两个版本. 一种是与传统浏览器兼容, 另一个是针对ES2015编译的, 它使用较新的api,需要更少的填充运行在浏览器上.

Angular 9的一大改进是包的大小. 根据Angular团队的说法,对于大型应用程序,你可以看到高达40%的下降.

对于新创建的应用程序, bundle的大小与Angular 8非常相似, 但随着应用的发展, 你会看到包的大小比. previous versions.

Angular 9中引入的另一个特性是,如果有任何错误,它会向我们发出警告 component 样式CSS文件大于定义的阈值.

Angular 9 JSON配置文件中“预算”部分的截图, 使用数组中的两个对象. 第一个对象的“type”设置为“initial”,maximumWarning设置为2mb,和maximumError设置为“5mb”.第二个对象的“type”设置为“anyComponentStyle”,maximumWarning设置为6kb,, maximumError设置为10kb."

这将帮助我们捕获错误的样式导入或庞大的组件样式文件.

添加一个窗体来评价电视节目

接下来,我们将添加一个表单来对电视节目进行评级. 为此,首先,我们将安装 bootstrap and ng-bootstrap:

NPM install bootstrap @ng-bootstrap/ng-bootstrap

Angular 9的另一个改进是i18n(国际化). 以前,开发人员需要为应用程序中的每个地区运行完整的构建. 相反,Angular 9让我们只构建一次应用,然后在构建后的过程中生成所有的i18n文件, 显著减少构建时间. Since ng-bootstrap 有一个依赖于i18n的包,我们将把新包添加到我们的项目中:

添加@angular/ localalize

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

@ import " ~引导/ scss引导”;

And we’ll include NgbModule and ReactiveFormsModule in our AppModule on app.module.ts:

// ...
从“@angular/forms”中导入{ReactiveFormsModule};
从“@ng-bootstrap/ng-bootstrap”中导入{NgbModule};

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

接下来,我们将进行更新 app.component.html 我们的表单有一个基本的网格:

并生成表单组件:

TvRatingForm

Let’s update tv-rating-form.component.html 并添加表单来对电视节目进行评级.

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

// ...
导出类TvRatingFormComponent实现OnInit {

  tvShows = [
    {name: '最好打电话给Saul!' },
    {name: 'Breaking Bad'},
    {name: 'Lost'},
    {name: 'Mad men'}
  ];


  form = new FormGroup({
    new FormControl(", Validators . ".required),
    评级:new FormControl(”,Validators.required),
  });

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

}

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


至此,我们有了一些基本的UI功能. Now, if we run ng serve 再一次,我们可以看到它在运行.

一个Angular 9教程应用的屏幕截图,显示了一个名为“TV SHOW”的表单,下拉列表中列出了一些节目名称, a star-meter, and an OK button. 在动画中,用户选择一个节目,选择一个评级,然后单击OK按钮.

Before we move on, 让我们快速浏览一下Angular 9为帮助调试而添加的一些有趣的新特性. 因为这是我们日常工作中很常见的任务, 知道是什么改变让我们的生活变得更容易一点是值得的.

用Angular 9 Ivy调试

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

让我们看看实际情况. 首先,我们将激活模板检入 tsconfig.json:

{
  // ...
  " angularCompilerOptions ": {
    “fullTemplateTypeCheck”:没错,
    “strictInjectionParameters”:没错,
    “strictTemplates”:真的
  }
}

Now, if we update tvShows array and rename name to title:

  tvShows = [
    {title: '最好打电话给扫罗!' },
    {title: 'Breaking Bad'},
    {title: 'Lost'},
    {标题:《欧博体育app下载》}
  ];

我们将从编译器得到一个错误.

Angular 9/Angular Ivy编译器输出的截图, 具有文件名和位置, saying "error TS2339: Property 'name' does not exist on type '{ title: string; }'." It also shows the line of code in question and underlines the reference, 在这种情况下是电视收视率形式.component.其中tvShow . html文件.name is mentioned. After that, 对这个HTML文件的引用会被追踪到对应的TypeScript文件,并同样高亮显示.

这种类型检查将允许我们防止打字错误和错误地使用TypeScript类型.

Angular Ivy验证 @Input()

我们得到的另一个好的验证是 @Input(). 例如,我们可以把这个加到 tv-rating-form.component.ts:

@Input() title: string;

…and bind it in app.component.html:


…and then change app.component.ts like so:

// ...
导出类AppComponent {
  title = null;
}

如果我们做了这三个更改,我们将从编译器获得另一种类型的错误.

Angular 9/Angular Ivy编译器输出的截图, 格式与前一个类似, highlighting app.component.html with "error TS 2322: Type 'null' is not assignable to type 'string'."

如果我们想绕过它,我们 can use $any() 将值强制转换为的模板 any 并修复错误:


解决这个问题的正确方法是 title 关于nullable表单:

@Input() title: string | null;

The ExpressionChangedAfterItHasBeenCheckedError in Angular 9 Ivy

Angular开发中最可怕的错误之一就是 ExpressionChangedAfterItHasBeenCheckedError. Thankfully, Ivy以更清晰的方式输出错误, 这样更容易找到问题的来源.

那么,让我们来介绍一个 ExpressionChangedAfterItHasBeenCheckedError error. 要做到这一点,首先,我们将生成一个服务:

ng g s Title

Next, we’ll add a BehaviorSubject,以及访问 Observable 并发出一个新的值.

导出类TitleService {

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

  constructor() {}

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

  更新(标题:字符串){
    this.bs.next(title);
  }
}

之后,我们把这个加到 app.component.html:

      
      

{{title$ | async}}

And in app.component.ts,我们将注射 TitleService:

导出类AppComponent实现OnInit {

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

  constructor(
    private titleSvc: TitleService
  ) {}

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

Finally, in tv-rating-form.component.ts, we’ll inject TitleService 更新的标题 AppComponent,这将抛出一个 ExpressionChangedAfterItHasBeenCheckedError error.

  // ...

  constructor(
    private titleSvc: TitleService
  ) {

  }

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

现在我们可以在浏览器的开发控制台中看到详细的错误,并单击 app.component.html 会告诉我们错误在哪里.

浏览器开发控制台的屏幕截图, 显示了Angular Ivy对ExpressionChangedAfterItHasBeenCheckedError错误的报告. 用红色文本显示的堆栈跟踪给出了错误、先前值和当前值以及提示. 在堆栈跟踪的中间是唯一没有引用core的行.js. 用户点击它,被带到应用程序的行.component.导致错误的HTML.

我们可以通过将服务调用包装为 setTimeout:

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

要理解为什么 ExpressionChangedAfterItHasBeenCheckedError 错误发生,探索其他可能性, Maxim Koretskyi的帖子 关于这个话题值得一读.

Angular Ivy允许我们以更清晰的方式呈现错误,并帮助我们在代码中强制使用TypeScript类型. 在下一节中, 我们将介绍一些利用Ivy和调试的常见场景.

用组件束为Angular 9应用编写一个测试

在Angular 9中,引入了一个新的测试API 组件利用. 其背后的思想是消除与DOM交互所需的所有繁琐工作, 使它更容易使用和更稳定的运行.

组件线束API包含在 @angular/cdk 所以让我们首先在我们的项目中安装它:

NPM install @angular/cdk

现在我们可以编写一个测试并利用组件控制. In tv-rating-form.component.spec.ts,让我们设置测试:

从“@ng-bootstrap/ng-bootstrap”中导入{NgbModule};
从“@angular/forms”中导入{ReactiveFormsModule};

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

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

  // ...

});

接下来,让我们实现a ComponentHarness for our component. 我们将创建两个束带:一个用于 TvRatingForm, and another for NgbRating. ComponentHarness requires a static field, hostSelector,它应该取组件的选择器的值.

// ...

从“@angular/cdk/testing”中导入{ComponentHarness, HarnessLoader};
从“@angular/cdk/testing/testbed”中导入{TestbedHarnessEnvironment};

类TvRatingFormHarness扩展ComponentHarness {
  static hostSelector = 'app-tv-rating-form';
}

类NgbRatingHarness扩展ComponentHarness {
  static hostSelector = 'ngb-rating';
}

// ...

For our TvRatingFormHarness,我们将为提交按钮创建一个选择器,并创建一个函数来触发 click. 您可以看到实现它变得多么容易.

类TvRatingFormHarness扩展ComponentHarness {
  // ...
  protected getButton = this.locatorFor(“按钮”);

  async submit() {
    Const button =等待这个.getButton();
    await button.click();
  }
}

接下来,我们将添加方法来设置评级. Here we use locatorForAll 去寻找所有的 代表用户可以点击的星星的元素. The rate 函数只是获得所有可能的评级的星星,并点击一个对应的值发送.

类NgbRatingHarness扩展ComponentHarness {
  // ...

  protected getRatings =这个.locatorForAll(跨度:不.sr-only)');

  异步速率(值:number) {
    Const ratings =等待这个.getRatings();
    返回等级[value - 1].click();
  }
}

最后缺少的是连接 TvRatingFormHarness to NgbRatingHarness. 要做到这一点,我们只需在 TvRatingFormHarness class.

类TvRatingFormHarness扩展ComponentHarness {
  // ...
 
  getRating = this.locatorFor (NgbRatingHarness);

  // ...
}

现在,让我们编写测试:

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

  it('should pop an alert on submit', async () => {
    spyOn(窗口,“警报”);

    Const select = fixture.debugElement.query(By.css('select')).nativeElement;
    select.value = 'Lost';
    select.dispatchEvent(新事件('改变'));
    fixture.detectChanges();

    const harness = await TestbedHarnessEnvironment.harnessForFixture(夹具,TvRatingFormHarness);
    Const评级=等待控制.getRating();
    await rating.rate(1);
    await harness.submit();

    expect(window.alert).toHaveBeenCalledWith('{“tvShow”:“失去”,“等级”:1}”);
  });

});

注意,对于 select 在表单中,我们没有实现通过线束来设置它的值. 这是因为API仍然不支持选择选项. 但是这给了我们一个比较在组件控制之前与元素交互的机会.

做测试前还有最后一件事. We need to fix app.component.spec.ts since we updated title to be 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);
  });

});

Now, when we run ng test, our test passes.

Karma在我们的Angular 9应用上运行测试的截图. It shows "Ran 2 of 6 specs" with the message "Incomplete: fit() or fdescribe() was found, 2 specs, 0 failures, 随机分配种子69573." The TvRatingFormComponent's two tests are highlighted. AppComponent的三个测试和TitleService的一个测试都是灰色的.

回到我们的Angular 9示例应用:在数据库中保存数据

让我们通过添加到Firestore的连接并在数据库中保存评级来结束我们的Angular 9教程.

要做到这一点,我们需要 创建Firebase项目. 然后,我们将安装所需的依赖项.

NPM install @angular/fire firebase

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

导出const环境= {
  // ...
  firebase: {
    apiKey: {your-api-key},
    authDomain:“{your-project-id}.firebaseapp.com',
    databaseURL: http:// {your-project-id}.firebaseio.com',
    projectId: {your-project-id},
    storageBucket:“{your-project-id}.appspot.com',
    messagingSenderId: {your-messaging-id},
    appId:“{你的应用id}”
  }
};

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

从“@angular/fire”中导入{AngularFireModule};
从“@angular/fire/firestore”中导入{AngularFirestoreModule};
从“导入{环境}”../环境/环境”;

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

Next, in tv-rating-form.component.ts,我们将注射 AngularFirestore 在表单提交时提供服务并保存新评级:

从“@angular/fire/firestore”中导入{AngularFirestore};

导出类TvRatingFormComponent实现OnInit {

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

  异步提交(event: any) {
    this.form.disable();
    await this.af.收集(“评级”).add(this.form.value);
    this.form.enable();
    this.form.reset();
  }

}

一个Angular 9教程应用的屏幕截图,显示一个名为“TV SHOW”的表单在一个更大的页面标题“new title”下面!同样,它有一个下拉列表,列出了一些节目名称,一个星表和一个OK按钮, 用户再次选择了一个节目, selects a rating, 然后点击OK按钮.

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

Firebase控制台的截图. 左列是joaq-lab,其中包含一些集合:与会者、比赛、评级、测试和用户. 选择了评级项,并在中间列中显示了选中的ID—它是唯一的文档. The right column shows two fields: "rating" is set to 4, and "tvShow" is set to "Mad men."

最后,让我们列出所有的评级 AppComponent. To do that, in app.component.ts,我们将从集合中获取数据:

从“@angular/fire/firestore”中导入{AngularFirestore};

导出类AppComponent实现OnInit {
  // ... 
  ratings$: Observable;

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

  ngOnInit() {
    // ...
    this.ratings$ = this.af.收集(“评级”).valueChanges();
  }
}

…and in app.component.html,我们将添加一个评级列表:

// ...

{{rating.tvShow}}({{评级.rating}})

这就是我们的Angular 9教程应用程序组合在一起时的样子.

一个Angular 9教程应用的屏幕截图,显示一个名为“TV SHOW”的表单在一个更大的页面标题“new title”下面!同样,它有一个下拉列表,列出了一些节目名称,一个星表和一个OK按钮. This time, 右边一栏已经列出了《欧博体育app下载》(Mad men)。,,用户对《迷失》的评价为三星, 紧随其后的是《欧博体育app下载》(Mad men),评分为4星. 右边的一列在两个新评级之后保持字母顺序.

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

在这个Angular 9教程中, 我们已经介绍了构建一个基本表单, 将数据保存到Firebase, 并从中检索项目.

一路上,我们看到了Angular 9和Angular Ivy中包含了哪些改进和新特性. 要了解完整的列表,你可以查看Angular官方博客 最新发布帖子.


谷歌云合作伙伴徽章.

作为谷歌云合作伙伴,Toptal的谷歌认证专家可以为公司服务 on demand 为了他们最重要的项目.

关于总博客的进一步阅读:

了解基本知识

  • Angular是用来做什么的?

    Angular是一个JavaScript框架,可以帮助开发人员轻松构建快速的web和移动应用程序. 它带有预构建的功能,可以帮助开发人员快速启动和运行.

  • Angular的最新版本是什么?

    Angular的最新版本是Angular 10, 除了框架之外,它还更新了CLI和Angular Material. (在此之前,Angular 9是第一个默认启用Angular Ivy编译器的版本.)

  • 为什么Angular如此受欢迎?

    Angular创建于2013年,并作为一个全功能框架而广受欢迎, 它附带了许多web开发人员最常用的组件.

  • Angular值得学习吗??

    Yes. Angular是三大最流行的框架之一,拥有一个庞大而活跃的社区. 在谷歌的支持下,它很可能会伴随我们很多年.

  • Angular中的Ivy是什么?

    Angular Ivy是新的Angular编译器. 这是Angular团队花了两年时间进行重大重构的结果. 它为许多改进奠定了基础, 让Angular能够跟上web开发的最新创新.

  • 如何启用Angular Ivy?

    在Angular 9中,Ivy是默认启用的. 在Angular 8中也可以启用Ivy, 但是如果你还没有使用Angular 9的话, 建议升级到版本9以启用Ivy.

聘请Toptal这方面的专家.
Hire Now
Joaquin Cid

Joaquin Cid

Verified Expert in Engineering

阿根廷圣达菲省罗萨里奥

2018年5月2日成为会员

About the author

Joaquin是一名全栈和混合移动应用程序开发人员,在WebMD和Getty Images等公司工作了13年以上.

Read More
作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

PREVIOUSLY AT

Velocity Partners

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.