WordHunt – plansza do gry przy użyciu Flex Layout

Dzisiaj pokażę jak wygląda plansza do gry. Przyznaję, że spodziewałem się że ten element zostanie skończony już dawno, ale wyszło jak wyszło. Zadanie to sprawiło mi więcej problemów niż się spodziewałem, ale jestem w miarę zadowolony z tego co otrzymałem.

Plansza i jej elementy

Jeżeli chodzi o styl, pozostawia ona jeszcze sporo do życzenia, ale pola na planszy zachowują się tak jak chciałem. Dostosowują się do wielkości ekranu, w pełni wypełniając całą dostępną przestrzeń.

Board_mobile

Plansza do gry 5×5 na małym urządzeniu

W przypadku, gdy plansza posiada więcej słów i pola nie będą się mieścić w całości, wyrazy zostaną ucięte. W przyszłości czcionka będzie zmieniać rozmiar w zależności od wielkości ekranu i długości słowa, dzięki czemu unikniemy takich sytuacji. Na troszkę większych urządzeniach np. tabletach wyświetlanie działa tak jak należy, nawet na największej możliwej planszy 8×8.

board2_tablet_8x8

Plansza do gry 8×8 na tablecie

Teraz, gdy plansza jest już w takim stanie, zaczynam dodawać pozostałe fragmenty. Na tą chwilę jest jeszcze menu boczne, gdzie będą dodatkowe informacje oraz opcje. Żeby nie marnować miejsca, jest ono ukryte. Gdy je otworzymy, menu pojawia się nad planszą.

board3_sidenav

Plansza do gry – menu boczne

Obecnie obsługuję jedynie przycisk pominięcia rundy, dzięki czemu kolejna drużyna może wykonać ruch. Jutro dodam obsługę „odsłonięcia” pola, czyli jedną z najważniejszych rzeczy do zrobienia.

Jak to wygląda pod maską?

Skoro już zademonstrowałem jak plansza wygląda, pokażę teraz jak to działa. Wygląd aplikacja zawdzięcza komponentom Angular Material Design, natomiast sam layout planszy działa na bazie Flex Layout. Obie biblioteki są w wersji beta, ale większych bugów nie znalazłem. Muszę jednak powiedzieć, że próg wejścia do tych technologi jest troszkę większy niż to, co spotyka nas przy pierwszym kontakcie z popularnym konkurentem, czyli poczciwym Bootstrapem. Nie ma jednak tragedii i coś nawet da się z tym zrobić.

Ekran gry podzielony jest na kilka elementów. Kod głównego komponentu, na którym umieszczone są wszystkie elementy wygląda następująco:

Kontener, w którym wyświetlane jest menu boczne, toolbar gdzie jest tytuł aplikacji, a będą także ikony drużyn oraz plansza do gry. Tutaj nie musiałem dużo majstrować przy stylach. Jedynie toolbar wymagał drobnych modyfikacji.

Ręcznie modyfikuję rozmiar górnego paska aplikacji, ponieważ zmieniał się on przy zmianie wielkości okna. Powodowało to, że plansza do gry odrobinę się przesuwała i wychodziła poza ekran.
W bazowej klasie ze stylami zdefiniowałem mixin, który wykorzystujemy w innych komponentach, tak jak powyżej. Dzięki temu dokładnie i w łatwy sposób sterujemy stylami w zależności od wielkości ekranu. Był on szczególnie potrzebny w przypadku planszy, ponieważ przesuwała się ona w dół o wysokość paska górnego.

Sam kod planszy jest dosyć krótki, ale powiedziałbym że intensywny. Mamy tutaj dwa layouty fxLayout: column i row. Kolumnowy sprawia, że każdy rząd zabiera odpowiednią ilość miejsca w pionie. Dzięki temu plansza wypełnia całą przestrzeń. Każdy rząd składa się z komponentu game-field, który wyświetla słowa i odpowiadać będzie za kliknięcie na element.

Wartość fxFlex, która definiuje nam jak dużo przestrzeni ma wykorzystać każdy blok, wyliczana jest w kodzie po załadowaniu danych gry. Na podstawie ilości rzędów i kolumn dodajemy odpowiednią wartość do każdego elementu. Wartość bazowa baseFlexValue, od której liczymy wielkość elementów sprawia, że otrzymamy małe odstępy pomiędzy polami, które będą zmieniały wielkość w zależności od wielkości ekranu. Gdyby było tam 100, nie mielibyśmy przerw. Można to oczywiście zaprogramować inaczej, np. poprzez margin pól gry, ale na razie zostawię to tak jak jest.

Jeżeli chodzi o stylowanie tego komponentu, trzeba było dodać obliczenie wielkości planszy z uwzględnieniem paska górnego. Bez tego plansza była przesunięta o jego wysokość.

Tutaj uwaga! calc nie przyjmuje zmiennych z SASSa, chyba że opakujemy je w nawiasy klamrowe poprzedzone hasztagiem. Trochę mi zajęło zanim na to wpadłem… Szczerze, to boję się tego ruszać, bo już raz coś zmieniłem, co sprawiło że zepsułem cały ekran gry i naprawiałem go dobrych kilka minut. Szybko nauczyłem się robić częste commity, o których zdarza mi się zapomnieć. Kodu nie jest wiele, ale jako że nie jestem mistrzem CSS, trochę mi zajęło znalezienie takiej konfiguracji.

Podsumowanie

Plansza w wersji beta gotowa, teraz dodanie obsługi wykrywania pól oraz mapy dla kapitanów. Gdy to będzie gotowe, MVP projektu będzie skończone. Mam nadzieję że przed końcem konkursu!

SignalR w ASP.NET Core i Angular – implementacja po stronie klienta

Dzisiaj pora na kolejny post dotyczący wykorzystania SignalR w ASP.NET Core. Ostatnim razem pisałem o implementacji po stronie serwera, tym razem zajmiemy się klientem aplikacji z wykorzystaniem frameworka Angular. Poniżej zamieszam odnośniki do powiązanych postów.

Część pierwsza: SignalR w ASP.NET Core – instalacja i uruchomienie

Część druga: SignalR w ASP.NET Core i Angular – przykładowa implementacja

Połączenie z serwerem

Gdy pisałem o konfiguracji, podałem na przykładzie w jaki sposób możemy połączyć się z serwerem. W tym celu wykorzystałem tzw. resolver w Angularze. Działa on na takiej zasadzie, że zapytanie wykonane w nim wykonuje się przed załadowaniem naszego komponentu, czyli np. planszy do gry. Poniżej przedstawię trochę inny przykład, gdzie ręcznie wywołujemy funkcję odpowiedzialną za połączenie. Na początek dodajemy fabrykę, która potrafi utworzyć nam nowe połączenie.

Fabrykę wykorzystujemy tylko do nawiązania nowego połączenia. Jeżeli będę chciał wykorzystywać inne konfigurację niż standardowa, nie chcę dodawać tego kodu do klasy odpowiedzialnej za samo przetwarzanie komunikatów.

Dwustronna komunikacja

Następnie dodaję serwis, który będzie przetrzymywał otwarte połączenie z serwerem oraz rozsyłał komunikaty do komponentów w Angularze. Cały klient będzie miał tylko jedno otwarte połączenie, ponieważ otwarcie takiego połączenia jest czasochłonne. Dodatkowo nie ma sensu obciążać serwera wieloma połączeniami w ramach jednej aplikacji klienckiej. Poniżej przedstawiam taką klasę, która obsługuje dwa typy zdarzeń:

  • Połączenie klienta do serwera, inicjowane prze klienta
  • Zmiana drużyny która wykonuje ruch, inicjowane prze serwer

Posiada ona kilka funkcji, omówię krótko każdą z nich.

  • connect – odpowiada za nawiązanie połączenia w ramach klienta. Połączenie jest przechowywanie w hubie i dzielone pomiędzy wszystkie komponenty.
  • disconnect– analogicznie do funkcji powyżej, zamyka ono połączenie z serwerem.
  • subscribeToGame – zdarzenie inicjowane po stronie klienta. Za jej pomocą dołączamy do kanału obecnej gry, dzięki temu otrzymamy komunikaty powiązane z grą.
  • subscribed – zdarzenie inicjowane po tym, jak nowy klient zapisze się do naszej gry.
  • teamChanged – zdarzenie inicjowane po tym, gdy jedna z drużyn wykona ruch, który spowoduje zmianę kolejki na inną drużynę.

Wykorzystanie serwisu w aplikacji

Gdy mamy już gotowy serwis, możemy wykorzystać go w naszej aplikacji. Na początek w głównym komponencie gry wywołujemy połączenie z serwerem.

Dzięki jej wywołaniu kolejno:

  • nawiązujemy połączenie z serwerem
  • zapisujemy się do gry
  • ładujemy dane gry
  • inicjalizujemy komponenty

Przykładem komponentu wykorzystującego ten serwis może być menu boczne aplikacji. Z jego poziomu drużyna może pominąć kolejkę, co będzie skutkowało zmianą obecnie aktywnej drużyny. Dodatkowo, gdy nastąpi zmiana drużyny, będziemy mogli nasłuchiwać na takie zdarzenie i wyświetlić drużynę wykonującą obecnie ruch.

Mamy tutaj trzy elementy na które należy zwrócić uwagę:

  • initialize – tutaj komponent zgłasza chęć otrzymywania komunikatów związanych ze zmianą aktywnej drużyny. teamChanged przyjmuje funkcję, która wykona się za każdym razem, gdy takie zdarzenie nastąpi.
  • onTeamChanged – wspomniana wyżej funkcja, która wykona się po wystąpieniu zdarzenia zmiany drużyny. Tutaj wyświetlony zostanie tylko komunikat w konsoli.
  • skipRound – pominięcie tury. Nie odwołujemy się tutaj bezpośrednio do SignalR’a, ale wykonujemy zwykłe zapytanie do serwera. On później roześle informacje o zmianie aktywnej drużyny

Podsumowanie

Wydaje mi się, że w tych trzech wpisach pokazałem, jak wykorzystać w naszej aplikacji bibliotekę SignalR i tworzyć aplikacje działające w czasie rzeczywistym. Zachęcam do wypróbowania jej we własnym zakresie. Jeżeli jednak miałbym być szczery, to na chwilę obecną proponuję wykorzystanie tej biblioteki w starszej wersji frameworka, chyba że tak jak ja chcecie po prostu pobawić się z nowszą wersją frameworka .NET. Co prawda, pojawił się już ASP.NET Core Preview 1, ale nie jest to wersja stabilna. Taka pojawi się dopiero pod koniec obecnego roku.

Co nowego w projekcie – aktualizacja

Od ostatniej aktualizacji minęły prawie dwa tygodnie, więc pora na krótkie podsumowanie tego, co udało mi się zrobić przez ten czas. Niestety dla projektu, przez dwa ostatnie weekendy byłem poza domem, więc nie wszystko co sobie zaplanowałem zostało zrealizowane. Jakieś postępy jednak są, dlatego zapraszam do lektury!

post-background

Exception handling w WebAPI

Przechwytywanie błędów to było coś, co mi się nie podobało w moim rozwiązaniu. Wiadomo, na początku nie było to istotne, ale w miarę jak ilość elementów aplikacji rosła, trzeba było pomyśleć nad czymś sensownym. Domyślnie, gdy w aplikacji pojawiał się błąd, wyświetlana była strona z jego treścią, standardowe zachowanie. Jako, że w API nie jest to pożądane, dodałem do aplikacji middleware który przechwytuje wszystkie wyjątki. W ten sposób nie trzeba się bawić w przekazywanie statusów z metod domeny do kontrolera – gdy trzeba rzucamy wyjątek odpowiedniego typu, a resztą zajmie się właśnie ten fragment kodu.

Teraz tylko dodajemy tą klasę do pipeline w ASP poprzez metodę UseMiddleware<ExceptionHandlingMiddleware>() i gotowe. Wszystkie wyjątki zostaną przechwycone i zwrócone do klienta. W zależności od rodzaju wyjątku, zwrócony zostanie inny status błędu.

Obsługa SignalR

Mój poprzedni post jest w całości poświęcony tej tematyce, a w szczególności wspominam tam o wszystkich problemach jakie mnie spotkały podczas dodawania tej biblioteki. Warto jednak o tym tutaj wspomnieć. Dzięki temu moja aplikacja będzie mogła działać w czasie rzeczywistym, rozsyłając komunikaty pomiędzy wszystkich klientów. O tym, jak dokładnie zostało to zaimplementowane po stronie klienta i serwera opowiem w kolejnych postach.

Praca nad widokiem aplikacji

W miarę jak dodaje kolejne elementy, powoli buduje interfejs użytkownika. Na razie ten element wyraźnie kuleje, ale nie jest to najistotniejsze w tym momencie. Wygląd na tą chwilę nie jest istotny, ważne żeby dokładać kolejne elementy funkcjonalne. Obecnie pracuje nad planszą do gry. Jest to dosyć problematyczne zadanie, ponieważ staram się żeby była możliwość gry na tabletach i smartfonach. Ciężko pomieścić tak wiele pól na małych ekranach tak, żeby były one czytelne. Do tego CSSy to nie jest moja mocna strona… Na razie poprawki tego fragmentu zostawiam na później. Nie będę się także chwalił obecnym wyglądem aplikacji, myślę że w przyszłym tygodniu coś już pokażę.

Dalsza praca z Dapperem

Tej technologii też poświęciłem już cały wpis, ale chciałem pokazać kolejną metodę którą udostępnia. Jest to QueryMultipleAsync. Tak bardzo mi się to spodobało, że postanowiłem o tym tutaj wspomnieć. Po wejściu na planszę klient chce pobrać informacje o obecnej grze: pola, status, drużyny etc. W tym celu odpytuje API. Zamiast wielu zapytań do bazy danych, całą tą operację da się zrobić w jednym zapytaniu. Można by to wydzielić na wiele mniejszych zapytań, ale stwierdziłem że to dobry moment na wykorzystanie tej metody. Poniżej implementacja.

AccessQueries w powyższym przykładzie to po prostu treści zapytań SQL. Z wielu małych zapytań generowane jest jedno wielkie. Wyniki są mapowane do poszczególnych elementów obiektu Game. W SQL Server Profilerze wyraźnie widać, wszystko wykonane w jednej operacji, bardzo miła sprawa.

Sql Server Profiler - wynik zapytania QueryMultipleAsync

Zrzut ekranu z SQL Server Profiler

Podsumowanie

Podkręciłem ostatnio tempo pracy nad projektem, dodatkowo zapowiada mi się pierwszy wolny weekend w tym miesiącu, dlatego mam spore oczekiwania względem tego, co uda mi się zrobić w najbliższych dniach! Stay tuned!

SignalR w ASP.NET Core – instalacja i uruchomienie

Dzisiaj chciałem podzielić się moimi doświadczeniami z użyciem biblioteki SignalR w projekcie .NET Core. Co prawda, nie mam jeszcze za dużo przykładów użycia w projekcie, ponieważ dopiero udało mi się zmusić ją do działania. Wydaje mi się jednak, że to co tutaj opiszę przyda się komuś, jeżeli napotka na podobne problemy co ja.

signalr sign and pretty tree

Co to jest SignalR?

SignalR to biblioteka pozwalająca na tworzenie aplikacji aktualizujących dane w czasie rzeczywistym. Co to znaczy? W standardowym scenariuszu, po uruchomieniu strony czy aplikacji pobierane są dane. Jeżeli chcemy je odświeżyć, musimy przeładować stronę lub wywołać jakąś akcję która odświeży dane (np. ponowne przefiltrowanie listy). W taki czy inny sposób wysyłamy żądanie na serwer i otrzymujemy od niego odpowiedź. Tutaj mamy do czynienia z czymś innym. To serwer informuje klienta o tym, że dane zostały zmienione. Dzięki temu natychmiast możemy mieć dostęp do interesujących nas informacji. W tym celu stosowane jest na przykład API WebSockets, czyli komunikacja dwukierunkowa klient-serwer. Do naszej dyspozycji jest także HTML5 Server-Sent Events. Tutaj komunikacja jest tylko jednokierunkowa, z serwera do klienta. Bywa jednak, że przeglądarki nie wspierają obu tych rozwiązań. Wtedy z pomocą przychodzi SignalR. Korzystając z niego nie interesuje nas w jaki sposób przebiegać będzie komunikacja, framework zajmie się tym za nas. Jeżeli tak jak wspomniałem przed chwilą, przeglądarka nie dostarcza nam tych rozwiązań, wykonane zostaną po prostu zwykłe zapytania. Ale to już jest dla programisty mało ważne, stanie się to automatycznie. To tyle w skrócie, nie są to technologie nowe, dlatego po więcej szczegółów zachęcam do dalszej lektury we własnym zakresie.

SignalR w .NET Core

Na początek pokaże, co zrobić żeby wszystko ładnie działało po stronie API naszej aplikacji. Największym problemem na jaki napotkałem był brak implementacji biblioteki pod .NET Core. Jest ona jeszcze w przygotowaniu, i żeby mieć tą wersję, trzeba ją pobrać ze specjalnego źródła, gdzie wrzucane są wersje dopiero rozwijane. Stwierdziłem, że nie będę się katował wersjami testowymi tej technologi. Sami autorzy tego nie zalecają. Do tego musiałbym podnieść wersję samego .NET Core, a tego nie chciałem robić. Wystarczą mi problemy, które mam z wersją stabilną… Jako, że byłem zmotywowany do użycia tej technologi i nie chciałem po prostu odpytywać serwera co chwilę o to, czy pozostali gracze nie wykonali ruchu, znalazłem inne rozwiązanie. Postanowiłem, że użyję frameworka .NET w wersji 4.6.2. Nie wiązało się to z przepisywaniem aplikacji od nowa. Moja aplikacja dalej pisana jest w .NET Core, ale mam możliwość korzystania ze sprawdzonych bibliotek poprzedniej wersji .NET. Wystarczy zmienić trochę plik .csproj naszej aplikacji.

Zamiast netcoreapp1.1, jako TargetFramework podajemy net462. Reszta aplikacji pozostaje w zasadzie bez zmian. Dzięki temu, dalej korzystamy z nowego .NET Core, ale możemy dodać zależności do sprawdzonego Microsoft.AspNet.SignalR.Core. W ten sposób omijamy pierwszą przeszkodę, czyli wersję biblioteki. To jednak nie wszystko.
Żeby móc podpiąć SignalR do pipeline’u naszej aplikacji, musimy dodać następujące extension methods.

Kluczowe tutaj było dodanie do pipeline UseAesDataProtectorProvider. Bez tego, SignalR działał lokalnie, ale już w środowisku produkcyjnym nie chciał nawiązać połączenia. Objawiało się to błędem Error during negotiation request.... Po wykonaniu tych czynności możemy dodać SignalR do naszej aplikacji!

A poniżej pierwszy Hub, którym testowałem, czy komunikacja działa. Nie robi nic innego, niż tylko przekazanie wszystkim połączonym klientom otrzymanej wiadomości.

SignalR w Angular

Kolejne problemy, na szczęście mniejsze, pojawiły się po stronie frontendu aplikacji. Jak zwykle jakąś rolę odegrał tutaj Webpack, ale do tego już się przyzwyczaiłem. Do komunikacji z serwerem użyłem biblioteki ng2-signalr, która dostarcza implementacje pod Angulara. Problem polegał na tym, że nie widziała ona dodanych do projektu bibliotek jQuery oraz signalr, które są konieczne do jej działania. Do obejścia tego problemu w pliku webpack.config.js należało dodać nowy plugin, który automatycznie ładuje moduły do wybranego identyfikatora. Dodatkowo w app.module.ts należało dodać importy tych bibliotek. Dzięki temu problem zniknął.

Teraz można przejść do właściwej implementacji naszego rozwiązania. Dodajemy resolver połączenia do SignalR, który już na starcie komponentu zwraca nam obiekt połączenia. W obecnej implementacji, zawsze łączy się do tego samego Huba.

Połączenie tworzone jest na podstawie domyślnej konfiguracji, która jest podawana podczas deklaracji modułu SingalR. Oto implementacja funkcji, podającej domyślną konfigurację.

Korzystam z tego samego mechanizmu, który wykorzystałem do konfiguracji całej aplikacji. Inne połączenie stosowane jest lokalnie, a inne na serwerze. Taką domyślną konfigurację przekazujemy w pliku app.module.ts.

Do funkcji connect obiektu SingalR w naszym resolverze możemy przekazać dodatkowe opcje, które nadpiszą te domyślne. W ten sposób możemy przekazać na przykład tylko nazwę Huba, do którego chcemy się połączyć. Serwer pobrany zostanie wtedy z domyślnej konfiguracji, jest to bardzo wygodne. W samym komponencie możemy teraz dostać obiekt połączenia do serwera w funkcji ngOnInit, o ile w deklaracji routingu dodaliśmy resolve przy pomocy wcześniej zaimplementowanego resolvera.

Podsumowanie

Na tą chwilę w aplikacji udało mi się zrobić tylko tyle w kwestii SignalR’a. Teraz mogę przejść do konkretnej implementacji mojego rozwiązania. Na pewno w kolejnych postach podzielę się jak ona wygląda.

Zmienne środowiskowe w Angular

Często, gdy pracujemy nad naszą aplikacją musimy korzystać z innych adresów dla naszego API na produkcji, a innego lokalnie. Między innymi w takich przypadkach przychodzą nam z pomocą zmienne środowiskowe aplikacji. Dzisiaj chciałem pokazać jak w szybki sposób dodać możliwość obsługi zmiennych środowiskowych w naszej aplikacji wykorzystującej Angulara. Myślałem że będzie to coś, co jest dostępne out of the box, jednak nie zawsze tak jest. Jeżeli korzystacie z Angular-CLI, macie dostęp do nich w katalogu environments. Ja pokażę jak łatwo coś takiego zaimplementować w sytuacji, gdy nie mamy takiego udogodnienia. Mój przykład opiera się o aplikację, która powstała z szablonu dostępnego w dotnet CLI o którym pisałem w poprzednich postach. Moja aplikacja wykorzystuje Webpacka.

Implementacja serwisu z konfiguracją

Moje rozwiązanie nie jest idealne, ponieważ obsługuje jedynie dwie wersje aplikacji: lokalną czyli developerską, oraz produkcyjną. Jeżeli chcielibyśmy mieć możliwość obsługi innego środowiska, należy dodać kilka dodatkowych elementów, np. task w Gulpie, który podmieniałby nasze konfiguracje. Ja nie będę się tutaj rozpisywał na ten temat. Rozwiązanie składa się z kilku elementów. Na początek dodajemy plik app.config.ts, który przechowuje nasze zmienne.

Plik ten zawiera dwie klasy, które implementują interfejs IConfig. Dzięki temu nigdy nie przeoczymy żadnej zmiennej podczas dodawania konfiguracji. Poniżej przykład takiego intefejsu.

Następnie dodajemy serwis, który będzie przekazywany wewnątrz aplikacji.

Tutaj, w zależności od tego, czy uruchomiliśmy aplikację lokalnie, czy na produkcji, tworzymy instancję klasy zawierającej nasze konfiguracje. Funkcja isDevMode sprawdza na jakim środowisku jesteśmy. W moim przypadku to Webpack uruchamia aplikację w odpowiednim trybie (podczas publishu na produkcję dodawana jest opcja env.prod, o tym więcej w dokumentacji dotnet CLI). W serwisie definiujemy tyle pól, ile potrzebujemy. Ja użyłem tutaj składni Typescipt’u, która pozwala na dodawanie getterów. Możemy taką klasę podzielić na mniejsze, jeżeli nie chcemy mieć jednej dużej klasy z całą konfiguracją. Taki serwis możemy wstrzykiwać w inne serwisy lub komponenty naszej aplikacji, oraz podmieniać ją na inną podczas testów.

Podsumowanie

Jest to rozwiązanie, które można jeszcze rozwinąć, ale obecnie spełnia moje wszystkie wymagania. Dodatkowo jest bardzo proste w implementacji i działa automatycznie, bez konieczności wykonywania innych operacji podczas rozwoju aplikacji, takich jak uruchamianie skryptów. Mam nadzieję, że komuś się przyda!

Routing w Angular2 – pierwsze kroki

Dzisiaj chce przedstawić jak w szybki sposób dodać routing do naszej aplikacji z frameworkiem Angular. Podczas tego procesu można natknąć się na kilka problemów, o których tutaj wspomnę. Do dzieła!

Routing krok po kroku

Moje przykłady podaję na projekcie, który powstał przy użyciu dotnet CLI. Po zainstalowaniu .NET Core SDK, w konsoli wpisujemy dotnet new --install Microsoft.AspNetCore.SpaTemplates::*. Daje nam to dostęp do wielu szablonów projektów. Następnie wpisujemy dotnet new angular i mamy gotowy projekt z którym można pracować! Po więcej informacji na ten temat odsyłam na bloga jednego z autorów tych szablonów, Steve’a Sandersona. Opisał to wszystko ładnie w tym poście.

Wracając do routingu, na początek definiujemy moduł, w którym powiemy aplikacji jak ma się zachowywać, gdy użytkownik będzie chciał wejść w podany przez niego adres na naszej stronie. Później zaimportujemy go do głównego modułu, który jest uruchamiany jako pierwszy. Moduł routingu składa się z kilku części. Na początek importujemy potrzebne komponenty:

Dekorator NgModule potrzebny jest do zdefiniowania modułu. Routes oraz RouterModule potrzebne są już do napisania kodu, który pozwoli nam na nawigację wewnątrz aplikacji. Następnie importowaliśmy komponenty, które sami dodaliśmy i chcemy wyświetlić. Następnie pora na przekazanie aplikacji jak ma się zachowywać, czyli definicję ścieżek.

Import RouterModule.forRoot(routes) załatwi nam sprawę routingu.

Dzięki takiej definicji, po otwarciu aplikacji, od razu zobaczymy komponent CatComponent. Został on jawnie zaimportowany na starcie aplikacji. W tym przykładzie pokażę także jeszcze jedną ważną sprawę na którą trzeba zwrócić uwagę, w momencie gdy dodajemy routing do naszej aplikacji. CatComponent został pobrany od razu gdy weszliśmy na stronę aplikacji, mogło to wpłynąć na czas otwarcia aplikacji. Takie podejście nazywamy EagerLoading. Jeżeli byłby on duży, mogłoby to w znaczący sposób wpłynąć na to, jak użytkownik będzie odbierał naszą aplikację. Z kolei DogModule zostanie załadowany dopiero wtedy, gdy będziemy chcieli wejść do niego. Będzie wtedy można zauważyć małe opóźnienie przed otwarciem tej strony. Ta metoda z kolei nazywana jest LazyLoading. Dzięki temu, że komponent załadowany będzie później, cała strona będzie ładować się szybciej. Coś za coś, idziemy tutaj na kompromis. Jednak nie musi tak być. Wcześniej w imporcie komponentów z @angular/router zaimportowałem jeszcze klasę PreloadAllModules. Jeżeli definicję modułu zmienimy na następującą:

Moduł DogModule , oraz wszystkie inne które tak dodamy do routingu pobrane zostaną w tle po uruchomieniu aplikacji. Dzięki temu zyskujemy zarówno szybkość ładowania całej aplikacji, jak i responsywność podczas przechodzenia pomiędzy stronami. Ważna uwaga: żeby LazyLoading działał w projekcie wykorzystującym Webpacka, musimy zainstalować paczkę angular-router-loader, a następnie użyć ją w pliku webpack.config.js podczas konfiguracji plików .ts.

Kolejnym istotnym punktem jest stała routableComponents którą eksportujemy razem z modułem. Dzięki temu że tutaj dodajemy moduły, nie trzeba będzie ich na nowo importować w głównym module.Należy pamiętać, że każdy komponent który jest używany musi być dodany do declarations.

Teraz pozostaje tylko dodać moduł DogModule:

Oraz moduł routingu DogRoutingModule:

Dla takich małych modułów może się wydawać, że to spory narzut, jeżeli chodzi o ilość pracy. Ale jeżeli nasze komponenty są bardziej rozbudowane i mają swoje ścieżki podrzędne, dzięki temu nasz główny komponent nie rozrośnie się do niebotycznych rozmiarów. Dodajemy także separację do logiki naszej aplikacji, co wpływa na przejrzystość kodu.

Na koniec dodajemy nasz MainComponent, czyli główną stronę aplikacji:

Oraz szablon dla tego komponentu:

W szablonie możemy zobaczyć dwa ważne elementy: routerLink oraz router-outlet. routerLink to odpowiednik ng-href z pierwszego Angulara, czyli po prostu doda nam odpowiedni adres dla odnośnika na podstawie podanej nazwy ścieżki. router-outlet to najważniejszy element tutaj, czyli miejsce gdzie wstawione zostaną nasze komponenty, w poprzedniej wersji ng-view. Dodatkowo należy pamiętać także o dodaniu do znacznika head wartość <base href="/">, bez tego routing nie zadziała.

Podsumowanie

Przedstawiłem tutaj jedynie część możliwości jakie daje nam Angular jeżeli chodzi o routing. W kolejnych postach przedstawię bardziej skomplikowane przypadki: przekazywanie parametrów w adresie scieżki, RouteGuards, czyli mechanizm pozwalający na np. blokowanie wejścia do niektórych fragmentów aplikacji oraz wykorzystanie więcej niż jednego router-outlet.

Wprowadzenie do Angular 2

Dzisiaj chciałem opisać w kilku punktach najważniejsze cechy frameworka Angular dla użytkowników, którzy nie mieli z nim jeszcze styczności. Na wstępnie trzeba zaznaczyć, że twórcy odeszli od nazwy Angular 2 (głównie z powodu wporwadzenia Semantic Versioning). Od teraz wersje 1.x.x nazywamy AngularJS, a do wydań oznaczonych jako 2.x.x i dalej będziemy używali krótkiej nazwy Angular. Można o tym poczytać tutaj.

Czym jest Angular?

Logo frameworka Angular

Angular to framework służący do rozwijania aplikacji webowych oraz na platformy mobilne. Nie jest to kolejna wersja swojego poprzednika, został napisany całkowicie od nowa. Wersje te nie są ze sobą kompatybilne. Na pierwszy rzut oka widać, że dwójka bardzo różni się od swojego pierwowzoru. Poniżej postaram się przedstawić te różnice i pokazać, że pomimo tego że są to całkiem inne narzędzia, developerzy korzystający z jedynki będą mogli wykorzystać część swojego doświadczenia podczas pracy z nowym wydaniem.

Wybór języka

Twórcy Angulara dają nam wybór w kwestii języka, którym będziemy się posługiwać podczas pisania naszych aplikacji. Do wyboru mamy następujące opcje:

  • ES5, czyli po prostu JavaScript. Ten sam, który był najczęściej wykorzystywany podczas rozwoju aplikacji w starym AngularJS.
  • ES6/ES2015, jest to rozszerzenie standardowego języka JavaScript, dodające nowe funkcje. Nie wszystkie przeglądarki go wspierają, dlatego podczas rozwoju aplikacji wykorzystujących ES6 należy korzystać z kompilatorów np. Babel.
  • TypeScript wprowadza do JavaScript’u jeszcze więcej możliwośći np. typy, dzięki czemu łatwiej wyłapywać błędy podczas pisania aplikacji. Jest rozszerzeniem ES6, i tak samo jak on, wymaga kompilatora. Więcej na jego temat można poczytać w tym miejscu.
  • Dart jest to język programowania stworzony przez firmę Google. Po więcej informacji na temat tego języka zapraszam tutaj.

W dalszej części tego artykuły będę posługiwał się przykładami napisanymi w TypeScripcie.

Moduły

Obie wersje Angulara przedstawiają koncepcję modułu, jako swego rodzaju punkt wejścia dla naszej aplikacji. W pierwszej wersji definiowaliśmy go w następujący sposób:

W nawiasach kwadratowych podawaliśmy zależności z których chcieliśmy korzystać w naszej aplikacji. Następnie musieliśmy oznaczyć w HTMLu gdzie nasza aplikacji ma się znajdować:

Teraz definiujemy go w następujący sposób:

Moduł tym razem służy do oznaczenia z jakich elementów nasza aplikacja będzie się składała. Do tego celu służy dekorator @NgModule, który używamy w połączeniu z klasą modułu. Wskazujemy, z jakich modułów dostarczanych z zewnątrz będziemy korzystać (imports) np. RouterModule lub BrowserModule. Deklarujemy (declarations) z jakich komponentów będziemy korzystać w naszej aplikacji. Może się ona składać z setek lub tysięcy małych komponentów. Definiujemy tutaj także komponent wejściowy przy użyciu słowa bootstrap. Komponent ten zostaje uruchomiony jako pierwszy. W górnej części tego pliku znajdują się importy, czyli mechanizm TypeScriptu, który pozwala na użycie innych bibliotek w naszej aplikacji. Modułom dokładniej przyjrzymy się w kolejnych postach.

Kontrolery i Komponenty

Angular odszedł od koncepcji kontrolerów, używanych w pierwszej wersji. Kontrolery były definiowane w widoku:

Następnie dodawany był kod, który przedstawiał jego logikę.

Obecnie w widoku używamy tylko nazwy naszego komponentu np.:

Sam komponent zaś definiuje jego logikę oraz wygląd:

Importujemy tutaj dekorator komponentu, który pozwala nam na jego definicje. Wskazujemy selector, który będzie wskazywał na nasz komponent w HTMLu, jego wygląd poprzez template (można oczywiście dodać ten szablon w osobnym pliku), oraz logikę komponentu w klasie.

Dyrektywy

Dyrektywy dostępne w Angularze uległy znacznym zmianom, ale dalej są obecne. Popularne w pierwszej wersji dyrektywy to na przykład ng-repeat oraz ng-if. W nowej wersji zostały one zastąpione przez *ngFor oraz *ngIf. Nazywamy je Structural directives, ponieważ wpływają na strukturę naszej strony (powielają elementy, usuwają etc). Są poprzedzane znakiem *. Przykłady dla AngularJS:

A teraz nowa wersja:

Różnice nie są wielkie, oprócz samej składni, dla *ngFor dochodzi także słowo kluczowe let, które definiuje zmienną lokalną.
Oprócz tego, w Angularze usunięte zostają inne dyrektywy, takie jak ng-src, ng-style, ng-href. Podobnie jest z dyrektywami, które służyły do obsługi zdarzeń np. ng-click czy ng-focus. Zamiast tego, możemy podpiąć się bezpośrednio do atrybutów lub zdarzeń w HTML, na przykład:

Do właściwości podpinamy się poprzez nawiasy kwadratowe [], a do zdarzeń nawiasy okrągłe (). Daje nam to dużo możliwości, ponieważ nie musimy już polegać na tym, czy interesująca nas dyrektywa istnieje. Zamiast tego korzystamy po prostu z atrybutów oraz zdarzeń w HTMLu.

Data binding

Tutaj także jest wiele podobieństw, chociaż ponownie składnia trochę się różni. O ile dalej możemy korzystać z nawiasów klamrowych {{...}} aby wyświetlić dane lub tworzyć wyrażenia, to two-way oraz one-way binding znany z poprzednika wygląda trochę inaczej. Działa tutaj ten sam mechanizm, który pokazałem w dyrektywach. One-way binding, w którym zmiany wychodzą z widoku do kodu, odbywa się poprzez użycie nawiasów okrągłych (). Tutaj przykładem mogą być eventy, gdzie zdarzenie wysyła nam informacje do naszej klasy. Wysyłanie danych z klasy do widoku odbywa się poprzez nawiasy kwadratowe [] (wykorzystując atrybuty, klasy, style, właściwości) lub klamrowe {} (mechanizm ten nazywa się Interpolation). Two-way binding, czyli przesył danych w obie strony, odbywa się zarówno przy użyciu nawiasów kwadratowych i okrągłych [(...)]. Składnia ta wydaje się dziwna, ale jest w 100% spójna z tym co powiedzieliśmy wcześniej: dane zarówno wysyłane są z widoku do kodu, oraz z kodu do widoku.

Zamiast nawiasów mamy możliwość korzystania z prefixów bind- lub on- dla komunikacji w jedną stronę, lub bindon- w obie strony. Przykłady:

Serwisy

W pierwszym Angularze mieliśmy do wyboru kilka metod, które pozwalały na dodawanie serwisów do aplikacji. Dzięki nim byliśmy w stanie wyciągnąć w jedno miejsce logikę, która powtarzała się w wielu fragmentach aplikacji lub pozwalały na komunikację pomiędzy różnymi jej elementami. Czasem pojawiał się problem: którą metodę wybrać? Do wyboru były popularne Factory, Service czy Provider, a także Constants oraz Values. Ten element został całkowicie zmieniony w nowej wersji. Zamiast wybierać dostępne mechanizmy, po prostu dodajemy klasę, która ma w sobie określoną funkcjonalność, a następnie używamy jej w dowolnym miejscu.

Należy jedynie dodać dekorator @Injectable, który pozwoli na wstrzyknięcie naszego serwisu np. do komponentu. O tym opowiem w kolejnym akapicie.

Dependency Injection

Dependency Injection to wzorzec architektoniczny, polegający na wstrzykiwaniu zależności do naszego kodu. Jeżeli chcemy odseparować z komponentu logikę np. pobierania użytkowników z bazy danych, a w samym komponencie zająć się jedynie ich wyświetlaniem, możemy zrobić to w łatwy sposób. Pobieranie danych wyciągamy do Serwisu, czyli innej klasy, a w komponencie wskazujemy na to, że potrzebujemy dostać obiekty do wyświetlenia. W module dodajemy komponent i serwis:

W komponencie wskazujemy, że potrzebujemy serwisu (używamy tutaj CatsService z poprzedniego akapitu), który zwróci nam nasze obiekty do wyświetlenia:

W ten sposób nasz komponent będzie w stanie wyświetlić listę naszych kotów, bez wiedzy skąd trzeba je pobrać!

Podsumowanie

Oczywiście, to co tutaj przedstawiłem to tylko część dobrodziejstw które oferuje nam Angular. Mamy jeszcze Routing, który został znacznie uproszczony, filtry, które teraz nazywają się Pipes oraz wiele innych. Wydaje mi się, że pomimo wielu różnic które pojawiły się w nowej wersji (wiele z nich moim zdaniem dobrych), kilka konceptów znanych z pierwszego wydania tego frameworka można zastosować w nowych aplikacjach, które będą już korzystały z najnowszej edycji. Dzięki temu próg wejścia w tą technologię jest dla weteranów AngularJS znacznie mniejszy.

Na dzisiaj to tyle. Tematy, które nie zostały tutaj poruszone na pewno pojawią się w przyszłości. Postaram się także rozwinąć bardziej szczegółowo to, o czym już mówiłem. Zapraszam do komentowania!