Při práci s asynchronními operacemi, jako jsou HTTP požadavky, je důležité nejen správně zpracovávat data, ale také strukturovat kód tak, aby byl přehledný, testovatelný a snadno rozšiřitelný. V tomto kontextu jsou principy SOLID, především principy otevřenosti a uzavřenosti, velmi užitečné. Tyto principy nám umožňují psát kód, který je flexibilní, robustní a snadno udržovatelný.

V předchozím příkladu jsme přepsali funkci getCurrentWeather, která nyní zahrnuje logiku pro zpracování různých typů parametrů pro hledání měst (např. město nebo PSČ), a zároveň umožňuje volitelný parametr pro zemi. Pro tento účel jsme použili HttpParams k vytvoření dynamických parametrů URL na základě typu hledaného vstupu, ať už jde o řetězec (město) nebo číslo (PSČ). Funkce následně volá helper metodu getCurrentWeatherHelper, která provádí vlastní HTTP požadavek.

Tento přístup je v souladu s principem jedné odpovědnosti (Single Responsibility Principle, SRP), což znamená, že každá funkce má pouze jednu konkrétní odpovědnost. Takto jsme oddělili samotnou logiku volání API a logiku zpracování parametrů, což usnadňuje údržbu a testování jednotlivých částí aplikace.

Metoda getCurrentWeatherHelper se stala otevřenou pro rozšíření, ale uzavřenou pro změny, což je dalším důležitým principem SOLID – open/closed principle. Tato metoda je nyní schopná pracovat s různými vstupy (například s různými parametry uriParams), ale její implementace se nebude muset měnit, pokud chceme přidat novou funkcionalitu, například získání počasí podle zeměpisné šířky a délky.

Pro přidání této nové funkcionality stačilo implementovat metodu getCurrentWeatherByCoords, která využívá getCurrentWeatherHelper k získání počasí na základě souřadnic. Tento způsob přidávání nových funkcí je velmi efektivní, protože minimalizuje potřebu úprav již existujícího kódu.

Dalším krokem je implementace nové služby ve formě rozhraní IWeatherService, které definuje metody pro získání aktuálního počasí podle města nebo PSČ, ale i podle souřadnic. Tato změna usnadňuje integraci s jinými částmi aplikace, protože všechny volání API jsou nyní centralizovány v tomto rozhraní, což zjednodušuje správu změn a testování.

Následně jsme se zaměřili na komponentu pro vyhledávání měst, kde jsme implementovali obsluhu vstupních změn uživatele. Uživatel může zadat název města, PSČ nebo kombinaci obou s volitelným kódem země. Funkce valueChanges sleduje změny ve vstupním poli a spouští volání metody služby pro získání počasí.

Důležitým vylepšením, které jsme přidali, je debounce, tedy zpoždění vyvolání požadavku po určitý časový interval (v tomto případě 1 sekundu). Tato technika zajišťuje, že server nebude přetížen příliš častými požadavky při každém stisknutí klávesy. Díky použití debounceTime z RxJS knihovny jsme dosáhli efektivního zpracování uživatelských vstupů, čímž jsme výrazně zlepšili uživatelský komfort a snížili zatížení serveru.

Další optimalizací je použití metody split pro rozdělení vstupního textu na jednotlivé části (město a kód země) a jejich následné ošetření pomocí metody trim pro odstranění nežádoucích mezer. Tímto způsobem můžeme snadno zpracovávat různé formáty vstupu, které uživatelé mohou zadávat.

Pro správnou implementaci těchto funkcí je kladeno důraz na efektivitu a čistotu kódu, což je zásadní pro dlouhodobou udržitelnost a rozšiřitelnost aplikace. Rozdělení odpovědností do menších, jednoduše testovatelných částí nám pomáhá psát aplikace, které jsou stabilní, snadno rozšiřitelné a efektivní.

V rámci návrhu uživatelského rozhraní bychom měli také přemýšlet o informačním zajištění uživatele. Pokud například uživatel zadá název města bez uvedení kódu země, měli bychom jej informovat o možnosti zadání volitelného kódu země. Může to být jednoduše zobrazena nápověda pod vyhledávacím polem, která uživatele upozorní na tuto možnost. Tento krok zlepší celkový dojem z aplikace a pomůže předejít nejasnostem.

Pro efektivní testování těchto funkcionalit je klíčové, aby každá metoda byla psána s ohledem na jednoduché a robustní testování. Oddělení logiky pro získání počasí od zbytku aplikace usnadňuje psaní jednotkových testů, které mohou ověřit správnost každé části aplikace nezávisle na ostatních.

Jak využít signály místo BehaviorSubject v aplikaci Angular?

Při vývoji moderních aplikací v Angularu se často setkáváme s potřebou efektivně spravovat stav a komunikaci mezi komponentami. V minulosti byla často využívána technologie jako BehaviorSubject, která je součástí knihovny RxJS. Nicméně, jak ukazuje novější přístup s využitím signálů (signals), existují jednodušší, rychlejší a levnější způsoby, jak dosáhnout podobné funkcionality s menší složitostí.

Signály, které jsou součástí nových funkcí JavaScriptu, se ukázaly jako vynikající nástroj pro zjednodušení aplikací. Na rozdíl od BehaviorSubject, které je založeno na asynchronním RxJS streamu, signály fungují synchronně, což může být výhodné v případech, kdy je požadováno rychlé a efektivní zpracování dat. I když se může zdát, že asynchronní zpracování je v moderním vývoji výhodnější, signály dokážou běžet neblokujícím způsobem, což znamená, že jsou efektivnější a výkonnější.

Přechod na signály může výrazně zjednodušit kód aplikace a snížit její velikost. Představme si, jak bychom mohli implementovat tuto změnu v aplikaci, která sleduje počasí. Prvním krokem je nahrazení stávajícího použití BehaviorSubject signálem.

Začneme tím, že v našem WeatherService nahradíme currentWeather$ novým signálem, jak je uvedeno níže:

typescript
import { signal } from '@angular/core';
export class WeatherService implements IWeatherService { readonly currentWeatherSignal = signal(defaultWeather); }

Následně musíme přidat funkci pro převod stavu na Promise a aktualizovat náš signál v případě změny dat:

typescript
import { firstValueFrom } from 'rxjs';
getCurrentWeatherAsPromise(searchText: string, country?: string): Promise {
return firstValueFrom(this.getCurrentWeather(searchText, country));
}
async updateCurrentWeatherSignal(searchText: string, country?: string): Promise { this.currentWeatherSignal.set(await this.getCurrentWeatherAsPromise(searchText, country)); }

Poté v CurrentWeatherComponent nahradíme stávající property current$ novým signálem:

typescript
export class CurrentWeatherComponent { readonly currentSignal: WritableSignal; constructor(private weatherService: WeatherService) {
this.currentSignal = this.weatherService.currentWeatherSignal;
} }

A nakonec v šabloně komponenty provedeme úpravy, abychom pracovali se signálem místo Observable:

html
@if (currentSignal(); as current) { ... }

Výhodou signálů je jejich nízká náročnost na paměť a vysoká rychlost vykonávání. Na rozdíl od BehaviorSubject, které je plně postavené na RxJS, signály umožňují efektivní správu dat mezi komponentami bez zbytečného zatížení aplikace. I když jsou signály jednodušší, umožňují také využití pokročilých technik, jako je použití vypočítaných signálů, což může přinést nové možnosti pro vývojáře.

Vzhledem k tomu, že signály jsou postavené na moderních funkcích JavaScriptu, dnes už není třeba používat plně vybavené frameworky jako Angular, React nebo Vue pro menší a středně velké projekty. Mnoho aplikací může být efektivně postaveno s využitím těchto jednodušších technologií, které poskytují lepší výkon a nižší náklady na údržbu.

Důležitým bodem při práci s signály je jejich správné nasazení v celém projektu. Pokud se rozhodneme pro tuto technologii, je důležité provést refaktoring aplikace na signály end-to-end, aby bylo možné využít všechny výhody, které signály nabízejí. Míchání různých paradigmat, jako je kombinace RxJS a signálů, může vést k nežádoucí složitosti a snížení výkonnosti aplikace.

V kontextu vývoje aplikací s využitím signálů je také zajímavé, jak dnes umělé inteligence jako ChatGPT mohou pomoci generovat kód a zjednodušit proces vývoje. I když stále existují oblasti, kde AI nedosahuje dokonalosti, například v oblasti designu, její schopnosti generovat základní funkčnost aplikací mohou výrazně urychlit vývojový cyklus.

V budoucnu bude pravděpodobně stále více aplikací využívat signály, protože technologie pro zpracování dat se stávají efektivnějšími a jednoduššími. To otevírá nové možnosti pro vývojáře, kteří hledají způsoby, jak optimalizovat své aplikace pro rychlost, výkon a snadnou údržbu.

Jak implementovat autentifikaci a autorizaci pomocí JWT v aplikaci

Implementace autentifikace a autorizace je nezbytnou součástí vývoje moderních webových aplikací. V tomto procesu je klíčovou součástí správa přístupových tokenů, obvykle ve formátu JWT (JSON Web Token), které umožňují ověřování uživatele bez nutnosti neustálého zasílání přihlašovacích údajů. Tento článek se zaměřuje na to, jak správně implementovat autentifikační službu (AuthService) s použitím JWT, jak ji testovat a jak řešit různé scénáře, jako je neplatný token nebo nedostatečná autorizace uživatele.

Začněme základními funkcemi správy tokenu v autentifikační službě. První krok je implementovat metody pro nastavení, získání a odstranění tokenu:

typescript
protected setToken(jwt: string) {
this.cache.setItem('jwt', jwt);
}
getToken(): string { return this.cache.getItem('jwt') ?? ''; } protected clearToken() {
this.cache.removeItem('jwt');
}

Důležitým krokem je zavolat tyto metody při přihlášení a odhlášení uživatele. Při přihlášení je potřeba uložit nový token, zatímco při odhlášení by měl být token odstraněn. Příklad implementace metody login a logout může vypadat takto:

typescript
login(email: string, password: string): Observable { this.clearToken(); const loginResponse$ = this.authProvider(email, password) .pipe( map(value => { this.setToken(value.accessToken); const token = decode(value.accessToken); return this.transformJwtToken(token); }),
tap(status => this.authStatus$.next(status))
); }
logout(clearToken?: boolean) { if (clearToken) { this.clearToken(); } setTimeout(() => this.authStatus$.next(defaultAuthStatus), 0); }

Důležité je si uvědomit, že každý následný požadavek bude obsahovat JWT token v hlavičce požadavku, což umožňuje serveru ověřit, zda je uživatel autentifikován. Nicméně samotné ověření tokenu nestačí – je třeba ještě ověřit, zda má uživatel také příslušná oprávnění pro přístup k požadovaným datům. To znamená, že server provádí další kontrolu na základě databázového dotazu.

Pokud uživatel nemá potřebná oprávnění pro požadovanou akci, například pokud zaměstnanec chce získat seznam všech uživatelů, AuthService vrátí chybu 403 (Forbidden), což znamená, že uživatel nemá přístup. Pokud token vypršel, server vrátí chybu 401 (Unauthorized), což je signál pro klienta, aby vyzval uživatele k novému přihlášení.

Při práci s JWT je důležité mít na paměti, že bezpečnost je zajištěna především na straně serveru. Klientská strana se zaměřuje především na to, aby poskytla uživatelsky přívětivou interakci s bezpečnostními mechanismy, jako je například automatické přihlášení po vypršení platnosti tokenu. Uživatel by měl být informován o nutnosti přihlášení a jeho workflow by měl být co nejméně přerušen.

Vytváření autentifikační služby v paměti

Pro účely vývoje a testování aplikace je možné vytvořit i jednoduchou autentifikační službu, která nebude ve skutečnosti komunikovat se serverem, ale pouze simuluje proces autentifikace a generování tokenů. Taková služba je užitečná při testování chování aplikace před implementací reálné autentifikace. Příklad implementace takovéto služby může vypadat následovně:

typescript
@Injectable({ providedIn: 'root' })
export class InMemoryAuthService extends AuthService {
constructor() { super(); console.warn('Používáte InMemoryAuthService. Nepoužívejte tuto službu v produkci.'); } protected authProvider(email: string, password: string): Observable { email = email.toLowerCase(); if (!email.endsWith('@test.com')) { return throwError('Chyba při přihlášení! Email musí končit @test.com.'); } const authStatus = { isAuthenticated: true,
userId: this.defaultUser._id,
userRole: email.includes('manager') ? Role.Manager : Role.None, }; const authResponse = { accessToken: sign(authStatus, 'secret', { expiresIn: '1h', algorithm: 'none' }), }; return of(authResponse); } protected transformJwtToken(token: IAuthStatus): IAuthStatus { return token; } protected getCurrentUser(): Observable {
return of(this.defaultUser);
} }

V tomto případě authProvider simuluje serverovou autentifikaci tím, že vrací falešný token, který je podepsán knihovnou fake-jwt-sign. Tento přístup je vhodný pro prototypování nebo testování základní funkcionality bez skutečné serverové logiky.

Při vytváření autentifikační služby v paměti je nutné mít na paměti, že tento přístup by neměl být použit v produkčním prostředí, protože falešné tokeny nejsou bezpečné a nesplňují požadavky na bezpečnost v reálných aplikacích.

Dalším krokem je implementace jednoduché funkce pro přihlášení uživatele, která umožní simulovat přihlášení uživatele, například jako manažera, prostřednictvím pevně daných přihlašovacích údajů. To usnadňuje testování aplikace bez nutnosti implementovat plně funkční uživatelské rozhraní pro přihlášení.

Pokud jde o reálné nasazení autentifikace, je důležité správně nakonfigurovat serverovou část, která bude správně validovat tokeny a kontrolovat oprávnění uživatele. Kromě toho, že samotná autentifikace musí být bezpečná, měla by být rovněž dobře integrována s uživatelským rozhraním tak, aby uživatelé měli co nejlepší zkušenosti při práci s aplikací.

Jak efektivně spravovat stav v Angular aplikacích: SignalStore, Akita a Elf

V oblasti správy stavu pro Angular aplikace existuje několik robustních knihoven, které mohou pomoci zjednodušit složité procesy spojené se správou dat a asynchronními operacemi. Kromě populárního NgRx ekosystému, existují i alternativy jako Akita a Elf, které se zaměřují na zjednodušení práce s reaktivními daty. Tento text se zaměří na SignalStore a SignalState, součásti NgRx, a porovná je s alternativami jako Akita a Elf, které mohou být v určitých případech výhodnější.

SignalStore představuje robustní systém pro správu stavu, který kombinuje výhody známých knihoven jako NgRx/Store a NgRx/ComponentStore. SignalStore umožňuje efektivní správu stavu aplikace, zatímco SignalState přináší zjednodušený způsob správy stavu uvnitř Angular komponent a služeb. S SignalState můžete spravovat stav přímo v komponentách, což eliminuje potřebu samostatně spravovaných signalových vlastností v službách. Dále, rxMethod poskytuje flexibilitu pro práci s Observable, což je užitečné pro integraci se stávajícím kódem.

SignalStore se ukazuje jako užitečné řešení pro složitější aplikace, které potřebují robustní správu stavu, zatímco SignalState je ideální pro jednodušší případy, kde stačí spravovat stav přímo v rámci komponenty nebo služby bez zbytečné složitosti.

Kromě SignalStore a SignalState jsou tu i další populární knihovny, jako je Akita a Elf, které mají své specifické výhody a použití v závislosti na požadavcích aplikace.

Akita je řešení pro správu stavu, které kombinuje principy Flux, Redux a RxJS do modelu Observable Data Store, favorizující imutabilitu a streaming dat. Akita klade důraz na jednoduchost, minimalizuje boilerplate kód a nabízí přístupné rozhraní pro vývojáře všech úrovní. Tento nástroj je obzvlášť silný v situacích, kdy potřebujete vestavěnou správu entit a pokročilé možnosti, jako je historie stavu, serverová stránkování nebo podpora pro objektově orientované přístupy. Akita je postavena na RxJS a BehaviorSubject a nabízí specializované třídy pro správu stavu jako Store, Query a EntityStore.

Elf je novější knihovna pro správu stavu v Angularu, která se zaměřuje na zjednodušení reaktivity a mutací stavu pomocí minimalistického API, které klade důraz na ergonomii a snadné použití. Elf využívá moderní RxJS vzory pro správu stavu, což umožňuje jemnou kontrolu nad změnami stavu a reaktivitou. Je navržen tak, aby byl lehký a přehledný, což ho činí ideálním pro aplikace, které potřebují jednoduchou alternativu k rozsáhlejším knihovnám, jako je NgRx. Elf podporuje pokročilé funkce, jako je cache požadavků, historie stavu pro jednoduchou funkčnost undo/redo, a pokročilé stránkování, které optimalizuje získávání a cachování paginovaných dat. Navíc je modulární a plně tree-shakable, což znamená, že si můžete vybrat pouze ty funkce, které skutečně potřebujete.

Pokud máte specifické požadavky na práci s entitami nebo chcete implementovat podporu offline aplikací, Elf může být výbornou volbou díky vestavěné podpoře pro synchronizaci stavu mezi záložkami prohlížeče.

Vzhledem k tomu, že každé z těchto řešení má své specifické silné stránky, je důležité zvážit, které z nich bude nejvhodnější pro konkrétní projekt. Například, pokud vaše aplikace vyžaduje pokročilé operace CRUD s entitami a historii stavu, Akita by mohla být správnou volbou. Naopak, pokud hledáte jednoduchost a flexibilitu s možností optimalizace výkonnosti, Elf by mohl být ideální.

Důležitou součástí každé aplikace, zejména v případě rozsáhlejších systémů s více komponentami, je efektivní správa stavu asynchronních operací, například při práci s API. V takovém případě může být velmi užitečné nasazení globálního spinneru pro indikaci načítání dat. Globální spinner může sloužit jako rychlé řešení pro vizuální indikaci, že aplikace čeká na dokončení operace, nicméně v rozsáhlejších aplikacích může způsobit problémy, pokud jsou souběžně prováděny více API volání. V těchto případech je lepší využívat lokalizované spinner-y nebo jiný typ indikace.

Pro efektivní správu stavů a zajištění hladkého UX je tedy důležité zvolit správný přístup k práci se stavem, závislosti na velikosti aplikace a složitosti logiky asynchronních operací. Ať už se rozhodnete pro SignalState, SignalStore, Akitu nebo Elf, výběr by měl vždy vycházet z konkrétních požadavků vašeho projektu a týmového workflow.