Paweł Łukasiewicz
2019-12-30
Paweł Łukasiewicz
2019-12-30
Udostępnij Udostępnij Kontakt
Wprowadzenie

Poprzednie wpisy wprowadzały nas coraz głębiej w świat Angulara. Nie robiliśmy niczego skomplikowanego:

  • poznaliśmy wymagane narzędzia;
  • zapoznaliśmy się nieco z CLI;
  • utworzyliśmy pierwszą aplikację;
  • zmodyfikowaliśmy strukturę projektu;
  • dodaliśmy nowy komponent;
  • mieliśmy styczność z podstawowymi dyrektywami.
Przed dalszą częścią wyprawy warto nieco głębiej zgłębić temat archituktury Angulara.

Angular 8 jest platformą i frameworkiem, który pozwala na budowanie aplikacji klienckich przy wykorzystaniu HTML oraz TypeScript. Sam również został napisany w TypeScript. Implementacja rdzenia (oraz dodatkowych funkcjonalności) odbywa się za pomocą zestawu bibliotek TypeScript, które można importować do tworzonej aplikacji.

Bardzo istotnymi (a jednocześnie podstawowymi) elementami składowymi aplikacji AngularNgModules - zapewniają odpowiedni kontekst dla używanych komponentów. NgModules gromadzą powiązany kod w ramach zdefiniowanych zestawów funkcjonalności. Aplikacja zawsze posiada przynajmniej jeden moduł główny, który pozwala na ładowanie innych modułów posiadających określoną implementację. Jeżeli macie jakiekolowiek wątpliwości zapraszam do poprzedniego wpisu: Angular 8: komponenty

Wspomniane wcześniej komponenty definiują widoki, które, dzięki napisanej logice, mogą być modyfikowane zgodnie z implementacją i danymi aplikacji. Sama logika pozwala na wstrzykiwanie różnych zależności dzięki czemu napisany kod staje się kodem modułowym oraz wielokrotnego użytku.

Zwróćcie uwagę na kluczowe elementy architektury Angulara: Angular 8: architektura

Moduły

Aplikacja Angular 8 składa się z modułu głównego zwanego AppModule. Zapewnia on mechanim początkowego ładowania, które pozwala na uruchomienie aplikacji. Szczegóły proces od momentu wywołania polecenia ng serve do uruchomienia aplikacji został omówiony we wpisie: Angular 8: ładowanie aplikacji

W pełni funkcjonalna aplikacja składa się z wielu modułów implementujących określone (inne) funkcjonalności.

Do najważniejszych cech modułów możemy zaliczyć:

  • importowanie dodatkowych funkcjonalności z innych NgModules;
  • eksport własnych funkcji i wykorzysywanie ich przez inne NgModules. Prostym przykładem może być wykorzystanie routera w aplikacji – możemy dokonać importu modułu Router (temat ten zostanie omówiony w osobnym wpisie ponieważ jest niezywkle istotny);

Komponenty

Na ich temat wiemy już nieco więcej – głównie ze względu na ich znaczenie w aplikacji i eksperymenty, które prowadziliśmy. Nie wykorzystaliśmy jednak jeszcze ich klas (w świadomy sposób) ponieważ poruszaliśmy jedynie zagadanienia podstawowe. Ten temat rozszerzymy we wpisie dotyczącym ‘wiązania właściwości’. Warto jednak mieć w pamięci, że każdy komponent definiuje klasę zawierającą dane aplikacji oraz logike i jest powiązany z szablonem HTML, który definiuje widok wyświetlany jako część aplikacji.

Zanim pójdziemy dalej musimy poświęcić parę chwil na znaczenie metadanych. Z perspektywy mojego doświadczenia z frameworkiem Oracle JET wiem, że są niezwykle istotne (dodatkowo niektóre z ich komponentów z czasem stają się przestarzałe więc trzeba w miarę na bieżąco śledzić postępy prac związane z nadchodzącymi wersjami). Metadane klasy komponentu są kojarzone/powiązane z szablonem definiującym widok. Szablon łączy zwykły kod HTML z dyrektywami Angulara oraz znacznikami wiązania (temat data-binding omówimy później), które pozwalają Angularowi modyfikować HTML przed renderowaniem danego widoku i przygotowaniem go do wyświetlenia. W później części pracy z tą plalformą wykorzystamy wpis: .NET Core: tworzenie danych na potrzeby fake API w celu przygotowania danych przy użyciu .NET Core Web API.

Proces dostarczania danych do komponentów odbywa się przez wstrzykiwanie zależności (Dependency Injection). Angular 8: architektura

Szablony, dyrektywy i wiązanie danych

Szablony w Angular 8 służą do łączenia kodu HTML ze znacznikami wiązania w celu modyfikacji kodu HTML przed wyświetleniem. Z kolei specjalne dyrektywy pozwalają na implementacje odpowiedniej logiki.

Możemy wyróżnić dwa typy wiązania danych:

  • Event Binding: wiązanie zdarzeń służy do wiązania zdarzeń z aplikacją i reagowania na dane wejściowe użytkownika poprzez aktualizację danych wyświetlanych w aplikacji;
  • Property Binding: polega na przekazywaniu danych z klasy komponentu zapewniając łączenie danych aplikacji z drzewem DOM.

Usługi i wstrzykiwanie zależności

Angular 8 pozwala na tworzenie klasy usługi dla danych lub logiki biznesowej, która nie jest powiązana z konkretnym widokiem. Klasa taka może zostać udostępniona pomiędzy komponentami.

Tutaj z pomocą przychodzi nam Dependency Injection. Mechanizm ten nie pobiera danych z serwera, nie przeprowadza walidacji danych wejściowych oraz nie odpowiada za logowanie żadnych informacji do konsoli – takie zadania przekazywane są wyżej wspomnianym usługą. Wstrzykiwanie zależności pozwala na tworzenie lekkich i wydajnych usług.

Aby nieco rozjaśnić powyższe wyjaśnienie spojrzymy na prosty przykład. Wyobraźmy sobie usługę, której zadaniem jest wypisywanie różnych poziomów logów w oknie konsoli przeglądarki. Poniżej przykładowa implementacja:

export class Logger { 
    log(msg: any) { 
        console.log(msg); 
    } 
    error(msg: any) { 
        console.error(msg); 
    } 
    warn(msg: any) { 
        console.warn(msg); 
    } 
}
Usługi mogą oczywiście zależeć od innych usług. Wyobraźmy sobie serwis, który zależy od serwisu Logger ale również od drugiego serwisu, którego celem jest pobieranych danych (BackendService). Ten serwis z kolei będzie zależał od zaimplementowanego serwisu HttpClient, który pozwala na asynchroniczne pobieranie danych z serwera:
export class CarsService {
  private cars: Cars[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getCars() {
    this.backend.getAll(Cars).then( (cars: Cars[]) => {
      this.logger.log(`Pobrano ${cars.length} samochodów.`);
      this.cars.push(...cars); // wypełniamy tablicę danymi
    });
    return this.cars;
  }
}
Dependency Injection jest używany w aplikacjach Angular dosłownie wszędzie – pozwala dostarczać komponentom wymagane usługi. Usługi takie są wstrzykiwane do komponentu umożliwiając dostęp do jego klasy (a zarazem całej funkcjonalności).

Routing

W Angular 8 Router jest modułem NgModule, który zapewnia usługę pozwalającą programistom na definiowanie ścieżek nawigacji pomiędzy różnymi stanami oraz hierachią widoków aplikacji. Czemu wspominam o różnych stanach? Wyobraźcie sobie np. widok konfiguracji sytemu który posiada wiele zakładek, tj. ustawienia główne, status serwera, konfiguracja serwisów, etc. Router pozwala na nadanie stanu każdej z zakładek – dzięki temu dysponując adresem URL jesteśmy w stanie ponownie otworzyć aplikację na danej stronie oraz zaznaczyć wskazaną zakładkę.

Generalnie zasada działania jest taka sama jak działa nawigacja przeglądarki:

  • wprowadzenie adresu URL prowadzi do odpowiedniej strony w aplikacji;
  • kliknięcie linka na stronie spowoduje przejście przeglądarki pod wskazany adres (tj. widok);
  • klikając wstecz przy użyciu przycisków przeglądarki aplikacja będzie w stanie przejść do odpowiedniej strony ‘do tyłu’ zgodnie z historią przeglądania.