V Angularu jsou poskytovatelé závislostí klíčovým nástrojem pro správu a sdílení závislostí mezi komponentami a moduly aplikace. V tomto kontextu se budeme zaměřovat na dva důležité typy poskytovatelů: any a platform, které jsou součástí Angular Ivy, novější verze Angularu. Tyto typy poskytovatelů umožňují flexibilní správu závislostí a jejich správné rozdělení mezi moduly a aplikace. Pochopení jejich fungování je zásadní pro efektivní strukturování Angular aplikací, zejména v případech složitějších aplikací nebo mikrofront-endů.

any provider scope

Scope any poskytovatelů závislostí v Angularu umožňuje vytvoření instance závislosti specifické pro modul, což znamená, že závislosti se používají pouze v rámci určitého modulu. Tento přístup je užitečný v případech, kdy je potřeba izolovat stav nebo konfiguraci pro jednotlivé moduly, například pro různé části aplikace, které nevyžadují sdílení stavu mezi sebou.

Příklad ukazuje dvě komponenty: SharesComponent a BankAccountsComponent, které používají stejný typ závislosti – BackendService. Obě komponenty si injektují tuto službu, ale každá komponenta má svou vlastní instanci této služby, která je specifická pro daný modul. V případě SharesComponent závislost používá konfiguraci poskytovanou v modulu SharesModule, zatímco BankAccountsComponent používá konfiguraci poskytovanou v kořenovém modulu aplikace, pokud není v modulu BankAccountsModule specifikována žádná vlastní konfigurace.

Tento způsob poskytování závislostí je vhodný pro scénáře, kdy je potřeba mít různé konfigurace pro různé moduly aplikace, ale zároveň chceme zachovat izolaci mezi jednotlivými moduly. Typickým příkladem může být situace, kdy různé části aplikace potřebují různé konfigurace pro připojení k backendu nebo pro správu stavu.

platform provider scope

Scope platform je konceptuálně jednodušší než scope any. Tento typ poskytovatele vytváří jedinou instanci závislosti, která je sdílená napříč více Angular aplikacemi, které jsou bootstrapovány na stejné stránce. Tento přístup se často používá v případech, kdy je potřeba sdílet určitou závislost mezi různými mikrofront-endy nebo webovými komponentami, které byly vytvořeny pomocí Angular Elements.

Platform scope poskytuje globální závislost, která je přístupná všem aplikacím nebo komponentám na stránce, což umožňuje jejich vzájemnou interakci bez nutnosti opětovného vytváření nebo správy jednotlivých instancí. Příkladem takové závislosti může být například Storage API, které je použito ve více aplikacích na stejné stránce pro práci s webovým úložištěm.

Příklad ukazuje dvě samostatné aplikace: DocumentsApp a MusicApp, které obě závisí na storageToken, což je závislost reprezentující localStorage. Ačkoli každá aplikace má svůj vlastní kořenový injektor, storageToken je poskytováno v platform scope, což znamená, že sdílí jednu instanci napříč oběma aplikacemi.

Jak správně využít scopes závislostí

Pochopení rozdílu mezi těmito dvěma typy scopes – any a platform – je klíčové pro návrh správné architektury aplikace. Any provider scope je ideální pro případy, kdy chcete izolovat závislosti mezi jednotlivými moduly a umožnit jim používat různé konfigurace, zatímco platform provider scope je vhodný pro situace, kdy potřebujete sdílet jednu instanci mezi více aplikacemi nebo komponentami.

V praxi může být kombinace těchto dvou typů scopes užitečná pro aplikace, které se skládají z různých modulů a aplikací běžících na jedné stránce. Například v mikrofront-endové architektuře může každý mikrofront-end používat svůj vlastní any scope pro modulární závislosti, zatímco sdílené služby, jako například ukládání dat, mohou být poskytovány prostřednictvím platform scope.

Je důležité také zmínit, že využívání těchto poskytovatelů umožňuje lépe řídit životní cyklus závislostí v aplikaci. Závislosti poskytované v platform scope jsou obvykle sdílené mezi více aplikacemi na stránce a tím pádem mají jednu instanci, což může výrazně zjednodušit správu těchto závislostí, například při práci s globálními stavovými proměnnými.

Co je důležité pochopit?

Kromě uvedeného rozdílu mezi any a platform scopes je klíčové pochopit, jak tyto možnosti ovlivňují výkon aplikace. Správně navržená architektura závislostí může přispět k lepší optimalizaci výkonu, protože umožňuje efektivněji spravovat instance služeb a závislostí mezi moduly. Zatímco any scope může pomoci udržet oddělení mezi moduly a tím přispět k modularitě aplikace, platform scope může zjednodušit sdílení společných služeb mezi aplikacemi.

Pro vývojáře je také důležité pochopit, jak správně využívat poskytovatele v závislosti na specifických potřebách aplikace. Při rozhodování, zda použít any nebo platform scope, by měli zvážit jaký typ sdílení závislostí je pro jejich aplikaci nejvhodnější a jaký má vliv na modularitu, výkon a údržbu aplikace v dlouhodobém horizontu.

Jak optimalizovat práci s Angular Compatibility Compiler v CI/CD prostředí

V případě, že pracujeme s více aplikacemi v monorepo workspace, je klíčové optimalizovat procesy kompilace, zejména v rámci CI/CD pracovního toku. Pokud máme několik aplikací a pouze některé z nich používají Angular CDK nebo Angular Material, je možné zaměřit se pouze na ty subbalíčky, které jsou skutečně potřeba pro konkrétní aplikaci. Tato optimalizace se projevuje především ve zrychlení kompilace, což šetří cenný čas při každém testování nebo budování aplikace.

Představme si například, že máme dvě aplikace ve stejném monorepo workspace. Jedna využívá knihovnu komponent Bootstrap UI, zatímco druhá pracuje s Angular Material. V případě aplikace používající Bootstrap bychom v rámci CI/CD procesu použili následující příkaz po instalaci závislostí:

css
npx ngcc --first-only --properties es2015 module fesm2015 esm2015 browser main --create-ivy-entry-points --tsconfig projects/bootstrap-app/tsconfig.app.json --use-program-dependencies

Tento příkaz zaměřuje kompilaci pouze na aplikaci Bootstrap tím, že předává cestu k TypeScript konfiguraci konkrétní aplikace. Dále použití přepínače --use-program-dependencies znamená, že nebudou kompilovány žádné zbytečné subbalíčky Angular Material, což šetří výpočetní čas na serveru.

Podobnou optimalizaci můžeme provést i v případě aplikace, která používá Angular Material. Příkaz bude vypadat následovně:

css
npx ngcc --first-only --properties es2015 module fesm2015 esm2015 browser main --create-ivy-entry-points --tsconfig projects/material-app/tsconfig.app.json --use-program-dependencies

V tomto případě se opět zaměříme pouze na ty subbalíčky Angular Material, které jsou skutečně importovány do aplikace. Pokud bychom se rozhodli neprovádět tuto optimalizaci, CI/CD server by musel zkompilovat všechny balíčky Angular Material, což by znamenalo zbytečnou zátěž.

Tato technika zrychluje kompilaci a testování aplikací v prostředí CI/CD, což je obzvlášť důležité při práci s rozsáhlými monorepo workspace a když používáme knihovny, které obsahují velké množství subbalíčků. Vzhledem k tomu, že ne všechny části knihovny jsou vždy potřebné, tento přístup pomáhá výrazně optimalizovat výkon bez ztráty funkčnosti.

Důležitým faktorem, který je třeba mít na paměti, je správné nastavení a pochopení struktury aplikace v rámci monorepo workspace. I když výše zmíněné optimalizace výrazně šetří čas, je důležité zajistit, že konfigurace TypeScriptu a závislosti jsou správně nastaveny, aby předešlé kompilace nevedly k nesouladu mezi aplikacemi a jejich závislostmi.

Přechod z kompilátoru View Engine na Angular Ivy a optimalizace Angular Compatibility Compileru přinášejí výrazné výhody nejen z hlediska výkonnosti, ale i dlouhodobé údržby aplikací. Je důležité správně řídit a optimalizovat CI/CD pracovní tok, protože tím lze eliminovat většinu problémů spojených s časem kompilace a rozsahem závislostí, což zefektivňuje procesy vývoje a testování.

Jak správně používat operátory pro nulové hodnoty a soukromé členy tříd v JavaScriptu a TypeScriptu

V moderním vývoji softwaru se stále více prosazují pokročilé funkce, které usnadňují práci s hodnotami a zpřesňují kontrolu nad přístupem k datům. Dva takové nástroje, které byly přidány do jazyka TypeScript, jsou operátory pro práci s nulovými hodnotami – nullish coalescing (??) a optional chaining (?.), stejně jako novinka v oblasti tříd – soukrmé členy tříd. Každý z těchto nástrojů poskytuje vývojářům jasnější a bezpečnější způsob, jak pracovat s hodnotami, které mohou být null nebo undefined, a jak správně izolovat data v rámci tříd, čímž se zlepšuje struktura a čitelnost kódu.

Nullish coalescing operátor

Operátor ??, známý také jako nullish coalescing, byl představen ve verzi TypeScript 3.7 a poskytuje efektivní způsob, jak pracovat s hodnotami, které mohou být null nebo undefined. Na rozdíl od běžného operátoru logického OR (||), který ignoruje všechny hodnoty považované za „falsy“ (například 0, false, prázdný řetězec '', nebo NaN), operátor ?? reaguje pouze na hodnoty null a undefined. Tento rozdíl může vyřešit skrytý problém, který může vzniknout při použití || pro přiřazení výchozích hodnot.

Například, pokud máme funkci, která nastavuje hodnoty pro hlasitost a časovač spánku:

typescript
function changeSettings(settings: { sleepTimer: number; volume: number; }): void {
const volumeSetting = settings.volume || 0.5; sendVolumeSignal(volumeSetting); const sleepTimerSetting = settings.sleepTimer || 900; sendSleepTimerSignal(sleepTimerSetting); }

V tomto příkladu bude hodnoty přiřazené pomocí operátoru || vždy nahrazeny výchozí hodnotou, pokud je nastavený jakýkoliv „falsy“ výraz, což zahrnuje i 0 pro ztlumení zvuku. To může vést k neočekávaným výsledkům.

Použití operátoru ?? tento problém vyřeší:

typescript
function changeSettings(settings: { sleepTimer: number; volume: number; }): void { const volumeSetting = settings.volume ?? 0.5; sendVolumeSignal(volumeSetting); const sleepTimerSetting = settings.sleepTimer ?? 900; sendSleepTimerSignal(sleepTimerSetting); }

Tato změna zaručí, že pokud bude hodnota volume rovna 0, nebude přepsána na výchozí hodnotu 0.5 a zařízení bude skutečně ztlumeno.

Práce s hodnotami nullish

Ve světě JavaScriptu a TypeScriptu existují dvě hodnoty, které označují „prázdno“ nebo „neexistující“: null a undefined. Tyto hodnoty mají specifické využití a mohou způsobit nejednoznačnosti v některých API. Například v formátu JSON není hodnota undefined vůbec zahrnuta, čímž se šetří šířka pásma při přenosu dat a prostor při jejich ukládání. Naopak některé servery nebo API používají hodnotu null pro označení neexistujících dat. Takové odlišnosti mohou vyvolávat chyby nebo nedorozumění, pokud programátoři nesprávně manipulují s těmito hodnotami.

Optional chaining

Další užitečný nástroj, který byl zaveden ve TypeScript 3.7, je optional chaining (?.). Tento operátor umožňuje bezpečně přistupovat k vlastnostem objektů, které mohou být null nebo undefined, aniž by došlo k vyvolání chyby. Dříve bylo nutné provádět kontrolu typu před přístupem k těmto vlastnostem. S ?. to lze provést jednoduše:

typescript
const jsonString = user?.profile?.address?.city;

Pokud některá z částí řetězce user, profile nebo address nebude definována, výraz vrátí undefined namísto chyby.

Kombinací operátorů ?. a ?? můžeme ještě více zvýšit robustnost našeho kódu. Například pokud máme objekt, který může obsahovat volitelné možnosti, můžeme použít následující přístup:

typescript
function prettyPrint(value: T, options?: { spaces?: number }): void {
const spaces = options?.spaces ?? 2; const json = JSON.stringify(value, undefined, spaces); console.log(json); }

Tato konstrukce zajistí, že i v případě, kdy není hodnota spaces definována, bude použita výchozí hodnota 2. Tento přístup eliminuje potenciální chyby spojené s nejednoznačností hodnot.

Soukromé členy tříd v TypeScriptu

Ve verzi TypeScript 3.8 byly přidány soukrmé členy tříd, které poskytují větší úroveň ochrany dat než běžné přístupové modifikátory (private, protected, public). Tyto soukromé členy jsou zcela nepřístupné zvenčí třídy, a to i během běhu aplikace. Tento přístup je silnější než standardní private modifikátor, který je vyhodnocován pouze během kompilace.

V následujícím příkladu je vytvořena třída Person, která obsahuje soukromý člen #name, který je dostupný pouze prostřednictvím veřejného getteru:

typescript
class Person { #name: string; get name() { return this.#name; } constructor(name: string) { this.#name = name; } }

Pokud by třída Employee měla možnost dědit z třídy Person, přístup k soukromému členu #name by byl stále omezen pouze na metody a vlastnosti třídy. Tento přístup k ochraně dat je silně zaměřen na enkapsulaci, což vede k bezpečnějšímu kódu.

V případě dědění třídy a použití soukromých členů nelze tento článek přistupovat nebo modifikovat mimo rámec definovaných metod. Toto použití soukromých polí zaručuje, že třídy správně oddělují interní logiku a data, která nejsou určena pro přímý přístup z vnějších částí aplikace.

Zároveň, při používání soukromých členů tříd v TypeScriptu je důležité, že výstup kódu musí být kompilován pro podporu minimálně ECMAScript 2015, protože jinak bude výstup obsahovat nekompatibilní syntaxi, která používá WeakMap.

Jak psát testy v Angularu s přesnou typovou kontrolou a proč na tom záleží

Ve světě testování Angular aplikací se knihovna TestBed stala klíčovým nástrojem, bez kterého se dnes neobejde žádný seriózní vývojář. Její role spočívá především v konfiguraci testovacích modulů, poskytování závislostí a simulaci aplikačního prostředí. S příchodem Ivy vykreslovacího enginu však došlo k významnému posunu směrem ke striktnějšímu typování – a to zejména při řešení závislostí pomocí nového API TestBed.inject.

Tradiční metoda TestBed.get byla v minulosti hojně využívána, ale její nevýhodou je, že vždy vrací hodnotu typu any. To znamená, že TypeScript zde není schopen poskytnout dostatečně silnou typovou kontrolu a chyby, které mohou nastat při nesprávném použití závislostí, se projeví až za běhu. Ve chvíli, kdy se proměnná typu MyService naplní instancí TheirService, TypeScript nebude varovat, dokud se skutečně nespustí test využívající metodu, která v dané třídě neexistuje.

Oproti tomu TestBed.inject umožňuje compileru typ správně odvodit bez nutnosti explicitní deklarace. Přiřazením například const service = TestBed.inject(MyService) TypeScript sám vyvodí, že service má typ MyService, čímž se eliminuje riziko tichých typových chyb. Pokud se omylem použije jiná závislost, kompilátor na to upozorní ještě před spuštěním testů.

Tento rozdíl nabývá na významu zejména v komplexních scénářích, kde se závislosti sdílejí napříč několika testovacími sadami. U metody TestBed.get může chyba zůstat skryta až do momentu, kdy je volána metoda neexistující na injektovaném objektu, zatímco TestBed.inject včas varuje při konfiguraci setup hooku pomocí beforeEach.

Z pohledu API mají tyto metody podobnou signaturu, ale liší se v návratových typech. TestBed.get vrací any, zatímco TestBed.inject vrací typ T, který je odvozen z předaného typu nebo injekčního tokenu. Token může být konkrétní třída (Type), známý InjectionToken nebo abstraktní třída (AbstractType). Poslední varianta je užitečná například při dědičnosti nebo při vytváření lehkých injekčních tokenů bez konkrétní implementace.

Parametr notFoundValue slouží jako záložní hodnota v případě, že požadovaná závislost není k dispozici. Pokud jej neuvedeme a závislost chybí, vyvolá Angular runtime chybu. Pokud jej zadáme (např. null), je možné v kombinaci s příznakem InjectFlags.Optional zajistit, že injekce proběhne i bez přítomné závislosti.

Zde je potřeba upozornit na paralelu mezi TestBed.inject a metodou Injector#get. Obě mají identickou typově bezpečnou signaturu, ale druhá z nich si zachovala také starší, deprecated podobu, která umožňuje jako token použít i prostý text nebo číslo. Tyto typy však nejsou podporovány TestBed.inject, a jakmile bude metoda get zcela odstraněna, nebude již možné s těmito typy závislostí pracovat v testech.

Význam typové bezpečnosti se tedy ukazuje nejen při konfiguraci testovacích modulů, ale i při navrhování celkového testovacího prostředí. Použití TestBed.inject je tak více než jen syntaktickou preferencí – je klíčem k robustním, udržitelným a předvídatelným testům, které mohou odhalit chyby ještě před tím, než se vůbec spustí.

Je také důležité zmínit, že typová bezpečnost není jen otázkou přísnosti, ale i produktivity. Méně runtime chyb znamená méně ztraceného času při ladění. Lepší inferování typů zároveň zvyšuje efektivitu při psaní testů – vývojář má okamžitou podporu IDE a může se více soustředit na samotnou logiku testů než na hledání chyb v závislostech.

Dalším významným prvkem je práce s vlastním FakeMatIconRegistry, což umožňuje testování komponent využívajících SVG ikony z Angular Material. Simulace těchto ikon bez nutnosti jejich načítání z reálného zdroje je klíčová pro rychlé a spolehlivé testy. Při správném nastavení testovacího prostředí tak lze bez problémů ověřovat vzhled, reakce a dostupnost UI prvků i bez připojení ke skutečným zdrojům.

Vývojáři by také měli mít na paměti, že jakékoli přechody mezi API metodami v testech, zejména ty související s typováním, nejsou pouze technickým detailem. Jsou to rozhodnutí, která ovlivňují celkovou kvalitu projektu. Přechod na TestBed.inject by měl být samozřejmostí ve všech nových i refaktorovaných testovacích sadách.