Vytvoření aplikace, která správně reaguje na uživatelské požadavky a poskytuje okamžitou zpětnou vazbu, vyžaduje pečlivé zpracování uživatelských vstupů a správné rozvržení celkové struktury aplikace. Tento proces zahrnuje nejen design uživatelského rozhraní, ale i optimalizaci backendu pro různé typy vstupů, jakými jsou například název města nebo PSČ. Cílem je vytvořit aplikaci, která je efektivní, intuitivní a flexibilní.

Začněme nejprve tím, jak správně vytvořit vyhledávací komponentu ve frameworku Angular, který zajišťuje flexibilní a čistou architekturu.

Nejprve musíme přidat kontrolu formuláře pomocí Angular Forms. I když máme pouze jedno pole pro vstup uživatele, které nám umožní zadat název města nebo PSČ, doporučuje se vždy použití formuláře. Tento přístup poskytuje větší flexibilitu a umožňuje reagovat na události vstupu uživatele, přičemž využíváme možnosti validace a zpětné vazby. V Angularu máme dvě možnosti práce s formuláři: Template-driven forms a Reactive forms. Doporučeným přístupem je použití Reactive forms, protože poskytují silnější oddělení logiky a uživatelského rozhraní a usnadňují testování.

Ve frameworku Angular tvoříme formuláře pomocí modulů, které obsahují třídy jako FormControl, FormGroup, a FormArray. Pro naše účely použijeme ReactiveFormsModule, který umožňuje jednoduše reagovat na změny v kontrolovaných prvcích formuláře a využít metod pro ověřování a manipulaci s daty.

K vytvoření komponenty pro vyhledávání města nebo PSČ v naší aplikaci musíme následovat několik kroků. Nejprve definujeme novou komponentu, například CitySearchComponent, která bude obsahovat formulář pro zadání vstupu. Tento formulář bude reagovat na vstupy a odesílat je na backendovou službu, která vyhledá aktuální počasí.

Po vytvoření komponenty importujeme nezbytné moduly, jakými jsou MatFormFieldModule a MatInputModule z Angular Material, které zajišťují správné zobrazení a validaci formulářových prvků. Když má každý vstupní prvek svůj vlastní obal v komponentě mat-form-field, získáváme možnost optimalizovaného zobrazení pro uživatele, včetně podporovaných chování pro klávesnici, čtečky obrazovky a rozšíření prohlížeče.

Dalším krokem je zpracování uživatelského vstupu. Vytvoříme instanci FormControl pro vyhledávání města nebo PSČ, přičemž tento prvek bude reagovat na změny uživatelského vstupu. Bude důležité správně zachytit každý znak, který uživatel zadá, ale zároveň minimalizovat zátěž serveru, který by mohl být přetížen častými dotazy při každém stisku klávesy. K tomu používáme mechanismus „throttling“, který omezí počet dotazů na API na přijatelnou úroveň.

Když máme připravenou komponentu a správně ošetřený formulář, musíme upravit backendovou službu, která bude vyhledávat počasí na základě uživatelských vstupů. Do této služby přidáme možnost zpracování jak názvu města, tak PSČ. API služby OpenWeatherMap akceptuje parametry v URL, a proto můžeme přizpůsobit naši funkci pro získání počasí tak, aby přijímala jak textový vstup (název města), tak číselný vstup (PSČ). Pro dosažení maximální flexibility používáme union typy v TypeScriptu, které umožňují zpracovávat oba typy vstupů.

Důležitým prvkem, který je třeba mít na paměti, je optimalizace uživatelské zkušenosti. Nejde jen o technické detaily implementace, ale i o to, jak rychle a intuitivně aplikace reaguje na požadavky uživatele. Při implementaci vyhledávání je kladeno důraz na okamžitou zpětnou vazbu a plynulost interakce. Uživatelé by měli mít pocit, že aplikace je rychlá a citlivá, aniž by se cítili frustrováni čekáním na načítání.

Mimo to, uživatelům je nutné poskytnout možnost okamžitého ověření výsledků. Pokud aplikace vrátí více než jeden výsledek pro zadaný dotaz, je dobré implementovat mechanismus zpětné vazby, který jim nabídne jasné a srozumitelné volby pro další akce. To nejen zlepšuje použitelnost aplikace, ale také ji činí více přívětivou pro širokou veřejnost,

Jak správně pracovat s formuláři a interakcemi mezi komponentami v Angularu?

Ve vývoji aplikací je často nezbytné zajistit efektivní komunikaci mezi komponentami a správně validovat uživatelský vstup. To platí i pro aplikace postavené na frameworku Angular, kde je potřeba mít dobře strukturovaný kód, který je zároveň udržovatelný a přehledný. V této kapitole se zaměříme na dvě důležité oblasti: správu formulářů s využitím validátorů a komunikaci mezi komponentami.

Prvním a základním pravidlem pro práci s kódem je, že není dobrým zvykem nechávat v něm aktivní ladicí výpisy jako console.log. Tyto debugovací výpisy ztěžují čitelnost samotného kódu, což se následně promítá do vysokých nákladů na údržbu. I když jsou tyto výpisy zakomentovány, neměly by zůstávat v kódu. Čistý a srozumitelný kód je základem dlouhodobé udržitelnosti a efektivity vývoje.

Pokud jde o validaci vstupních hodnot, Angular poskytuje silnou podporu díky třídě FormControl, která je vysoce přizpůsobitelná. Tato třída umožňuje nastavit počáteční hodnoty, přidat validátory a poslouchat na změny při událostech jako blur, change nebo submit. Například, pokud chceme zajistit, aby uživatel zadal alespoň dva znaky do vyhledávacího pole, můžeme použít validátor pro minimální délku:

typescript
import { FormControl, Validators } from '@angular/forms';
search = new FormControl('', [Validators.minLength(2)]);

Tato úprava garantuje, že uživatel nebude moci odeslat formulář, pokud zadá pouze jeden znak. Poté můžeme upravit šablonu tak, aby se pod textovým polem zobrazila chybová zpráva, pokud je vstup neplatný:

html
@if (search.invalid) { Type more than one character to search }

Důležité je také přidat dostatečnou mezeru pro případ, že chybová zpráva bude delší, než je původně očekáváno. Pokud potřebujeme zobrazit více typů chyb, můžeme implementovat funkci, která se postará o zobrazení správné zprávy na základě typu chyby:

html
@if (search.invalid) {
{{getErrorMessage()}} }

A v komponentě definujeme metodu pro získání zprávy:

typescript
getErrorMessage() { return this.search.hasError('minLength') ? 'Type more than one character to search' : ''; }

Pokud chceme zajistit, aby vyhledávání nebylo prováděno při neplatném vstupu, můžeme upravit funkci vyhledávání, aby kontrolovala platnost vstupu před tím, než spustí službu pro vyhledávání:

typescript
this.search.valueChanges
.pipe(debounceTime(1000)) .subscribe((searchValue: string) => { if (!this.search.invalid) { ... } });

Důležité je, že místo základní kontroly na prázdný řetězec či undefined, využíváme robustní validaci prostřednictvím this.search.invalid, což nám umožňuje lépe řídit logiku aplikace.

Když se podíváme na alternativu k reaktivním formulářům, tedy na formuláře řízené šablonou, je třeba si uvědomit, že i když tato metoda může být na první pohled jednodušší a pohodlnější, má své nevýhody. Použití ngModel, které je kompatibilní s dřívější verzí AngularJS, automaticky připojuje komponentu k FormGroup. Ačkoliv se může zdát výhodné, že většina logiky je implementována přímo ve šabloně, tento přístup vyžaduje neustálou změnu mezi šablonou a kódem komponenty, což ztěžuje údržbu aplikace a její rozšiřitelnost.

Příkladem je implementace v komponentě řízené šablonou, kde jsou validace a logika vyhledávání součástí šablony:

html
@if(search.invalid) {
Type more than one character to search }

Tento přístup přináší výhody v jednoduchosti, ale zároveň ztrácíme flexibilitu a kontrolu nad logikou. Navíc se tento vzorec špatně integruje s validací vstupu a obsluhou chybových stavů, což může vést k neefektivním řešením.

Pokud jde o komunikaci mezi komponentami, Angular nabízí několik technik. Pro jednoduché případy můžeme využít globální události, což může zahrnovat použití služby, která bude spravovat stav aplikace. Nicméně, tato technika může vést k negativním důsledkům, pokud je příliš často využívána, například ke vzniku globálního stavu, který je těžko udržovatelný. V tomto případě je lepší se vyhnout nadměrnému používání globálních služeb, které mohou způsobit nežádoucí vedlejší efekty a komplikovat testování.

Pro efektivní komunikaci mezi komponentami je tedy lepší využít konkrétní metody, které zajistí oddělenost odpovědností. Dobrým přístupem je využití EventEmitter pro komunikaci mezi rodičovskou a dětskou komponentou, přičemž dětská komponenta by neměla vědět nic o rodičovské komponentě. Tento vzorec přispívá k vytváření opakovaně použitelných komponent, které jsou snadno testovatelné a udržovatelné.

Při práci s komponentami a validací je důležité mít na paměti, že efektivní návrh a organizace kódu přímo ovlivňuje údržbu a rozšiřitelnost aplikace. Příliš složité a těžkopádné struktury mohou vést k těžkostem při přidávání nových funkcí a opravách chyb.

Jak implementovat Firebase autentifikaci ve vaší aplikaci s použitím rozšíření abstraktního autentifikačního servisu

Pro implementaci autentifikace Firebase v aplikaci, která již používá abstraktní autentifikační servis, není třeba duplikovat kód. Vše, co je potřeba, je implementovat rozdíl mezi již zavedeným systémem autentifikace a metodami autentifikace Firebase. To znamená, že většina autentifikačních mechanismů může být zachována, přičemž se změní pouze konkrétní části, které se týkají samotné autentifikace.

V našem případě použijeme službu Firebase pro autentifikaci uživatelů pomocí jejich e-mailu a hesla. Firebase nabízí rozhraní pro přihlášení uživatelů a získání jejich autentifikačních tokenů, které slouží k následnému ověření přístupu. Vytvoříme službu, která tuto autentifikaci zrealizuje a transformuje uživatelské objekty na interní uživatelské objekty aplikace.

typescript
@Injectable()
export class FirebaseAuthService extends AuthService {
private afAuth: FireAuth = inject(FireAuth);
constructor() { super(); } protected authProvider(email: string, password: string): Observable<IServerAuthResponse> { const serverResponse$ = new Subject<IServerAuthResponse>();
signInWithEmailAndPassword(this.afAuth, email, password).then(
(res) => { const firebaseUser: FireUser | null = res.user; firebaseUser?.getIdToken().then( (token) => serverResponse$.next({ accessToken: token } as IServerAuthResponse), (err) => serverResponse$.error(err) ); }, (err) => serverResponse$.error(err) ); return serverResponse$; }
protected transformJwtToken(token: IJwtToken): IAuthStatus {
if (!token) { return defaultAuthStatus; } return { isAuthenticated: token.email ? true : false, userId: token.sub, userRole: Role.None, }; }
protected getCurrentUser(): Observable<User> {
return of(this.transformFirebaseUser(this.afAuth.currentUser)); } private transformFirebaseUser(firebaseUser: FireUser | null): User { if (!firebaseUser) { return new User(); } return User.Build({ name: { first: firebaseUser?.displayName?.split(' ')[0] || 'Firebase', last: firebaseUser?.displayName?.split(' ')[1] || 'User', }, picture: firebaseUser.photoURL, email: firebaseUser.email, _id: firebaseUser.uid, role: Role.None, } as IUser); } override async logout() { if (this.afAuth) {
await signOut(this.afAuth);
}
this.clearToken(); this.authStatus$.next(defaultAuthStatus); } }

Tato implementace ukazuje, jak integrovat autentifikaci Firebase bez nutnosti přepisovat celý systém. V zásadě se používá existující struktura, která se rozšiřuje o volání metod Firebase pro přihlášení a transformaci uživatelských dat.

Firebase standardně neimplementuje pojem role uživatele, což znamená, že role uživatele musí být definována a přidána samostatně. V našem příkladu používáme výchozí hodnotu Role.None, což je nutné změnit po implementaci funkce pro správu rolí v aplikaci. Tato správa rolí by měla být implementována prostřednictvím dalších funkcí Firebase a Firestore, kde budou uloženy detailní informace o uživatelských profilech. Po přihlášení uživatele by bylo vhodné provést další volání pro získání informací o roli uživatele.

Pokud chcete tento systém autentifikace použít v produkčním prostředí, je nezbytné přidat autentifikační režim do konfiguračního souboru aplikace:

typescript
{ provide: AuthService, useClass: FirebaseAuthService },

Dále je třeba přidat a správně nastavit nového uživatele v konzoli Firebase, abyste se mohli přihlásit a autentifikovat uživatele v reálném prostředí. Při přenosu citlivých informací, jako jsou e-maily nebo hesla, je nutné používat šifrované připojení (HTTPS), aby se předešlo bezpečnostním hrozbám.

Pro zajištění kvality aplikace je důležité nezapomenout na testování. Zde je ukázka, jak nastavit unit testy pro Firebase autentifikační službu:

typescript
describe('AuthService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ FirebaseAuthService, UiService, { provide: FireAuth, useValue: angularFireStub }, ], }); }); it('should be created', inject([FirebaseAuthService], (service: FirebaseAuthService) => { expect(service).toBeTruthy(); })); });

Takto nakonfigurované testy umožní ověřit funkčnost autentifikační služby ve vaší aplikaci.

Nakonec nezapomeňte na odstranění všech balíčků, které sloužily pro simulaci autentifikace před použitím skutečné autentifikace Firebase, jako například balíček fake-jwt-sign.

Pokud byste chtěli flexibilitu v autentifikačních metodách, můžete použít továrnu pro výběr poskytovatele autentifikace podle režimu prostředí. Vytvoříte enum pro různé metody autentifikace, což vám umožní v průběhu vývoje přepínat mezi různými metodami autentifikace podle potřeby.

typescript
export enum AuthMode { InMemory = 'In Memory', CustomServer = 'Custom Server', CustomGraphQL = 'Custom GraphQL', Firebase = 'Firebase', }

Pro každý režim nastavíte v prostředí hodnotu authMode, která určí, jaký typ autentifikace bude v aplikaci použit. Tento přístup umožňuje přepínání mezi různými metodami bez potřeby zásahů do samotné aplikace.