Wszystkie korzyści, bez kłopotów: samouczek Angular 9
Opublikowany: 2022-03-11„Każdego roku internet się psuje”, mówi powiedzenie, a programiści zwykle muszą to naprawić. W przypadku długo oczekiwanej wersji 9 Angulara można by pomyśleć, że to miałoby zastosowanie, a aplikacje opracowane we wcześniejszych wersjach musiałyby przejść poważny proces migracji.
Ale tak nie jest! Zespół Angulara całkowicie przeprojektował swój kompilator, co zaowocowało szybszymi kompilacjami, szybszymi testami, mniejszymi rozmiarami pakietów i, co najważniejsze, wsteczną kompatybilnością ze starszymi wersjami. Dzięki Angular 9 programiści zasadniczo uzyskują wszystkie korzyści bez żadnych kłopotów.
W tym samouczku Angular 9 zbudujemy aplikację Angular od podstaw. Wykorzystamy niektóre z najnowszych funkcji Angulara 9 i przy okazji omówimy inne ulepszenia.
Samouczek Angulara 9: Rozpoczęcie od nowej aplikacji Angular
Zacznijmy od naszego przykładu projektu Angular. Najpierw zainstalujmy najnowszą wersję CLI Angulara:
npm install -g @angular/cli
Wersję Angular CLI możemy zweryfikować uruchamiając ng version
.
Następnie stwórzmy aplikację Angular:
ng new ng9-app --create-application=false --strict
W naszym ng new
używamy dwóch argumentów:
-
--create-application=false
powie CLI, aby generował tylko pliki obszaru roboczego. Pomoże nam to lepiej zorganizować nasz kod, gdy potrzebujemy więcej niż jednej aplikacji i wielu bibliotek. -
--strict
doda bardziej rygorystyczne reguły, aby wymusić pisanie w języku TypeScript i czystość kodu.
Dzięki temu mamy podstawowy folder i pliki obszaru roboczego.
Teraz dodajmy nową aplikację. W tym celu uruchomimy:
ng generate application tv-show-rating
Zostaniemy poproszeni:
? 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
Teraz, jeśli uruchomimy ng serve
, zobaczymy, że aplikacja działa z początkowym rusztowaniem.
Jeśli uruchomimy ng build --prod
, możemy zobaczyć listę wygenerowanych plików.
Mamy dwie wersje każdego pliku. Jedna jest zgodna ze starszymi przeglądarkami, a druga jest skompilowana pod kątem ES2015, która korzysta z nowszych interfejsów API i wymaga mniejszej liczby wypełniaczy do działania w przeglądarkach.
Dużym ulepszeniem Angulara 9 jest rozmiar pakietu. Według zespołu Angulara w przypadku dużych aplikacji można zaobserwować spadek nawet o 40%.
W przypadku nowo utworzonej aplikacji rozmiar pakietu jest dość podobny do rozmiaru w Angular 8, ale wraz z rozwojem aplikacji zobaczysz, że rozmiar pakietu będzie mniejszy w porównaniu z poprzednimi wersjami.
Inną funkcją wprowadzoną w Angular 9 jest możliwość ostrzeżenia nas, jeśli którykolwiek z plików CSS w stylu komponentów jest większy niż określony próg.
Pomoże nam to wyłapać złe importy stylów lub duże pliki stylów komponentów.
Dodawanie formularza do oceny programów telewizyjnych
Następnie dodamy formularz do oceniania programów telewizyjnych. W tym celu najpierw zainstalujemy bootstrap
i ng-bootstrap
:
npm install bootstrap @ng-bootstrap/ng-bootstrap
Kolejnym ulepszeniem Angulara 9 jest i18n (internacjonalizacja). Wcześniej programiści musieli uruchomić pełną kompilację dla każdej lokalizacji w aplikacji. Zamiast tego Angular 9 pozwala nam zbudować aplikację raz i generować wszystkie pliki i18n w procesie post-kompilacji, znacznie skracając czas kompilacji. Ponieważ ng-bootstrap
jest zależny od i18n, dodamy nowy pakiet do naszego projektu:
ng add @angular/localize
Następnie dodamy motyw Bootstrap do styles.scss
naszej aplikacji:
@import "~bootstrap/scss/bootstrap";
W naszym AppModule
na app.module.ts
uwzględnimy NgbModule
i ReactiveFormsModule
:
// ... import { ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ // ... ReactiveFormsModule, NgbModule ], })
Następnie zaktualizujemy app.component.html
podstawową siatką dla naszego formularza:
<div class="container"> <div class="row"> <div class="col-6"> </div> </div> </div>
I wygeneruj komponent formularza:
ng gc TvRatingForm
Zaktualizujmy tv-rating-form.component.html
i dodajmy formularz do oceniania programów telewizyjnych.
<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>
A tv-rating-form.component.ts
będzie wyglądać tak:
// ... 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(); } }
Na koniec dodajmy formularz do app.component.html
:
<!-- ... --> <div class="col-6"> <app-tv-rating-form></app-tv-rating-form> </div>
W tym momencie mamy podstawową funkcjonalność interfejsu użytkownika. Teraz, jeśli ponownie uruchomimy ng serve
, możemy zobaczyć to w akcji.
Zanim przejdziemy dalej, rzućmy okiem na kilka interesujących nowych funkcji Angulara 9, które zostały dodane w celu ułatwienia debugowania. Ponieważ jest to bardzo częste zadanie w naszej codziennej pracy, warto wiedzieć, co się zmieniło, aby trochę ułatwić nam życie.
Debugowanie za pomocą Angular 9 Ivy
Kolejnym dużym ulepszeniem wprowadzonym w Angular 9 i Angular Ivy jest debugowanie. Kompilator może teraz wykrywać więcej błędów i rzucać je w bardziej „czytelny” sposób.
Zobaczmy to w akcji. Najpierw aktywujemy sprawdzanie szablonów w tsconfig.json
:
{ // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }
Teraz, jeśli zaktualizujemy tablicę tvShows
i zmienimy name
na title
:
tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];
…otrzymamy błąd kompilatora.
To sprawdzanie typu pozwoli nam zapobiec literówkom i nieprawidłowemu użyciu typów TypeScript.
Walidacja kątowego bluszczu dla @Input()
Inną dobrą walidacją, którą otrzymujemy, jest @Input()
. Na przykład możemy dodać to do tv-rating-form.component.ts
:
@Input() title: string;
…i zwiąż go w app.component.html
:
<app-tv-rating-form [title]="title"></app-tv-rating-form>
…a następnie zmień app.component.ts
taki sposób:
// ... export class AppComponent { title = null; }
Jeśli dokonamy tych trzech zmian, kompilator otrzyma inny rodzaj błędu.
W przypadku, gdy chcemy to ominąć, możemy użyć $any()
w szablonie, aby rzutować wartość na any
i naprawić błąd:
<app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>
Właściwym sposobem na naprawienie tego byłoby jednak uczynienie title
w formularzu wartością null:
@Input() title: string | null ;
ExpressionChangedAfterItHasBeenCheckedError
po sprawdzeniuBłąd w Angular 9 Ivy
Jednym z najbardziej przerażających błędów w rozwoju Angulara jest ExpressionChangedAfterItHasBeenCheckedError
. Na szczęście Ivy wyświetla błąd w jaśniejszy sposób, co ułatwia znalezienie źródła problemu.
Przedstawmy więc błąd ExpressionChangedAfterItHasBeenCheckedError
. W tym celu najpierw wygenerujemy usługę:
ng gs Title
Następnie dodamy BehaviorSubject
i metody, aby uzyskać dostęp do Observable
i wyemitować nową wartość.
export class TitleService { private bs = new BehaviorSubject < string > (''); constructor() {} get title$() { return this.bs.asObservable(); } update(title: string) { this.bs.next(title); } }
Następnie dodamy to do app.component.html
:

<!-- ... --> <div class="col-6"> <h2> {{title$ | async}} </h2> <app-tv-rating-form [title]="title"></app-tv-rating-form> </div>
A w app.component.ts
TitleService
:
export class AppComponent implements OnInit { // ... title$: Observable < string > ; constructor( private titleSvc: TitleService ) {} ngOnInit() { this.title$ = this.titleSvc.title$; } // ... }
Na koniec w tv-rating-form.component.ts
wprowadzimy TitleService
i zaktualizujemy tytuł AppComponent
, co spowoduje zgłoszenie błędu ExpressionChangedAfterItHasBeenCheckedError
.
// ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }
Teraz możemy zobaczyć szczegółowy błąd w konsoli deweloperskiej przeglądarki, a kliknięcie app.component.html
wskaże nam, gdzie jest błąd.
Możemy naprawić ten błąd, opakowując wywołanie usługi setTimeout
:
setTimeout(() => { this.titleSvc.update('new title!'); });
Aby zrozumieć, dlaczego pojawia się błąd ExpressionChangedAfterItHasBeenCheckedError
i zbadać inne możliwości, warto przeczytać post Maxima Koretskyi na ten temat.
Angular Ivy pozwala nam prezentować błędy w bardziej przejrzysty sposób i pomaga wymusić pisanie TypeScript w naszym kodzie. W następnej sekcji omówimy kilka typowych scenariuszy, w których wykorzystamy Ivy i debugowanie.
Pisanie testu dla naszej aplikacji Angular 9 z wiązkami komponentów
W Angular 9 wprowadzono nowy testowy interfejs API, nazwany uprzężami komponentów . Ideą, która się za tym kryje, jest usunięcie wszystkich obowiązków wymaganych do interakcji z DOM, dzięki czemu praca z nim jest znacznie łatwiejsza i bardziej stabilna.
Component wiązki API jest zawarte w bibliotece @angular/cdk
, więc najpierw zainstalujmy to w naszym projekcie:
npm install @angular/cdk
Teraz możemy napisać test i wykorzystać wiązki komponentów. W tv-rating-form.component.spec.ts
skonfigurujmy test:
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(); })); // ... });
Następnie zaimplementujmy ComponentHarness
dla naszego komponentu. Zamierzamy stworzyć dwie wiązki: jedną dla TvRatingForm
, a drugą dla NgbRating
. ComponentHarness
wymaga static
pola hostSelector
, które powinno przyjąć wartość selektora składnika.
// ... 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'; } // ...
Dla naszej TvRatingFormHarness
utworzymy selektor dla przycisku przesyłania i funkcję wyzwalającą click
. Możesz zobaczyć, o ile łatwiejsze staje się to wdrożenie.
class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }
Następnie dodamy metody ustalania oceny. Tutaj używamy locatorForAll
do wyszukania wszystkich elementów <span>
reprezentujących gwiazdy, na które użytkownik może kliknąć. Funkcja rate
po prostu pobiera wszystkie możliwe gwiazdki ocen i klika tę, która odpowiada przesłanej wartości.
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(); } }
Ostatni brakujący element to połączenie TvRatingFormHarness
z NgbRatingHarness
. Aby to zrobić, po prostu dodajemy lokalizator w klasie TvRatingFormHarness
.
class TvRatingFormHarness extends ComponentHarness { // ... getRating = this.locatorFor(NgbRatingHarness); // ... }
Teraz napiszmy nasz test:
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}'); }); });
Zauważ, że dla naszego select
w formularzu nie zaimplementowaliśmy ustawiania jego wartości za pomocą uprzęży. Dzieje się tak, ponieważ interfejs API nadal nie obsługuje wybierania opcji. Ale to daje nam możliwość porównania tutaj, jak wyglądała interakcja z elementami przed wiązkami komponentów.
Ostatnia rzecz, zanim przeprowadzimy testy. Musimy naprawić app.component.spec.ts
, ponieważ zaktualizowaliśmy title
na 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); }); });
Teraz, kiedy uruchamiamy ng test
, nasz test przechodzi.
Powrót do naszej przykładowej aplikacji Angular 9: Zapisywanie danych w bazie danych
Podsumujmy nasz samouczek Angulara 9, dodając połączenie do Firestore i zapisując oceny w bazie danych.
Aby to zrobić, musimy utworzyć projekt Firebase. Następnie zainstalujemy wymagane zależności.
npm install @angular/fire firebase
W ustawieniach projektu Konsoli Firebase pobierzemy jego konfigurację i dodamy je do environment.ts
i 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}' } };
Następnie zaimportujemy niezbędne moduły do 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, ], // ... })
Następnie w tv-rating-form.component.ts
wprowadzimy usługę AngularFirestore
i zapiszemy nową ocenę podczas przesyłania formularza:
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(); } }
Teraz, gdy przejdziemy do konsoli Firebase, zobaczymy nowo utworzony element.
Na koniec wypiszmy wszystkie oceny w AppComponent
. W tym celu w app.component.ts
otrzymamy dane z kolekcji:
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(); } }
…a w app.component.html
dodamy listę ocen:
<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>
Tak wygląda nasza samouczek Angular 9, gdy wszystko się złoży.
Angular 9 i Angular Ivy: lepszy rozwój, lepsze aplikacje i lepsza kompatybilność
W tym samouczku Angular 9 omówiliśmy tworzenie podstawowego formularza, zapisywanie danych w Firebase i pobieranie z niego elementów.
Po drodze widzieliśmy, jakie ulepszenia i nowe funkcje są zawarte w Angular 9 i Angular Ivy. Aby uzyskać pełną listę, możesz sprawdzić najnowszy post na oficjalnym blogu Angulara.
Jako Partner Google Cloud, certyfikowani przez Google eksperci Toptal są dostępni dla firm na żądanie w ich najważniejszych projektach.