Component Driven User Interfaces (CDUI) to podejście projektowania i tworzenia interfejsów użytkownika, które skupia się na budowaniu aplikacji poprzez połączenie małych, samodzielnych komponentów. W CDUI, każdy element UI jest traktowany jako oddzielny komponent, który może być projektowany, testowany i rozwijany niezależnie od reszty aplikacji. Głównym celem CDUI jest zwiększenie modularności i skalowalności aplikacji, a także ułatwienie tworzenia spójnego i łatwego do utrzymania interfejsu użytkownika. Poprzez budowanie aplikacji z mniejszych, samodzielnych elementów, zespoły projektowe mogą łatwiej przystosować aplikację do zmieniających się wymagań biznesowych, a także szybciej reagować na ewentualne błędy i problemy.
Więcej o CDUI: https://www.componentdriven.org/
Storybook:
Storybook to narzędzie do budowania, prezentowania i testowania komponentów interfejsu użytkownika (UI) w izolacji od reszty aplikacji. Umożliwia programistom, projektantom i testerom pracę na poszczególnych elementach UI w odizolowanym środowisku bez konieczności uruchamiania całej aplikacji.
src: https://prateeksurana.me/blog/react-component-library-using-storybook-6/
Storybook stanowi też interaktywną dokumentację komponentów, która może być łatwo udostępniona i wykorzystywana przez całe zespoły projektowe. W ramach Storybooka można tworzyć różne wersje i warianty komponentów, zasilać je różnymi danymi oraz testować je w różnych przypadkach brzegowych, co pozwala na łatwe debugowanie i wykrywanie błędów.
Jednym z kluczowych problemów, które rozwiązuje Storybook, jest zmniejszenie czasu potrzebnego na rozwój aplikacji. Dzięki możliwości pracy na pojedynczych komponentach w izolacji, programiści i projektanci mogą skupić się na rozwoju poszczególnych elementów, bez konieczności ciągłego uruchamiania całej aplikacji.
Zanim zaczniemy…
Aby w angularowym (i każdym innym) projekcie móc w pełni skorzystać z podejścia CDUI z wykorzystaniem Storybooka należy zadbać o wydzielenie interfejsu użytkownika jako osobnej (możliwie niezależnej) warstwy w aplikacji. Jednym ze sposobów na osiągnięcie tego jest wprowadzenie podziału komponentów na te odpowiedzialne za warstwę prezentacyjną (presentational components) oraz te odpowiedzialne za routing i logikę biznesową (container components).
Więcej o takim podziale: https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/
Dla porządku warto w kodzie źródłowym wprowadzić konwencje porządkujące ten podział:
- nazewnictwo komponentów (np. wszystkie kontenery oznaczać postfixem *-container.component.ts),
- struktura katalogów (oba typy komponentów umieszczać w różnych odpowiednio nazwanych katalogach, np. containers i components),
- struktura modułów (dla reużywalnych komponentów prezentacyjnych tworzyć osobne moduły/biblioteki, niezależne od reszty aplikacji),
- osobne interfejsy dla komponentów prezentacyjnych (unikanie korzystania bezpośrednio z referencji na modele biznesowe. Niezależny interfejs dla każdego komponentu można umieścić w osobnym pliku *-foo.interface.ts zaraz obok pliku z klasą komponentu *-foo.component.ts. Interface można również poprzedzić prefixem Ui-*, np. UiUserDetails).
Oto przykładowa struktura zawierająca folder grupujący moduły z warstwy UI, a w nim moduł user-details
zawierający komponent z interfejsem wydzielonym do osobnego pliku. W zależności od wielkości projektu struktura może być uproszczona lub bardziej złożona.
Dodanie Storybooka i pierwsze story:
Dla istniejącego projektu wystarczy uruchomić prostą komendę (znajdując się w głównym katalogu projektu):
npx storybook@latest init
Dzięki temu zainstalujemy wszystkie zewnętrzne zależności, wygenerujemy pliki konfiguracyjne, dodamy przydatne skrypty w package.json a nawet utworzymy przykładowe storybook stories.
Po uruchomieniu komendy npm run storybook w konsoli zobaczmy mniej więcej taki output:
Pod wskazanym adresem zobaczymy uruchomionego lokalnie Storybooka, wraz z live-reloadem (każda modyfikacja komponentu spowoduje odświeżenie go w Storybooku w przeglądarce w czasie rzeczywistym).
Wróćmy teraz do naszego komponentu ‘user-details’ i rzućmy okiem na jego prostą implementację oraz przygotowany w osobnym pliku interfejs.
// user-details.interface.ts
1 2 3 4 5 6 7 8 9 |
export interface UiUserDetails { firstName: string; lastName: string; email: string; avatar: { url: string; alt: string; } | null; } |
// user-details.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 |
import { Component, Input } from "@angular/core"; import { UiUserDetails } from "./user-details.interface"; @Component({ selector: "app-user-details", templateUrl: "./user-details.component.html", styleUrls: ["./user-details.component.scss"] }) export class UserDetailsComponent { @Input() user?: UiUserDetails; } |
Dla takiego komponentu utwórzmy tuż obok nowy plik user-details.stories.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import type { Meta, StoryObj } from "@storybook/angular"; import { UserDetailsComponent } from "./user-details.component"; import { UiUserDetails } from "./user-details.interface"; // story meta config const meta: Meta<UserDetailsComponent> = { title: "User/UserDetails", component: UserDetailsComponent }; export default meta; // mocks const userMock: UiUserDetails = { firstName: "John", lastName: "Smith", avatar: { url: "https://placehold.it/100x100", alt: "John Smith avatar" } }; // stories type UserDetailsStory = StoryObj<UserDetailsComponent>; export const primary: UserDetailsStory = { args: { user: userMock } }; |
Przygotowanie obiektu meta, powiązanie go z konkretnym ui-komponentem i wyeksportowanie jako wartość domyślna pliku pozwoli nam wyświetlić różne warianty komponentu w Storybooku. W dalszej części zdefiniowaliśmy demonstracyjną wartość dla inputu user
(tzw. mock), a w ostatnim kroku zdefiniowaliśmy konkretną pojedynczą story
z konkretnymi danymi. Wszystko to razem już teraz pozwala nam podejrzeć gotowy komponent w Storybooku (bez uruchamiania angularowej aplikacji!):
Nieco interakcji:
Powyżej zaprezentowany został najprostszy możliwy use-case Storybooka, jednak samo narzędzie pozwala na o wiele więcej. Rozszerzmy nasz demonstracyjny komponent o dodatkowy parametr (input) notificationCount
, który będzie wyświetlał liczbę oczekujących na użytkownika powiadomień (o ile jest ona większa, niż 0):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Component, Input } from "@angular/core"; import { UiUserDetails } from "./user-details.interface"; @Component({ selector: "app-user-details", templateUrl: "./user-details.component.html", styleUrls: ["./user-details.component.scss"] }) export class UserDetailsComponent { @Input() user?: UiUserDetails; @Input() notificationCount = 0; } |
Tym razem zamiast hardcodować w pliku user-details.stories.ts kolejną demonstracyjną wartość skorzystamy z dostępnych w Storybooku kontrolek, które pozwolą dynamicznie zmieniać przekazywane do komponentu wartości.
Nasz obiekt meta należy rozbudować o dodatkowy atrybut zawierający konfigurację interaktywnych kontrolek:
1 2 3 4 5 6 7 8 9 10 11 12 |
const meta: Meta<UserDetailsComponent> = { title: "User/UserDetails", component: UserDetailsComponent, argTypes: { notificationCount: { options: [0, 1, 9, 15, 99, 123, 999, 2317], defaultValue: 0, control: { type: "radio" } } } }; export default meta; |
Dzięki temu pozwolimy w sposób interaktywny przetestować nasz komponent dla różnych możliwych przypadków brzegowych (i bardzo szybko wykryć potencjalne błędy, jak np. ucinanie zbyt długich wartości):
W prawdziwym życiu i prawdziwych projektach nasze komponenty bywają oczywiście dużo bardziej skomplikowane. Czasem konieczne może być zaimportowanie w obiekcie meta dodatkowych zależności, zamockowanie jakichś providerów czy stworzenie wielu osobnych wariantów historyjek dla pojedynczego komponentu. Trzymając się jednak wspomnianych wcześniej zasad dot. wydzielania komponentów UI jako osobnej warstwy tworzenie i utrzymywanie Storybooka dla dowolnych komponentów jest to po prostu szybkie i wygodne.
Kod źródłowy do pokazanego powyżej przykładu znajduje się tu:
https://github.com/Herdu/storybook-demo
Chromatic:
Chromatic to platforma, która pozwoli nam wycisnąć ze Storybooka to co najlepsze. Dzięki temu narzędziu w bardzo prosty sposób wykonamy następujące rzeczy:
- udostępnimy gotowy katalog komponentów reszcie zespołu (w tym osobom nietechnicznym). Dajemy możliwość dodawania komentarzy, przez co mocno skrócimy feedback loop,
- przeprowadzimy zautomatyzowane testy regresji wizualnej,
- przetestujemy zachowanie komponentów w różnych przeglądarkach,
- automatycznie utworzymy historię zmian UI,
- zintegrujemy wszystko z procesami CI/CD.
src: https://www.chromatic.com/docs/
Dodanie Chromatica do projektu i korzystanie z niego jest proste (po więcej szczegółów odsyłam na oficjalną stronę: https://www.chromatic.com/), a korzyści z niego płynące wynagradzają wysiłek włożony w Storybooka.
Podsumowanie:
Przedstawione narzędzia i koncepcje pozwalają wdrożyć koncepcję CDUI w życie. Tworzenie warstwy interfejsu użytkownika może stać się zadaniem odrębnym, niezależnym, łatwym do wydelegowania innemu programiście, czy nawet innemu zespołowi. Sama praca nad komponentami staje się dużo szybsza i wygodniejsza, a testowanie przypadków brzegowych jeszcze prostsze (zarówno w momencie tworzenia, jak i w późniejszych etapach rozwoju).
Storybook, nawet w najprostszej formie, stanowi zestaw działających przykładów użycia każdego UI komponentu. Korzystając z dodatkowych rozszerzeń (np. https://storybook.js.org/addons/@storybook/addon-docs) możemy bez dodatkowej konfiguracji wygenerować pełną dokumentację naszego katalogu komponentów.
Stawiając na reużywalność zapewnimy spójność wyglądu i zachowania naszej aplikacji. Idąc krok dalej możemy zbudować bibliotekę komponentów współdzieloną między wieloma projektami (nasze ui-komponenty mają przecież własne interfejsy, nie zależą od kontekstu biznesowego konkretnej aplikacji).
Źródła:
„Angular UI Design Patterns & Storybook” Angular Warsaw | #5 Angular Meetup https://www.youtube.com/watch?v=PqIYMylxXCY
Dokumentacja Storybook’a https://storybook.js.org/docs/angular/
Dokumentacja Chromatic’a: https://www.chromatic.com/
Dodaj komentarz