Angular - async pipe czyli asynchroniczne wykonanie zapytania do serwera

Stronę tą wyświetlono już: 60 razy

Angular udostępnia pipe-a o nazwie async. Umożliwia on asynchroniczne wykonanie zapytania do serwera, dzięki czemu dany element strony będzie w stanie wyświetlić się prawidłowo. Prosty przykład wykorzystania tego pipa będzie wymagał użycia omawianego wcześniej na stronie Programowanie → Angular - podstawy → Angular - tworzenie lokalnego serwera na potrzeby serwisów serwera lokalnego. Jego uruchomienie będzie wymagało obecności np. takiego przykładowego zbioru danych:

Listing 1
  1. {
  2. "book": [
  3. {
  4. "id": "1",
  5. "title": "Rio Anaconda",
  6. "author": "Wojciech Cejrowski"
  7. },
  8. {
  9. "id": "2",
  10. "title": "Pan Tadeusz",
  11. "author": "Adam Mickiewicz"
  12. }
  13. ]
  14. }

Zapisanego w pliku json_data.json. Teraz start serwera za pomocą polecenia:

json-server json_data.json

Dla sprawdzenia w przeglądarce można wpisać: localhost:3000 by sprawdzić, czy wszystko działa prawidłowo. Jeżeli tak to czas stworzyć sobie komponent, który w jakiś w miarę przyzwoity sposób wyświetli te dane. Tak więc tworzę sobie komponent o nazwie booksList:

ng g c bookList

Do wczytywania danych z serwera wykorzystam serwis opisywany już wcześniej na stronie Programowanie → Angular - podstawy → Angular - komunikacja z serwerem za pomocą HttpClient. Natomiast w HTML-u komponentu wykorzystam materialową tabelkę. Żeby jednak było to możliwe konieczne jest zainstalowanie biblioteki material w następujący sposób:

npm install @angular/material

a następnie dodać do projektu:

ng add @angular/material --save

Teraz w module komponentu np. app.module.ts konieczne będzie zaimportowanie modułu MatTableModule.

Po wykonaniu wszystkich tych niezmiernie nużących czynności można zacząć tworzyć swoją pierwszą tabelkę materialową w HTML-u komponentu:

Listing 2
  1. <div *ngIf="getBooks | async">
  2. <mat-table [dataSource]="dataSource" class="material-table">
  3. <ng-container matColumnDef="position">
  4. <mat-header-cell *matHeaderCellDef> Nr. </mat-header-cell>
  5. <mat-cell *matCellDef="let element; let index = index"> {{ index + 1 }} </mat-cell>
  6. </ng-container>
  7. <ng-container matColumnDef="title">
  8. <mat-header-cell *matHeaderCellDef> Tytuł </mat-header-cell>
  9. <mat-cell mat-cell *matCellDef="let element"> {{element.title}} </mat-cell>
  10. </ng-container>
  11. <ng-container matColumnDef="author">
  12. <mat-header-cell *matHeaderCellDef> Autor </mat-header-cell>
  13. <mat-cell mat-cell *matCellDef="let element"> {{element.author}} </mat-cell>
  14. </ng-container>
  15. <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  16. <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
  17. </mat-table>
  18. </div>

W pierwszej linijce wczytywane są asynchronicznie dane. Zmienna dataSource to specjalna zmienna przechowująca między innymi dane do wyświetlania w tabelce. Natomiast displayedColumns to zmienna, która zawiera nazwy wyświetlanych kolumn w postaci tablicy string-ów oznaczających nazwy plików. Znacznik ng-container opisuje zawartość kolumny identyfikowanej za pomocą atrybutu matColumnDef będącego nazwą kolumny. Znacznik th zawierający dyrektywę mat-header-cell określa nagłówek kolumny, natomiast dane są wyświetlane w znaczniku td. Odwołanie się do danych jest możliwe dzięki dyrektywie *matRowDef, w której można utworzyć odwołanie do rekordu danych:

Listing 3
  1. *matCellDef="let element"

Możliwe jest również uzyskanie indeksu danego rekordu:

Listing 4
  1. *matCellDef="let element; let index = index"

Dane dotyczące kolumn nagłówka i rekordów danych zostały zapisane na końcu tabelki. Odpowiednie dyrektywy mat-header-row i mat-row wiążą dany znacznik th z nagłówkiem lub danymi.

Przejdźmy jednak do kodu samego komponentu, albowiem treścią tego tematu nie jest tworzenie tabelek, a użycie samego asynchronicznego ładowania danych. Oto kod komponentu:

Listing 5
  1. import { Component, OnInit } from '@angular/core';
  2. import { Book, FirstContactService } from '../submodule/first-contact.service';
  3. import { MatTableDataSource } from '@angular/material';
  4. import { Observable } from 'rxjs';
  5. import { tap } from 'rxjs/operators';
  6. @Component({
  7. selector: 'app-books-list',
  8. templateUrl: './books-list.component.html',
  9. styleUrls: ['./books-list.component.css']
  10. })
  11. export class BooksListComponent implements OnInit {
  12. // zmienna zawierająca między innymi dane tabelki
  13. dataSource: MatTableDataSource<Book> = new MatTableDataSource<Book>();
  14. // zmienna zawierająca nazwy wyświetlanych kolumn (określa również położenie danej kolumny w tabeli)
  15. displayedColumns: string[] = ['position', 'title', 'author'];
  16. constructor(
  17. public firstContactService: FirstContactService
  18. ) { }
  19. // ten getter jest wykorzystywany do ładowania asynchronicznym pipe-m danych do tabelki
  20. get getBooks(): Observable<Book[]> {
  21. if (this.dataSource.data.length === 0) {
  22. return this.firstContactService.getBooks()
  23. .pipe(
  24. tap((books: Book[]) => {
  25. this.dataSource.data = books;
  26. console.log('First');
  27. }),
  28. map((books: Book[]) => this.dataSource.data)
  29. );
  30. }
  31. return of(this.dataSource.data).pipe(tap(() => console.log('Second')));
  32. }
  33. ngOnInit() {
  34. }
  35. }

Co trzeba zrobić, aby dane przeładować asynchronicznie? W tym przypadku trzeba wyczyścić pole data pola dataSource komponentu. Nie jest to najlepsze rozwiązanie, ponieważ jeżeli w bazie danych nie będzie wpisu to żądanie cały czas będzie ponawiane. Jest jednak to dobre rozwiązanie, gdy wiadomym jest, że dane takie muszą istnieć w bazie danych. Dla przykładu, użytkownik się zalogował pobierając dane dostępowe do różnych elementów systemu jakim jest tworzony przez ciebie program. W takim przypadku konieczny będzie dostęp do tych danych aby poprawnie wyświetlać widok. Takie więc asynchronicznie ładowane pozwoli na odzyskiwanie danych, gdy np. użytkownik odświeży stronę.

Komentarze