所有特權,無後顧之憂:Angular 9 教程

已發表: 2022-03-11

“每年互聯網都會中斷,”俗話說,開發人員通常不得不去修復它。 對於期待已久的 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 類型和代碼清潔度。

因此,我們有一個基本的工作區文件夾和文件。

顯示 ng9-app 文件夾的 IDE 屏幕截圖,其中包含 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 的腳手架屏幕截圖,帶有“tv-show-rating app is running!”的通知。還有指向資源和後續步驟的鏈接。

如果我們運行ng build --prod ,我們可以看到生成的文件列表。

Angular 9 的“ng build --prod”輸出的截圖。它以“為差異加載生成 ES5 包......”開始,完成之後,它列出了幾個 JavaScript 文件塊——運行時、polyfills 和 main,每個都有 -es2015 和 -es5 版本——以及一個 CSS 文件。最後一行給出了時間戳、哈希和 23,881 毫秒的運行時間。

我們有每個文件的兩個版本。 一種與舊版瀏覽器兼容,另一種是針對 ES2015 編譯的,它使用更新的 API 並且需要更少的 polyfill 在瀏覽器上運行。

Angular 9 的一大改進是包大小。 根據 Angular 團隊的說法,您可以看到大型應用程序的降幅高達 40%。

對於新創建的應用程序,包大小與 Angular 8 非常相似,但隨著應用程序的增長,您會看到包大小與以前的版本相比變得更小。

Angular 9 中引入的另一個功能是,如果任何組件樣式的 CSS 文件大於定義的閾值,則能夠警告我們。

Angular 9 JSON 配置文件的“預算”部分的屏幕截圖,數組中有兩個對象。第一個對象的“type”設置為“initial”,“maximumWarning”設置為“2mb”,“maximumError”設置為“5mb”。第二個對象的“type”設置為“anyComponentStyle”,“maximumWarning”設置為“6kb”,“maximumError”設置為“10kb”。

這將幫助我們捕獲錯誤的樣式導入或巨大的組件樣式文件。

添加表格以評價電視節目

接下來,我們將添加一個表單來為電視節目評分。 為此,首先,我們將安裝bootstrapng-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";

我們將在AppModule上的app.module.ts中包含NgbModuleReactiveFormsModule

 // ... 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 gc 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 serve ,我們可以看到它的實際效果。

Angular 9 教程應用程序的屏幕截圖,顯示了一個名為“TV SHOW”的表單,下拉列表中列出了一些節目標題、一個星表和一個 OK 按鈕。在動畫中,用戶選擇一個節目,選擇一個評級,然後單擊“確定”按鈕。

在繼續之前,讓我們快速瀏覽一些有趣的 Angular 9 新特性,這些特性是為了幫助調試而添加的。 由於這是我們日常工作中非常常見的任務,因此值得了解發生了什麼變化以使我們的生活更輕鬆一些。

使用 Angular 9 Ivy 進行調試

Angular 9 和 Angular Ivy 中引入的另一個重大改進是調試體驗。 編譯器現在可以檢測到更多錯誤並以更“可讀”的方式拋出它們。

讓我們看看它的實際效果。 首先,我們將在tsconfig.json中激活模板檢查:

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

現在,如果我們更新tvShows數組並將 name 重nametitle

 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()的 Angular Ivy 驗證

我們得到的另一個很好的驗證是@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>

但是,解決此問題的正確方法是將表單上的title設為可空:

 @Input() title: string | null ;

Angular 9 Ivy 中的ExpressionChangedAfterItHasBeenCheckedError

Angular 開發中最可怕的錯誤之一是ExpressionChangedAfterItHasBeenCheckedError 。 值得慶幸的是,Ivy 以更清晰的方式輸出錯誤,從而更容易找到問題的根源。

所以,讓我們引入一個ExpressionChangedAfterItHasBeenCheckedError錯誤。 為此,首先,我們將生成一個服務:

 ng gs 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 form.component.ts 中,我們將注入TitleService並更新AppComponent的標題,這將引發ExpressionChangedAfterItHasBeenCheckedError錯誤。

 // ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }

現在我們可以在瀏覽器的開發控制台中看到詳細的錯誤信息,點擊app.component.html會指出錯誤所在。

瀏覽器開發控制台的屏幕截圖,顯示 Angular Ivy 報告 ExpressionChangedAfterItHasBeenCheckedError 錯誤。紅色文本中的堆棧跟踪給出了錯誤、先前和當前值以及提示。在堆棧跟踪的中間是唯一沒有引用 core.js 的行。用戶單擊它並被帶到導致錯誤的 app.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

現在我們可以編寫一個測試並利用組件線束。 在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 ,另一個用於NgbRatingComponentHarness需要一個static字段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 ,我們將為提交按鈕創建一個選擇器和一個觸發click的函數。 您可以看到實現這一點變得多麼容易。

 class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }

接下來,我們將添加設置評級的方法。 這裡我們使用locatorForAll來查找代表用戶可以點擊的星星的所有<span>元素。 rate函數只是獲取所有可能的評分星,然後單擊與發送的值相對應的星。

 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}'); }); });

請注意,對於我們在表單中的select ,我們沒有實現通過線束設置其值。 那是因為 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() or fdescribe() was found, 2 specs, 0 failures, randomized with seed 69573.” TvRatingFormComponent 的兩個測試被突出顯示。 AppComponent 的三項測試和 TitleService 的一項測試都是灰色的。

回到我們的 Angular 9 示例應用程序:將數據保存在數據庫中

讓我們通過添加到 Firestore 的連接並將評級保存在數據庫中來結束我們的 Angular 9 教程。

為此,我們需要創建一個 Firebase 項目。 然後,我們將安裝所需的依賴項。

 npm install @angular/fire firebase

在 Firebase 控制台的項目設置中,我們將獲取其配置並將它們添加到environment.tsenvironment.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 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 教程應用程序的屏幕截圖,在較大的頁面標題“新標題!”下方顯示標題為“電視節目”的表單同樣,它有一個下拉列表,列出了一些節目標題、一個星表和一個 OK 按鈕,用戶再次選擇一個節目,選擇一個評級,然後單擊 OK 按鈕。

現在,當我們轉到 Firebase 控制台時,我們將看到新創建的項目。

Firebase 控制台的屏幕截圖。左欄中是 joaq-lab,其中包含一些集合:參加者、比賽、評級、測試和用戶。評級項目被選中,並顯示在中間列中,並帶有一個選定的 ID——它是唯一的文檔。右欄顯示兩個字段:“rating”設置為 4,“tvShow”設置為“Mad men”。

最後,讓我們在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 教程應用程序的屏幕截圖,在較大的頁面標題“新標題!”下方顯示標題為“電視節目”的表單同樣,它有一個下拉列表,列出了一些節目標題、一個星表和一個確定按鈕。這一次,右側的一欄已經列出了“Mad men (4)”,用戶對 Lost 的評價為三顆星,其次是“Mad men”,再次為四顆星。在兩個新評級之後,右側列仍按字母順序排列。

Angular 9 和 Angular Ivy:更好的開發、更好的應用和更好的兼容性

在這個 Angular 9 教程中,我們介紹了構建基本表單、將數據保存到 Firebase 以及從中檢索項目。

在此過程中,我們看到了 Angular 9 和 Angular Ivy 中包含了哪些改進和新功能。 如需完整列表,您可以查看官方 Angular 博客的最新發布帖子。


Google Cloud 合作夥伴徽章。

作為 Google Cloud 合作夥伴,Toptal 的 Google 認證專家可根據公司最重要項目的需求提供給他們。