Rozwijanie aplikacji w świecie rzeczywistym z wieloma wywołaniami do serwera może być pełne błędów. Jeśli jesteś tutaj, oznacza to, że zmagałeś się z opóźnieniami wywołań API. Opóźnienia te mogą powodować negatywny UX. Dziś zajmiemy się zrozumieniem Route Resolvers w Angular 8. Istnieje kilka różnych rzeczy, które możemy zrobić, aby poprawić doświadczenie użytkownika, takie jak wyświetlanie wskaźnika postępu. Aby trzymać się tematu, zobaczmy czym jest Route Resolver i co możemy dzięki niemu osiągnąć.
What Is an Angular Route Resolver?
A Resolver is a class that implements the Resolve interface of Angular Router. W rzeczywistości, Resolver jest usługą, która musi być w module głównym. Zasadniczo Resolver działa jak middleware, który może być wykonywany przed załadowaniem komponentu.
You may also like: Angular Resolvers.
Why Use Route Resolvers in Angular?
Aby wyjaśnić, jak resolver może być używany w Angular, pomyślmy o scenariuszu, w którym używasz *ngIf="some condition"
, a twoja logika opiera się na długości tablicy, która jest manipulowana po zakończeniu wywołania API. Na przykład możesz chcieć wyświetlić w komponencie elementy tej tablicy, które właśnie zostały pobrane w nieuporządkowanej liście.
<ul><li *ngFor="let item of items">{{item.description}}</li></ul>
W tej sytuacji możesz wpaść w problem, ponieważ twoje dane pojawią się po tym, jak komponent będzie gotowy. Elementy w tablicy tak naprawdę jeszcze nie istnieją. Tutaj z pomocą przychodzi Route Resolver. Klasa Route Resolver w Angularze pobierze Twoje dane zanim komponent będzie gotowy. Twoje instrukcje warunkowe będą działać płynnie z Resolverem.
Interfejs Resolve
Zanim zaczniemy implementować resolver trasy, zobaczmy najpierw, jak wygląda interfejs Resolve.
export interface Resolve<T> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T { return 'Data resolved here...' }}
Jeśli chcesz stworzyć Route Resolver, musisz stworzyć nową klasę, która będzie implementować powyższy interfejs. Interfejs ten udostępnia nam funkcję resolve
, która pobiera nam dwa parametry na wypadek gdybyśmy ich potrzebowali. Pierwszy z nich to trasa, która jest typu ActivatedRouteSnapshot
, a drugi to stan, typu RouterStateSnapshot
. Tutaj możesz wykonać wywołanie API, które pobierze dane, których potrzebujesz, zanim twój komponent zostanie załadowany.
Przez parametr route, możesz uzyskać parametry trasy, które mogą być użyte w wywołaniu API. Metoda resolve
może zwrócić Observable
, obietnicę lub po prostu typ niestandardowy.
Uwaga: Ważne jest, aby wspomnieć, że tylko rozwiązane dane zostaną zwrócone za pośrednictwem tej metody.
Jest to błąd, który wiele osób popełnia podczas używania resolwerów po raz pierwszy! Więc musisz je uzupełnić zanim wyślesz je do routera.
Wysyłanie danych do routera przez resolver
Pokażę ci co mam na myśli w powyższej sekcji. Załóżmy, że masz metodę, która zwraca obserwowalne:
items: any = ;getData(): Observable<any> { const observable = Observable.create(observer => { observer.next(this.items) }); return observable;}
Więc, teraz zobaczysz, że subskrypcja, którą mamy, nigdy nie trafia. Jest to spowodowane tym, że nie wysyłasz danych poprawnie. Nie wypełniasz subskrypcji. Aby naprawić ten błąd, musisz zakończyć subskrypcję dodając jeszcze jedną linię kodu.
observer.complete();
W przypadku, gdy zwracasz obietnicę, musisz ją rozwiązać przed wysłaniem danych do routera.
Implementowanie route resolvera
Po tym jak skończyliśmy z podstawowymi wyjaśnieniami i dlaczego i jak używać route resolvera, zacznijmy go implementować. W tym krótkim przykładzie zakładam, że jesteś zaznajomiony z poleceniami CLI Angulara i sposobem tworzenia nowego projektu, więc zademonstruję tylko obowiązkowy kod. Projekt demo można znaleźć w repozytorium GitHub na końcu artykułu.
W tym przykładzie użyję jsonplaceholder jako fałszywego API do pobierania danych użytkownika, aby zademonstrować wywołania API z route resolverami.
Po pierwsze, będziemy potrzebować usługi, która będzie pobierać dane użytkownika dla nas. W tej usłudze mamy funkcję o nazwie getUsers()
, która zwraca obserwowalną.
@Injectable({providedIn: 'root'})export class FakeApiService { constructor(private http: HttpClient) { } private usersEndpoint = "https://jsonplaceholder.typicode.com/users"; getUsers(): Observable<any> { // We do not subscribe here! We let the resolver take care of that... return this.http.get(this.usersEndpoint); }}
Ważne jest, aby nie subskrybować funkcji getUsers. Zajmie się tym za Ciebie route resolver o nazwie UserResolver. Następnym krokiem jest utworzenie nowej usługi o nazwie UserResolver, która będzie implementować funkcję resolve interfejsu Resolve routera.
@Injectable({providedIn: 'root'})export class UserResolverService implements Resolve<any> { constructor(private fakeApi: FakeApiService) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { return this.fakeApi.getUsers().pipe( catchError((error) => { return empty(); }); ); }}
Ta usługa, UserResolver
, będzie automatycznie subskrybować obserwowalną getUsers
i dostarczać routerowi pobrane dane. W przypadku błędu podczas pobierania danych, można wysłać pustą obserwowalną, a router nie przejdzie do trasy.
Nawigacja zostanie zakończona w tym momencie. Ostatnim krokiem jest stworzenie komponentu, który będzie wywoływany, gdy użytkownik przejdzie do trasy /users
. Zazwyczaj, bez Resolvera, trzeba będzie pobrać dane na haku ngOnInit
komponentu i obsłużyć błędy spowodowane przez 'brak danych’. Komponent użytkownika jest prosty. Po prostu pobiera dane użytkownika z ActivatedRoute
i wyświetla je w postaci nieuporządkowanej listy.
Po utworzeniu komponentu użytkownika, musisz zdefiniować trasy i powiedzieć routerowi, aby używał resolvera ( UserResolver
). Można to osiągnąć za pomocą następującego kodu w app-routing.modulte.ts
.
const routes: Routes = ;@NgModule({ imports: , exports: })export class AppRoutingModule { }
Trzeba ustawić właściwość resolve w trasie użytkownika i zadeklarować UserResolver
. Dane zostaną przekazane do obiektu z właściwością o nazwie users. Po tym, jesteś prawie gotowy. Pozostała jeszcze tylko jedna rzecz do zrobienia. Musisz wprowadzić pobrane dane do komponentu users poprzez ActivatedRoute
za pomocą następującego kodu.
constructor(private activatedRoute: ActivatedRoute) { }users: any;ngOnInit() { this.activatedRoute.data.subscribe((data: { users: any }) => { this.users = data.users; });}
Następnie możesz po prostu wyświetlić je w HTML-u bez żadnych deklaracji *ngIf
( *ngIf="users && users.length > 0
), ponieważ dane będą tam przed załadowaniem komponentu.
<h2>Fetched Users:</h2><ul><li *ngFor="let user of users">{{ user.name }}</li></ul>
Podsumowanie
Zamykając ten artykuł o Route Resolverach, chciałbym jeszcze raz zwrócić uwagę na zalety, jakie nam one dają. Po pierwsze unikamy irytujących kontroli, które muszą być wykonywane na poziomie HTML, dzięki czemu nie mamy problemów do momentu otrzymania naszych danych. Po drugie, pozwalają nam skupić się bardziej na doświadczeniach użytkownika, ponieważ możemy zatrzymać nawigację, jeśli wystąpi błąd danych podczas ich pobierania, bez konieczności ładowania komponentu podglądu. Możemy również zrobić więcej przed zakończeniem nawigacji, np. dołączyć więcej logiki lub zmapować dane przed uzyskaniem dostępu do załadowanego komponentu.
Kod demo jest dostępny na GitHub.
Dalsza lektura
- Angular Tutorial: Angular 7 and the RESTEasy Framework.
- Building an Angular 5 Application Step-By-Step.
- Angular 7 + Spring Boot Application: Hello World Example.
.