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.

Zrzut ekranu IDE przedstawiający folder ng9-app, zawierający node_modules, .editorconfig, .gitignore, angular.json, package-lock.json, package.json, README.md, tsconfig.json i tslint.json.

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.

Zrzut ekranu rusztowania Angulara 9 z informacją, że „aplikacja do oceny programów telewizyjnych jest uruchomiona!” Znajdują się tam również linki do zasobów i dalszych kroków.

Jeśli uruchomimy ng build --prod , możemy zobaczyć listę wygenerowanych plików.

Zrzut ekranu z wyjściem Angulara 9 "ng build --prod". Rozpoczyna się od „Generowanie pakietów ES5 do różnicowego ładowania...”. Po zakończeniu wyświetla listę kilku fragmentów plików JavaScript — runtime, polyfills i main, każda z wersjami -es2015 i -es5 — oraz jednym plikiem CSS. Ostatni wiersz zawiera znacznik czasu, skrót i czas działania wynoszący 23 881 milisekund.

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.

Zrzut ekranu sekcji „budżety” pliku konfiguracyjnego Angular 9 JSON z dwoma obiektami w tablicy. Pierwszy obiekt ma „type” ustawione na „initial”, „maximumWarning” ustawione na „2mb” i „maximumError” ustawione na „5mb”. Drugi obiekt ma „type” ustawione na „anyComponentStyle”, „maximumWarning” ustawione na „6kb” i „maximumError” ustawione na „10kb”.

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.

Zrzut ekranu aplikacji samouczka Angular 9 przedstawiający formularz zatytułowany „TV SHOW” z menu rozwijanym zawierającym kilka tytułów programów, licznik gwiazdek i przycisk OK. W animacji użytkownik wybiera pokaz, wybiera ocenę, a następnie klika przycisk OK.

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.

Zrzut ekranu danych wyjściowych kompilatora Angular 9/Angular Ivy, z nazwą pliku i pozycją, mówiący „błąd TS2339: Właściwość 'name' nie istnieje w typie '{ title: string; }'”. Pokazuje również dany wiersz kodu i podkreśla odwołanie, w tym przypadku w pliku tv-rating-form.component.html, w którym wymieniono tvShow.name. Następnie odwołanie do tego pliku HTML jest śledzone do odpowiedniego pliku TypeScript i podobnie podświetlane.

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.

Zrzut ekranu danych wyjściowych kompilatora Angular 9/Angular Ivy, w formacie podobnym do poprzedniego, z podświetleniem app.component.html za pomocą „błąd TS 2322: Typu „null” nie można przypisać do typu „string”.

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.

Zrzut ekranu konsoli deweloperskiej przeglądarki, pokazujący raportowanie przez Angular Ivy błędu ExpressionChangedAfterItHasBeenCheckedError. Ślad stosu w czerwonym tekście zawiera błąd wraz z poprzednimi i bieżącymi wartościami oraz podpowiedź. W środku śladu stosu znajduje się jedyna linia, która nie odnosi się do core.js. Użytkownik go klika i zostaje przeniesiony do wiersza app.component.html, który powoduje 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.

Zrzut ekranu przedstawiający przebieg testów Karmy w naszej aplikacji Angular 9. Pokazuje „Uruchomiono 2 z 6 specyfikacji” z komunikatem „Znaleziono niekompletne: fit() lub fdescribe(), 2 specyfikacje, 0 błędów, losowo z seedem 69573”. Dwa testy TvRatingFormComponent są podświetlone. Wszystkie trzy testy AppComponent i jeden test TitleService są szare.

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

Zrzut ekranu aplikacji samouczka Angular 9 przedstawiający formularz zatytułowany „TELEWIZJA” pod większym tytułem strony „nowy tytuł!” Ponownie ma menu rozwijane z listą tytułów programów, licznikiem gwiazdek i przyciskiem OK, a użytkownik ponownie wybiera program, wybiera ocenę, a następnie klika przycisk OK.

Teraz, gdy przejdziemy do konsoli Firebase, zobaczymy nowo utworzony element.

Zrzut ekranu konsoli Firebase. W lewej kolumnie znajduje się joaq-lab z kilkoma kolekcjami: uczestnicy, wyścigi, oceny, testy i użytkownicy. Pozycja ocen jest zaznaczona i znajduje się w środkowej kolumnie z wybranym identyfikatorem — jest to jedyny dokument. Prawa kolumna zawiera dwa pola: „ocena” jest ustawiona na 4, a „tvShow” jest ustawione na „Mad men”.

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.

Zrzut ekranu aplikacji samouczka Angular 9 przedstawiający formularz zatytułowany „TELEWIZJA” pod większym tytułem strony „nowy tytuł!” Ponownie, zawiera listę rozwijaną z kilkoma tytułami programów, licznikiem gwiazdek i przyciskiem OK. Tym razem w prawej kolumnie znajduje się już „Szaleńcy (4)”, a użytkownik ocenia „Zagubieni” na trzy gwiazdki, a następnie „Szaleńcy” ponownie na cztery gwiazdki. Prawa kolumna pozostaje w porządku alfabetycznym po obu nowych ocenach.

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.


Status Partnera Google Cloud.

Jako Partner Google Cloud, certyfikowani przez Google eksperci Toptal są dostępni dla firm na żądanie w ich najważniejszych projektach.