Angular - async pipe czyli asynchroniczne wykonanie zapytania do serwera

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 5241 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:

{ "book": [ { "id": "1", "title": "Rio Anaconda", "author": "Wojciech Cejrowski" }, { "id": "2", "title": "Pan Tadeusz", "author": "Adam Mickiewicz" } ] }

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:

<div *ngIf="getBooks | async"> <mat-table [dataSource]="dataSource" class="material-table"> <ng-container matColumnDef="position"> <mat-header-cell *matHeaderCellDef> Nr. </mat-header-cell> <mat-cell *matCellDef="let element; let index = index"> {{ index + 1 }} </mat-cell> </ng-container> <ng-container matColumnDef="title"> <mat-header-cell *matHeaderCellDef> Tytuł </mat-header-cell> <mat-cell mat-cell *matCellDef="let element"> {{element.title}} </mat-cell> </ng-container> <ng-container matColumnDef="author"> <mat-header-cell *matHeaderCellDef> Autor </mat-header-cell> <mat-cell mat-cell *matCellDef="let element"> {{element.author}} </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </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:

*matCellDef="let element"

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

*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:

import { Component, OnInit } from '@angular/core'; import { Book, FirstContactService } from '../submodule/first-contact.service'; import { MatTableDataSource } from '@angular/material'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Component({ selector: 'app-books-list', templateUrl: './books-list.component.html', styleUrls: ['./books-list.component.css'] }) export class BooksListComponent implements OnInit { // zmienna zawierająca między innymi dane tabelki dataSource: MatTableDataSource<Book> = new MatTableDataSource<Book>(); // zmienna zawierająca nazwy wyświetlanych kolumn (określa również położenie danej kolumny w tabeli) displayedColumns: string[] = ['position', 'title', 'author']; constructor( public firstContactService: FirstContactService ) { } // ten getter jest wykorzystywany do ładowania asynchronicznym pipe-m danych do tabelki get getBooks(): Observable<Book[]> { if (this.dataSource.data.length === 0) { return this.firstContactService.getBooks() .pipe( tap((books: Book[]) => { this.dataSource.data = books; console.log('First'); }), map((books: Book[]) => this.dataSource.data) ); } return of(this.dataSource.data).pipe(tap(() => console.log('Second'))); } ngOnInit() { } }

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ę.