สิทธิพิเศษทั้งหมด ไม่ยุ่งยาก: บทช่วยสอน Angular 9
เผยแพร่แล้ว: 2022-03-11“ทุกๆ ปีอินเทอร์เน็ตล่ม” กล่าวโดยนักพัฒนามักจะต้องไปแก้ไข ด้วย Angular เวอร์ชัน 9 ที่รอคอยมายาวนาน บางคนอาจคิดว่าสิ่งนี้จะมีผลบังคับใช้ และแอปที่พัฒนาในเวอร์ชันก่อนหน้าจะต้องผ่านกระบวนการย้ายข้อมูลครั้งใหญ่
แต่นั่นไม่ใช่กรณี! ทีม Angular ได้ออกแบบคอมไพเลอร์ใหม่ทั้งหมด ส่งผลให้บิลด์เร็วขึ้น รันการทดสอบเร็วขึ้น ขนาดบันเดิลที่เล็กลง และที่สำคัญที่สุดคือความเข้ากันได้แบบย้อนหลังกับเวอร์ชันเก่า ด้วย Angular 9 นักพัฒนาโดยทั่วไปจะได้รับสิทธิพิเศษทั้งหมดโดยไม่ต้องยุ่งยาก
ในบทช่วยสอน Angular 9 นี้ เราจะสร้างแอปพลิเคชัน Angular ตั้งแต่เริ่มต้น เราจะใช้คุณลักษณะล่าสุดของ Angular 9 และทำการปรับปรุงอื่นๆ ต่อไป
บทช่วยสอน Angular 9: การเริ่มต้นด้วยแอปพลิเคชันเชิงมุมใหม่
มาเริ่มกันที่ตัวอย่างโครงการ Angular ของเรา ขั้นแรก ให้ติดตั้ง CLI ของ Angular เวอร์ชันล่าสุด:
npm install -g @angular/cli
เราสามารถตรวจสอบเวอร์ชัน Angular CLI ได้โดยใช้ ng version
ต่อไป มาสร้างแอปพลิเคชันเชิงมุมกัน:
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 ที่ใหม่กว่าและต้องการโพลีฟิลน้อยลงเพื่อทำงานบนเบราว์เซอร์
การปรับปรุงที่สำคัญอย่างหนึ่งของ 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";
และเราจะรวม NgbModule
และ ReactiveFormsModule
ใน AppModule
ของเราบน app.module.ts
:
// ... 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
เป็น title
:
tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];
…เราจะได้รับข้อผิดพลาดจากคอมไพเลอร์
การตรวจสอบประเภทนี้จะช่วยป้องกันการพิมพ์ผิดและการใช้ TypeScript อย่างไม่ถูกต้อง
การตรวจสอบความถูกต้องของ Angular Ivy สำหรับ @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>
วิธีที่ถูกต้องในการแก้ไขปัญหานี้คือการทำให้ title
ในแบบฟอร์มเป็นโมฆะ:
@Input() title: string | null ;
ExpressionChangedAfterItHasBeenCheckedError
ใน Angular 9 Ivy
หนึ่งในข้อผิดพลาดที่น่ากลัวที่สุดในการพัฒนาเชิงมุมคือ 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
เราจะฉีด TitleService
และอัปเดตชื่อของ AppComponent
ซึ่งจะส่งข้อผิดพลาด ExpressionChangedAfterItHasBeenCheckedError
// ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }
ตอนนี้ เราสามารถเห็นข้อผิดพลาดโดยละเอียดในคอนโซล dev ของเบราว์เซอร์ แล้วการคลิกที่ app.component.html
จะนำเราไปยังตำแหน่งที่เกิดข้อผิดพลาด
เราสามารถแก้ไขข้อผิดพลาดนี้ได้โดยปิดการเรียกบริการด้วย setTimeout
:
setTimeout(() => { this.titleSvc.update('new title!'); });
เพื่อทำความเข้าใจว่าทำไมข้อผิดพลาด ExpressionChangedAfterItHasBeenCheckedError
จึงเกิดขึ้นและสำรวจความเป็นไปได้อื่น ๆ การโพสต์ของ Maxim Koretskyi ในหัวข้อนั้นควรค่าแก่การอ่าน
Angular Ivy ช่วยให้เราแสดงข้อผิดพลาดได้ชัดเจนขึ้นและช่วยบังคับให้พิมพ์ TypeScript ในโค้ดของเรา ในส่วนต่อไปนี้ เราจะพูดถึงสถานการณ์ทั่วไปบางส่วนที่เราจะใช้ประโยชน์จาก Ivy และการดีบัก
การเขียนการทดสอบสำหรับแอป Angular 9 ของเราพร้อมสายรัดส่วนประกอบ
ใน Angular 9 มีการแนะนำ API การทดสอบใหม่ที่เรียกว่า component harnesses แนวคิดเบื้องหลังคือการขจัดงานน่าเบื่อทั้งหมดที่จำเป็นในการโต้ตอบกับ 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 ของเรา: การบันทึกข้อมูลในฐานข้อมูล
มาสรุปบทช่วยสอน 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 Partner ผู้เชี่ยวชาญที่ผ่านการรับรองจาก Google ของ Toptal พร้อมให้บริการสำหรับบริษัทต่างๆ ที่ต้องการสำหรับโครงการที่สำคัญที่สุดของพวกเขา