Post on 03-Jun-2020
Aplikacje SPA, Angular, TypeScript
dr hab. inż. Marek Wojciechowski
2
Ewolucja aplikacji webowych (pre-SPA)
• Model 1 (pierwotny model WWW)
– Strony powiązane linkami (jak dokumenty WWW)
• Model 2 (MVC)
– Kontroler decyduje o nawigacji
– Strony nie odwołują się do siebie bezpośrednio
• Model 3 (MVC + komponenty)
– Wersja Model 2 inspirowana sposobem działaniem aplikacji desktopowych: komponenty obsługujące zdarzenia
– Przebudowa i przeładowanie strony w reakcji na zdarzenie
• (Model 2 / Model 3) + Ajax
– Częściowa eliminacja nawigacji i odświeżania stron
– Źródło: http://itsnat.sourceforge.net/php/spim/spi_manifesto_en.php
3
Single Page Applications (SPA)
• Strona startowa aplikacji jest jedyną stroną pobieraną w całości z serwera (potem interakcje Ajax, WebSocket)
• Strona aplikacji nie przeładowuje się w czasie pracy z nią
• Nie następuje nawigacja do innych stron
• Zmienia się stan (i wygląd) strony w przeglądarce
• User Experience (UX) podobny do aplikacji desktopowych
• Technologie: HTML5, CSS, JavaScript, Ajax, WebSocket
• Frameworki: Angular, Ember.js, Meteor.js, ExtJS, React, …
4
Single Page Interface (SPI)
• Czy model SPA jest odpowiedni tylko dla aplikacji webowych (web applications) czy również dla witryn (web sites)? – Pytanie otwarte, różne stanowiska
– Głos „na tak”: „The Single Page Interface Manifesto” (http://itsnat.sourceforge.net/php/spim/spi_manifesto_en.php)
• Problemy przy tworzeniu witryn mocno opartych o Ajax: – Zakładki do „stanu strony” po jej zmianie Ajaksem, historia przeglądarki
– Indeksowanie przez wyszukiwarki (Search Engine Optimization (SEO))
– Model biznesowy oparty o liczbę odwiedzin stron
– Potrzeba wyskakujących okienek (pop-up)
– Wolne pierwsze wyświetlenie strony
5
Stan aplikacji SPA/SPI
• W tradycyjnej aplikacji webowej sekwencja stanów aplikacji to sekwencja stron
• W aplikacji SPA/SPI każda częściowa zmiana strony w przeglądarce (Ajax, DOM) skutkuje zmianą stanu
• Stany aplikacji SPA/SPI można podzielić na:
– Stany fundamentalne (odpowiadałyby stronom w modelu tradycyjnym, warte tworzenia zakładek)
– Stany drugorzędne
• Przykład kategoryzacji stanów: obsługa logowania
– Ekran do wprowadzenia użytkownika i hasła (fundamentalny)
– Informacje o błędach w formularzu logowania (drugorzędny)
– Ekran powitalny po zalogowaniu (fundamentalny)
6
Zakładki i historia przeglądarki w SPA
• Opis problemu
– Dla aplikacji SPA samoistnie pojawi się tylko jeden wpis w historii przeglądarki
– Ewentualna zakładka będzie prowadzić do strony w wersji pobranej z serwera (stan początkowy)
– Programowa podmiana (JS) window.location (location.href) może spowodować odwołanie przeglądarki do serwera
• Rozwiązania
– Wykorzystanie adresów URL adresujących fragmenty stron (#)
• Podmiana adresu URL (przez location.href lub location.hash) na różniący się od poprzedniego treścią po znaku # nie powoduje odwołania do serwera
– Wykorzystanie HTML 5 History API: history.pushState
[W obu przypadkach obsługa nawigacji Back/Forward jest programowa]
7
Rozwiązania pozostałych problemów modelu SPA/SPI
• Search Engine Optimization (SEO)
– Specjalny tryb nawigacji dla robotów (web crawlers)
• <a href="URL page" onclick="return false">…</a> (roboty obecnie ignorują JavaScript)
• Linki w formacie (Ajax-crawlable): „#!” (wyszukiwarka widząc taki URL zmienia go na zawierający „?_escaped_fragment_” a aplikacja odpowiada snaphotem strony HTML)
• Zwiększanie licznika odwiedzin stron
– Ramka IFRAME prowadząca do pustej strony ze skryptem
• Okienka pop-up
– Problemy: brak okien modalnych w przeglądarce, współdzielenie stanu między oknami
– Rozwiązanie: Symulowanie okienek modalnych/niemodalnych za pomocą elementów HTML z odpowiednim pozycjonowaniem
8
Architektura aplikacji SPA
Źródło: http://singlepageappbook.com/single-page.html
Cechy architektury:
• Write-only DOM
• Cały stan aplikacji w komponentach modelu
• Widoki obserwujące zmiany w modelu
• Modularyzacja z jak najmniejszymi zależnościami między modułami
• Jak największa izolacja od implementacji DOM w przeglądarce
• Brak kontrolera (znanego z MVC)
9
Kontrolery w aplikacjach SPA?
• Część pierwszych frameworków dla aplikacji SPA (np. AngularJS 1.x) przeniosło do przeglądarki wzorzec architektoniczny MVC sprawdzony i powszechny po stronie serwerowej
• Separacja modeli i widoków powszechnie akceptowana, ale czy spoiwem powinien być kontroler?
– Koncepcja Model-View-Whatever, MV*
• Typowe zadania przydzielane kontrolerom w SPA:
– Obsługa zdarzeń w modelu DOM
– Renderowanie szablonów widoków
– Synchronizacja widoków z modelami
• Lepiej jest powyższe zadania rozdzielić (zasada pojedynczej odpowiedzialności)
10
Angular (2+) / AngularJS (1.x)
• Front-endowy framework do tworzenia aplikacji webowych
• Rozwijany pod skrzydłami Google
• Głównie zorientowany na aplikacje SPA
• Od wersji 2.0 pozycjonowany jako jeden framework dla aplikacji webowych, mobilnych i desktopowych
• Zaimplementowany w języku JavaScript i (od wersji 2.0) TypeScript, wykorzystujący HTML i CSS
• Główna motywacja: HTML jest odpowiedni dla stron WWW, ale nieodpowiedni dla aplikacji webowych
11
AngularJS 1.x
• Framework oparty o wzorzec MVC po stronie klienta
• Obiekt scope ($scope) jako sposób współdzielenia danych między kontrolerem a widokiem
– Spoiwo między kontrolerem i widokiem, pełni rolę ViewModel
• Wiązanie danych (data binding):
– Interpolacje {{ }}, one-way data binding, two-way data binding
• Dyrektywy ng-* – dodatkowe atrybuty dla elementów HTML
– np. ng-app, ng-model, ng-repeat
• Filtry | – filtracja, sortowanie, transformacja danych
• 4 rodzaje komponentów do logiki biznesowej:
– Factory, Service, Provider, Value
• Routing – nawigacja między widokami aplikacji SPA
• Modularność (Angular modules)
12
AngularJS 1.x – „Hello World”
Na podstawie: https://angularjs.org/
<!doctype html> <html ng-app> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"> </script> </head> <body> <div> <label>Name:</label> <input type="text" ng-model="yourName" placeholder="Enter a name here"> <hr> <h1>Hello {{yourName | uppercase}}!</h1> </div> </body> </html>
13
Web Components
• Zbiór cech dodawanych do specyfikacji HTML i DOM
– Częściowo wspierany przez współczesne przeglądarki
– Wsparcie dla starszych przeglądarek poprzez polyfille
• Intencja: – przeniesienie komponentowego modelu tworzenia aplikacji na grunt WWW
– umożliwienie tworzenia reużywalnych komponentów (widgets) do wykorzystania na stronach WWW i po stronie front-endu aplikacji webowych
• Cechy (rodzaje Web Components)
– Custom Elements - APIs to definiowania nowych elementów HTML
– Shadow DOM – enkapsulacja DOM i styli w elemencie HTML
• Przeglądarka renderuje DOM elementów z Shadow DOM bez umieszczania ich w DOM głównego dokumentu
– HTML Imports – deklaratywny import dokumentów HTML do innych dokumentów HTML
– HTML Templates – szablony fragmentów HTML
14
Angular 2+ na tle Angular 1.x
• Istotne zmiany w porównaniu z Angular 1.x
– Właściwie nowy framework
– Brak zgodności wstecz, opcja migracji i współistnienia obu wersji w obrębie jednej aplikacji
– Duże zmiany w czasie jego rozwoju (Alpha->Developer Preview->Beta->Final)
• wiele tutoriali nieaktualnych
– Angular 2 witany przez deweloperów z mieszanymi uczuciami
– Kolejne zmiany 2 -> 4 -> 5 -> 6 już ewolucyjne, a nie rewolucyjne
• Przyczyny radykalnych zmian: – Rozwój języka JavaScript, pojawienie się języka TypeScript
– Rozwój przeglądarek
– Nowe trendy, np. Web Components
– Nadmierna komplikacja niektórych aspektów Angular 1.x (np. kilka rodzajów komponentów logiki biznesowej)
– Problemy wydajnościowe (np. gdy dużo danych w $scope)
15
Cechy Angular 2+ (zalety na tle 1.x)
• Framework również dla urządzeń mobilnych
• Większa modularność
• Wsparcie tylko współczesnych przeglądarek
– Uproszczenie frameworka przez brak workarounds dla starszych
• Orientacja na język TypeScript – Choć możliwe jest programowanie w czystym JavaScript
• Wykorzystanie elementów ECMAScript 2015
• Dynamiczne ładowanie
• Prostszy routing, serwisy do logiki biznesowej
• Poprawione wstrzykiwanie zależności
• Komponenty i dyrektywy zamiast kontrolerów i $scope
• Programowanie reaktywne (RxJs)
16
Angular 2 – komponent „Hello World”
Na podstawie: https://angular.io/
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>` }) export class AppComponent { name: string = 'Marek'; }
17
TypeScript
• Nadzbiór języka JavaScript rozwijany przez Microsoft
– Ze względu na postulowaną nieodpowiedniość JS dla dużych aplikacji
– Powstał jako rozszerzenie ECMAScript 5, z uwzględnieniem cech przewidywanych w ECMAScript 6
– Transpilowany do kodu JavaScript
• Najważniejsze cechy języka TypeScript:
– Klasy, moduły, funkcje strzałkowe, param. opcjonalne/domyślne (ES6)
– Składowe public (domyślnie), protected i private, przestrzenie nazw
– Adnotacje typu i kontrola typów na etapie kompilacji
– Automatyczna dedukcja typu (type inference)
– Interfejsy, klasy używane jako interfejsy (implements)
– Typy generyczne (<>), typy wyliczeniowe (enum), unie (union type), krotki (tuple)
– Domieszki (mixins) – w części implementowane programowo
– Dekoratory (@)
18
TypeScript w Angular – stan aktualny
• Angular od wersji 2 jest napisany w języku TypeScript
• TypeScript jest również zalecany w projektach opartych na Angularze
• W związku z rozwojem języka JavaScript (ES2015) wiele elementów składni TypeScript stało się standardową składnią JavaScript
– Klasy,moduły
• Cechy języka TypeScript niedostępne w czystym JavaScript, a fundamentalne dla Angulara:
– Typy – dla wstrzykiwania zależności (ang. dependency injection)
– Dekoratory – dla metadanych o modułach, komponentach, usługach
19
Architektura frameworka Angular
• Moduły (NgModule)
– Podstawowe „klocki”, z których budowana jest aplikacja
– Przynajmniej jeden „root module” (startowy) + opcjonalnie wiele „feature modules”
– Kontener na kod odpowiedzialny za pewien wycinek funkcjonalności
– Zawierają komponenty i dostawców usług (ang. service providers)
– Mogą importować funkcjonalność z / eksportować funkcjonalność dla innych modułów
– Rozszerzają koncepcję modułu z ES2015 (nie tylko kod)
• Komponenty (ang. component)
– Zarządzają fragmentami ekranu – tzw. widokami
– component = directive + template + presentation logic
• Usługi (ang. service)
– Dostarczają funkcji niezwiązanych bezpośrednio z widokami
– Mogą być wstrzykiwane do komponentów
20
Anatomia NgModule
• declarations – components, directives, pipes należące do NgModule
• exports – deklaracje dostępne dla wzorców komponentów innych NgModules
• imports – inne moduły, których eksporty wykorzystują wzorce komponentów w tym NgModule
• providers – dostawcy usług, których ten moduł kontrybuuje do aplikacji
• bootstrap – główny widok aplikacji („root component”)
– tylko w „root module”
21
NgModule – Przykład
import { NgModule } from '@angular/core'; ... import { AppComponent } from './app.component'; ... @NgModule({ declarations: [ AppComponent, StudentsComponent ], imports: [ BrowserModule, FormsModule, HttpClientModule, AppRoutingModule ], providers: [StudentService], bootstrap: [AppComponent] }) export class AppModule { }
*.ts
22
Anatomia komponentu
• selector – selektor CSS nakazujący Angiularowi wstawić instancję komponentu w danym miejscu we wzorcu HTML
• template / templateUrl – wzorzec HTML komponentu (inline / URL)
• styles / styleUrls – arkusze stylów CSS definiujące formatowanie komponentu (inline / URL)
• providers – dostawcy usług, wymaganych przez komponent
23
Komponent – Przykład
import { Component, OnInit } from '@angular/core'; import { Student } from '../student'; import { StudentService } from '../student.service'; @Component({ selector: 'app-students', templateUrl: './students.component.html', styleUrls: ['./students.component.css'] }) export class StudentsComponent implements OnInit { students: Student[]; constructor(private studentService: StudentService) { } ... ngOnInit() { this.getStudents(); } }
<h2>List of students</h2> <ul class="students"> <li *ngFor="let student of students"> <button title="Delete" (click)="delete(student)"> Delete </button> <a routerLink="/detail/{{student.id}}"> <span>{{student.index}}</span> </a> <span>{{student.lastName}}</span> <span>{{student.firstName}}</span> </li> </ul>
.students { list-style-type: none; }
*.ts *.html
*.css
24
Usługi (ang. services)
• Klasy implementujące funkcje potrzebne w aplikacji
– Pojedyncza usługa powinna odpowiadać za konkretną funkcjonalność
• Podział logiki między klasy komponentów i usług
– Klasa komponentu powinna zawierać logikę specyficzną dla widoku
• Właściwości i metody do wiązania danych z widoku
• Pośredniczenie między widokiem a logiką biznesową (modelem)
– Klasy usług nie powinny zależeć od widoków
• Komunikacja z backendem, walidacja, obsługa logu itd.
• Usługi są udostępniane komponentom i innym usługom przez wstrzykiwanie zależności (ang. dependency injection)
– Dekorator @Injectable oznacza klasy będące usługami oraz klasy i komponenty zależne od usługi
– Wstrzykiwanie realizowane przez typowany parametr konstruktora
– Dostawcy usług (ang. providers), typowo klasy usług, rejestrowani na poziomie modułów lub komponentów
25
Usługa i wstrzykiwanie zależności – Przykład
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class StudentService { constructor(private http: HttpClient) { } ... }
@NgModule({ declarations: [ AppComponent, StudentsComponent, ...], imports: [...], providers: [StudentService], bootstrap: [AppComponent] }) export class AppModule { }
import { Component, OnInit } from '@angular/core'; import { StudentService } from '../student.service'; @Component({ selector: 'app-students', templateUrl: './students.component.html', styleUrls: ['./students.component.css'] }) export class StudentsComponent implements OnInit { constructor(private studentService: StudentService) { } ... }
Usługa Rejestracja dostawcy usług w module
Wstrzyknięcie usługi w komponencie
26
Architektura Angular – Podsumowanie
Źródło: https://angular.io/guide/architecture
27
Routing
• Zadaniem routera w ramach frameworka Angular jest nawigacja między widokami
– Jest on wydzielonym, opcjonalnym modułem
– Pojedyncza instancja usługi routera w aplikacji
• Router Angulara bazuje na modelu nawigacji przeglądarki
– URL wprowadzony w pasku adresu prowadzi do wskazanego widoku
– Kliknięcie linku w aktualnym widoku powoduje przejście do innego
– Adres URL może zawierać parametry dla widoku
– Przyciski Back i Forward w przeglądarce nawigują po historii
• Obszar na stronie, w którym wyświetlane mają być różne komponenty zależnie od stanu routera, wskazuje się znacznikiem <router-outlet></router-outlet>
28
Konfiguracja routera w module routingu
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { StudentsComponent } from './students/students.component'; import { StudentDetailComponent } from './student-detail/student-detail.component'; const routes: Routes = [ { path: '', redirectTo: '/students', pathMatch: 'full' }, { path: 'students', component: StudentsComponent }, { path: 'detail/:id', component: StudentDetailComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
29
Wykorzystanie routingu w aplikacji
import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; ... @NgModule({ declarations: [...], imports: [..., AppRoutingModule], providers: [...], bootstrap: [...] }) export class AppModule { }
… <router-outlet></router-outlet> …
Import modułu routingu w głównym (root) module Router outlet we wzorcu HTML
głównego komponentu aplikacji
Linki prowadzące do komponentów (we wzorcach HTML komponentów)
<nav> <a routerLink="/students">Students</a> <a routerLink="/about">About</a> </nav>
<a routerLink="/detail/{{student.id}}"> <span>{{student.index}}</span> </a>
30
Routing – dostęp do bieżącej trasy
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; ... @Component({ selector: 'app-student-detail', templateUrl: './student-detail.component.html' }) export class StudentDetailComponent implements OnInit { student: Student; constructor( private route: ActivatedRoute, …) {} getStudent(): void { const id = +this.route.snapshot.paramMap.get('id'); this.studentService.getStudent(id) .subscribe(student => this.student = student); } }
Dostęp do trasy, która aktywowała bieżący komponent w elemencie <router-outlet>, np. w celu odczytania parametrów zawartych w URL
31
Klient HTTP Angulara (HttpClient)
• Dostarczony w celu obsługi współpracy z usługami backendowymi aplikacji dostępnym poprzez protokół HTTP
• Oparty na XMLHttpRequest dostępnym w przeglądarce
• Oferuje uproszczone API i dodatkową funkcjonalność:
– Typowane obiekty request i response
– Zgodność z interfejsem Observable
– Usprawniona i uproszczona obsługa błędów
• Wykorzystuje bibliotekę RxJS
– Reaktywne podejście do asynchronicznego przetwarzania
– Koncepcja „observables”
• Obecna również w innych obszarach funkcjonalnych Angulara
32
Wykorzystanie HttpClient (1/2)
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Student } from './student';
const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
@Injectable() export class StudentService {
private studentsApiUrl = 'http://localhost:5000/api/student';
constructor(private http: HttpClient) { }
getStudents(): Observable<Student[]> { return this.http.get<Student[]>(this.studentsApiUrl); }
updateStudent(student: Student): Observable<any> { const url = `${this.studentsApiUrl}/${student.id}`; return this.http.put(url, student, httpOptions); } }
Odwołania do serwerowego API w klasie usługi
33
Wykorzystanie HttpClient (2/2)
… students: Student[]; … constructor(private studentService: StudentService) { } … getStudents(): void { this.studentService.getStudents() .subscribe(students => this.students = students); }
Wykorzystanie usługi dostępowej do API w klasach komponentów
… student: Student; … constructor(private studentService: StudentService) { } … save(): void { this.studentService.updateStudent(this.student) .subscribe(() => this.goBack()); } …
34
Angular CLI
• Interfejs linii komend do:
– Tworzenia projektów
– Dodawania składników aplikacji do projektu
– Testowania, pakowania i instalowania aplikacji
• Instalacja:
– Wymagana współczesna wersja Node.js wraz z npm
– npm install -g @angular/cli
• Przykładowe komendy:
– Utworzenie projektu/aplikacji: ng new my-app
– Uruchomienie serwera (z poziomu katalogu aplikacji): ng serve --open
– Utworzenie komponentu (z poziomu katalogu aplikacji): ng generate component my-comp