Jak zacząć używać UI Router - przepisanie aplikacji

Od jakiegoś czasu mam przyjemność używać biblioteki o nazwie UI Router. Dotychczas podchodziłem do niej sceptycznie — bo po co niby kolejny router, skoro ngRoute daje sobie radę? Ale skoro zastałem już w projekcie takie, a nie inne technologie, to przecież nie powiem, że mi się nie podobają. No i zaczęła się zabawa.

Na początku było trudno - przykładowa aplikacja UI Router nie była dla mnie w stu procentach zrozumiała, a dokumentacja, pomimo, że dość kompletna, jednak nie okazała się lekkostrawna. Z pomocą współpracowników udało się dojść do jako takiego zrozumienia tematu i teraz chyba mogę spokojnie powiedzieć, że wiem, co takiego oferuje UI Router, co daje przewagę nad ngRoute.

A takie ficzery są dwa. Po pierwsze, stany i ich hierarchia. Stan to coś w rodzaju ścieżki w ngRoute, z własnym URL, z tą różnicą, że stan może mieć wiele widoków z własnymi kontrolerami i templatkami, a nawet stany potomne — z oddzielnymi URLami, templatkami i widokami, ładujące się dynamicznie podczas ciągłego działania stanu (i kontrolera) nadrzędnego. W aplikacji demonstracyjnej użyto książki kontaktów — stan nadrzędny wyświetla listę kontaktów, a stan potomny pokazuje aktualnie wybrany kontakt. Kontakty się zmieniają, ale kontroler i widok listy nigdy się nie muszą przeładowywać.

Drugi ficzer to dyrektywa uiSref. Służy do tego, do czego w ngRoute jest ngHref, ale zamiast URL, wykorzystuje nazwę stanu i ewentualne jego parametry. Dzięki temu linki budują się dynamicznie, niezależnie od tego, jakie adresy URL faktycznie w nich tkwią. Adresy można też bezboleśnie zmienić w dowolnym momencie i zmiany będą natychmiast widoczne w całej aplikacji.

Powstaje zatem pytanie: jak dostosować juz istniejącą aplikację korzystającą z ngRoute? Ja postanowiłem przepisać jedną, na razie niezbyt złożoną aplikację i efekt jest jak najbardziej udany. Oto, co zrobiłem.

1. Instalacja

Oczywiście wystarczy nam komenda bowera, by zassać potrzebny moduł:

bower install --save angular-ui-router

Następnie dołączamy pobrany plik gdzie trzeba, czy to w HTML, czy w tasku Grunta.

2. Moduł

Zamiast korzystać z modułu ngRoute, do zależności naszych modułów dodajemy ui.router. Ten krok oczywiście spowoduje, że cała aplikacja nam klęknie i nie podniesie się dopóki wszystkie elementy pochodzące z modułu ngRoute nie zostaną usunięte. A o tym w dalszych krokach.

3. Zmiana ścieżek na stany

Zaczynamy oczywiście od zdefiniowania stanów. Korzystając z ngRoute, stworzyliśmy na pewno ileś tam ścieżek zdefiniowanych następująco:

$routeProvider
    .when("/jakis/url", {
        // parametry
    }).othherwise({
        redirectTo: "/"
    });

Na potrzeby UI Router, wyrzucamy $routeProvider z zależności app.config(). W zamian wstrzykujemy dwa inne providery: $stateProvider i $urlRouterProvider. Pierwszy posłuży do zdefiniowania stanów, zaś drugi do określenia fallbacku dla nieprawidłowej ścieżki URL.

Definicje stanów przepisujemy następująco:

$stateProvider
    .state("nazwa_stanu", {
        url: "/jakis/url",
        // parametry
    });

Jak widać, zmiana polega na zmianie nazwy funkcji, przeniesieniu adresu URL do parametrów stanu i dopisaniu w jego miejsce nazwy stanu. Uwaga: nazwa nie powinna zawierać kropek, ten znak bowiem jest używany do rozpoznawania stanów potomnych.

Na koniec ścieżka, która nie pasuje do żadnego stanu:

$urlRouterProvider.otherwise("/");

4. Zdarzenia

Moduł ngRoute definiuje kilka zdarzeń. Ja wykorzystywałem $routeChangeSuccess. UI Router definiuje podobne zdarzenia i będzie trzeba je wszystkie przepisać. Polecam zajrzenie do dokumentacji biblioteki, bo parametry w callbacku listenera nie są do końca takie same. Przykładowa zmiana:

$rootScope.$on("$routeChangeSuccess", function(e, route) {
   $rootScope.currentNav = route.$$route.nav;
});

staje się:

$rootScope.$on("$stateChangeSuccess", function(e, toState) {
   $rootScope.currentNav = toState.nav;
});

5. Parametry ścieżki

W kontrolerach nieraz używamy usługi $routeParams do pobrania parametrów ze ścieżki. Zmiana sprowadza się do użycia innej usługi: $stateParams. Jej działanie jest identyczne.

6. Dyrektywa ngView

W celu wyświetlenia templatki kontrolera, ngRoute korzysta z dyrektywy ngView. UI Router ma podobną dyrektywę: uiView. Jej użyteczność jest wprawdzie znacznie większa, ale na razie nie będziemy mieli stanów potomnych, więc nie będę na ten temat się rozpisywał.

7. Dyrektywy ngHref i atrybuty href

Ponieważ UI Router ma własną, bardzo potężną dyrektywę tworzącą adresy URL, zamieniamy wszystkie wystąpienia ngHref i href (przynajmniej dla linków wewnętrznych) na uiSref. Jak nazwa wskazuje, uiSref wskazuje na stan — i to właśnie jej podajemy: nazwę stanu. Do linków dynamicznych możemy również przekazać parametry, np. zamiast poniższego kodu:

<a href="#/users">Users</a>
<a ng-href="#/users/{{ id }}">User {{ id }}</a>

Używamy czegoś takiego:

<a ui-sref="users_list">Users</a>
<a ui-sref="user_details({ id: id})">User {{ id }}</a>

Dyrektywa nie przyjmuje konkretnego URL, a nazwę stanu, jaką wymyśliliśmy sobie w konfiguracji providera. Parametry oczywiście mają nazwy zdefiniowane w URL, więc jeśli w powyższym przykładzie chcemy się odwołać do stanu "user_details" mającego parametr o nazwie ":userId", to musi to zostać odzwierciedlone:

<a ui-sref="user_details({userId: id})">User {{ id }}</a>

Gotowe!

Tak to w telegraficznym skrócie wygląda. Dla ciekawych, jak dokładnie wyglądały zmiany w moim projekcie, oto commity, w których to się zamknęło i na podstawie których powstał ten post:

  • 71a6567 (większość zmian)
  • caf0cdb (całkowite usunięcie ngRoute)

I to by było na tyle. Mam nadzieję, że nie pominąłem jakiegoś kroku, który w innych aplikacjach często występuje, a u mnie z tego, czy innego powodu nie był konieczny. Jeśli tak się stało, będę wdzięczny za sugestie w komentarzach.


Komentarze

Dawid

2015-04-10, 00:26

Bardzo pomocne! Dzięki za ten post :)

jacek

2017-01-23, 14:25

naprawde po ludzku napisane, dzieki , zajelo to moment a nie wiele godzin:)


Napisz komentarz


Szukaj wpisów


Chmura tagów