所有特權,無後顧之憂: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 類型和代碼清潔度。
因此,我們有一個基本的工作區文件夾和文件。
現在,讓我們添加一個新應用。 為此,我們將運行:
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 編譯的,它使用更新的 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";
我們將在AppModule
上的app.module.ts
中包含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 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 新特性,這些特性是為了幫助調試而添加的。 由於這是我們日常工作中非常常見的任務,因此值得了解發生了什麼變化以使我們的生活更輕鬆一些。
使用 Angular 9 Ivy 進行調試
Angular 9 和 Angular Ivy 中引入的另一個重大改進是調試體驗。 編譯器現在可以檢測到更多錯誤並以更“可讀”的方式拋出它們。
讓我們看看它的實際效果。 首先,我們將在tsconfig.json
中激活模板檢查:
{ // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }
現在,如果我們更新tvShows
數組並將 name 重name
為title
:
tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];
…我們會從編譯器中得到一個錯誤。
這種類型檢查將允許我們防止打字錯誤和 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; }
如果我們進行這三個更改,我們將從編譯器中得到另一種類型的錯誤。
如果我們想繞過它,我們可以在模板上使用$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
會指出錯誤所在。
我們可以通過使用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
,另一個用於NgbRating
。 ComponentHarness
需要一個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
時,我們的測試通過了。
回到我們的 Angular 9 示例應用程序:將數據保存在數據庫中
讓我們通過添加到 Firestore 的連接並將評級保存在數據庫中來結束我們的 Angular 9 教程。
為此,我們需要創建一個 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
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 合作夥伴,Toptal 的 Google 認證專家可根據公司最重要項目的需求提供給他們。