V Angularu se často setkáváme s potřebou spravovat závislosti, které se mohou lišit mezi různými moduly, například při práci s konfiguracemi nebo při implementaci analytických a logovacích funkcí. Jedním z nástrojů, který nám Angular poskytuje, je zkratka poskytovatele s označením any, která umožňuje spravovat instanci závislosti specifickou pro daný modul a zároveň optimalizovat výkon aplikace pomocí schopnosti "tree-shaking", tedy odstranění nepoužívaného kódu při sestavení aplikace.

Tento poskytovatel deklaruje singleton závislost v rámci každého modulu, což znamená, že kořenový modul (obvykle AppModule) a všechny moduly, které jsou do něj staticky importovány, sdílejí jednu instanci této závislosti. U modulů, které jsou načítány lazy-load (na požádání), bude závislost poskytována v jiné instanci, která je specifická pro tento lazy-loaded modul. Každý další lazy-loaded modul a jeho závislosti budou mít svou vlastní instanci. Tento mechanismus je vhodný pro případy, kdy chceme mít více různých konfigurací nebo stavů pro jednotlivé moduly, aniž bychom došlo k nadbytečnému opakování kódu a závislostí.

Jedním z hlavních důvodů pro použití scope any je optimalizace kódu. Když závislosti poskytujeme pomocí tohoto poskytovatele, zůstávají tree-shakable, což znamená, že pokud nejsou použity, budou odstraněny při sestavení aplikace. Tento přístup se odlišuje od tradičních metod, jako je použití vzoru forRoot-forChild nebo poskytování závislostí v rámci Angularových modulů, kde závislost zůstává "bundlována" i v případě, že není přímo použita.

Příklad využití scope any v praxi

Zvažme konkrétní situaci, kdy máme backendovou konfiguraci, která se může lišit mezi různými lazy-loaded moduly. V tomto případě bychom mohli použít poskytovatele s označením any pro správu této konfigurace. Řekněme, že máme aplikaci pro správu bankovních účtů, kde každý modul může mít svou vlastní konfiguraci připojení k backendu. Některé moduly, například modul pro akcie, mohou mít specifické parametry pro připojení k backendu, zatímco jiný modul, jako například modul pro bankovní účty, bude sdílet obecnou konfiguraci.

Pro ilustraci můžeme definovat následující rozhraní pro konfiguraci backendu:

typescript
export interface BackendConfiguration {
readonly baseUrl: string; readonly retryAttempts: number; readonly retryIntervalMs: number; }

Následně vytvoříme službu BackendService, která bude využívat tento poskytovatel k načítání dat z backendu a implementaci logiky pro opakování pokusů v případě selhání:

typescript
@Injectable({ providedIn: 'any', }) export class BackendService { constructor( @Inject(backendConfigurationToken) private configuration: BackendConfiguration, private http: HttpClient ) {} get(url: string, options: BackendGetOptions = {}): Observable<any> { return this.http .get(`${this.configuration.baseUrl}/${url}`, { ...options, responseType: 'json' }) .pipe( retryWithDelay(this.configuration.retryAttempts, this.configuration.retryIntervalMs) ); } }

Důležitý aspekt tohoto přístupu je schopnost odlišit konfiguraci pro jednotlivé moduly a zároveň si zachovat možnost optimalizace (tree-shaking), což by nebylo možné u jiných metod poskytování závislostí, jako je právě vzor forRoot-forChild.

Jak to funguje v praxi

Pokud například v hlavním modulu aplikace (AppModule) definujeme výchozí konfiguraci pro připojení k backendu, všechny lazy-loaded moduly, které tuto konfiguraci nebudou explicitně přepisovat, ji automaticky použijí. Naopak, pokud nějaký modul, jako například SharesModule, potřebuje specifickou konfiguraci (například jiný URL endpoint a jiné parametry pro retry), poskytne tuto konfiguraci pomocí stejného tokenu pro injektování závislostí, což způsobí, že příslušná služba bude mít specifickou instanci pro tento modul.

Příklad konfigurace pro modul AppModule:

typescript
const backendConfiguration: BackendConfiguration = {
baseUrl: 'https://api01.example.com', retryAttempts: 4, retryIntervalMs: 250, }; @NgModule({ bootstrap: [AppComponent], imports: [BrowserModule, HttpClientModule, AppRoutingModule], providers: [ { provide: backendConfigurationToken, useValue: backendConfiguration }, ], }) export class AppModule {}

V případě modulu SharesModule, který má specifické požadavky na připojení k backendu, můžeme poskytnout odlišnou konfiguraci:

typescript
const backendConfiguration: BackendConfiguration = {
baseUrl: 'https://api02.example.com', retryAttempts: 7, retryIntervalMs: 100, }; @NgModule({ declarations: [SharesComponent], imports: [CommonModule, RouterModule.forChild(routes)], providers: [ { provide: backendConfigurationToken, useValue: backendConfiguration }, ], }) export class SharesModule {}

Co je důležité si zapamatovat

Scope any je určen pro velmi specifické případy, kdy potřebujeme mít odlišné konfigurace nebo stavy závislostí pro jednotlivé moduly. Je to velmi užitečné při práci s lazy-loaded moduly, kde se mohou konfigurace backendu nebo jiných služeb lišit mezi moduly, a zároveň nám tento přístup umožňuje využít optimální výkon aplikace díky tree-shakingu. Tento mechanismus by měl být volen opatrně, protože vyžaduje pečlivé řízení a pochopení, jak jednotlivé moduly a jejich závislosti spolupracují. Zároveň je důležité mít na paměti, že tato technika není vhodná pro všechny typy závislostí, ale spíše pro ty, které se skutečně liší mezi jednotlivými moduly a jsou závislé na specifických podmínkách, jako jsou konfigurace nebo analytické služby.

Jak používat Angular Compatibility Compiler a Angular Linker pro efektivní kompilaci knihoven Ivy

Angular Compatibility Compiler (ngcc) a jeho nástupce Angular Linker hrají klíčovou roli v procesu kompilace Angular aplikací, zejména při přechodu na nový kompilátor Ivy. Jak již bylo zmíněno, Angular Linker nahrazuje Angular Compatibility Compiler tím, že převádí částečně kompilované knihovny do formátu plně kompatibilního s Ivy před jejich zařazením do aplikace. Zatímco Angular Compatibility Compiler bude odstraněn ve verzi Angular vyšší než 12.2, stále je nezbytné pochopit, jak tyto nástroje fungují a jak je efektivně využívat.

V předchozích verzích Angularu, například od verze 9, bylo nutné spustit Angular Compatibility Compiler manuálně před tím, než jsme aplikaci postavili, otestovali nebo spustili vývojový server. Od té doby však Angular CLI začal tento proces spouštět automaticky, přičemž je stále možné spustit kompilátor ručně pro jemné ladění rychlosti kompilace.

Angular Compatibility Compiler se musí spustit alespoň jednou před každým z následujících kroků: spuštěním vývojového serveru, vykonáváním automatických testů nebo sestavením aplikace. Pokaždé, když nainstalujeme novou verzi Angular knihovny nebo přidáme novou knihovnu z balíčkového registru, musíme tento proces znovu provést. Pro zrychlení tohoto kroku je možné spustit Angular Compatibility Compiler jako součást postinstall hooku v repozitáři Git. Takto můžeme vyhnout čekání při příštím spuštění některé z výše uvedených akcí. Při běhu Angular Compatibility Compiler můžeme zároveň upravovat náš zdrojový kód, což optimalizuje workflow.

Pokud chceme ještě více optimalizovat tento proces, můžeme využít volbu --target, která umožňuje kompilaci specifického balíčku. To je užitečné zejména při práci s Angular Material nebo CDK, kde je možné, že se všechny podbalíčky těchto knihoven kompilují individuálně. Použití volby --use-program-dependencies zajistí, že kompilátor zpracuje pouze ty balíčky, které jsou skutečně importovány do naší aplikace, což významně zrychlí kompilaci.

Při ručním spouštění Angular Compatibility Compiler s volbami jako --first-only a --create-ivy-entry-points lze dosáhnout rychlejší kompilace. Volba --first-only zajistí, že se bude kompilovat pouze první rozpoznaný formát balíčku, což je často formát esm2015, který je obecně nejrychlejší pro převod z View Engine formátu na Ivy. Při použití volby --create-ivy-entry-points se Ivy-kompatibilní balíčky uloží do nového adresáře, což je výhodnější než přepisování původních souborů.

V případě verzí Angularu 9.0 nebo 11.1 je však doporučeno nevyužívat volbu --create-ivy-entry-points, protože v těchto verzích je rychlejší přímé přepsání balíčků.

Pro CI/CD workflow existuje několik specifických doporučení. Pokud je caching v CI procesu zapnutý pouze pro složku s balíčky a nikoli pro celé node_modules, bude potřeba spustit Angular Compatibility Compiler při každém běhu CI/CD procesu. V tomto případě je ideální použít následující postinstall hook pro co nejefektivnější kompilaci:

css
ngcc --first-only --properties es2015 module fesm2015 esm2015 browser main --create-ivy-entry-points

Tato kombinace parametrů zajišťuje, že se kompiluje pouze jeden balíčkový formát, který je optimální pro výkon, a preferuje se formát es2015 pro jeho rychlost kompilace. Pokud je to nutné, lze vynechat volbu --create-ivy-entry-points pro starší verze Angularu, které umožňují efektivnější přímou kompilaci.

Další metodou optimalizace je provádění kompilace v samostatném kroku mimo hlavní pracovní proces. To umožňuje lepší přizpůsobení konkrétním potřebám aplikace a lepší sledování, jaké balíčky byly kompilovány.

V případě, že používáme Angular Material nebo CDK, doporučuje se použít volbu --use-program-dependencies, která kompiluje pouze ty balíčky, které jsou ve skutečnosti použity v aplikaci. Tato metoda výrazně šetří čas při kompilaci, protože eliminuje zbytečnou kompilaci balíčků, které nejsou v aplikaci aktivně využívány.

Endtext

Jak správně inicializovat asynchronní závislosti pomocí Application Initializer v Angularu?

Inicializace asynchronních závislostí je v moderním vývoji aplikací v Angularu zásadním tématem, zejména v souvislosti s konfiguracemi, které musí být k dispozici ještě před tím, než se aplikace vůbec spustí. Jedním z elegantních řešení, které Angular poskytuje, je použití APP_INITIALIZER – mechanizmu, který umožňuje odložit samotné bootstrapování aplikace, dokud nebudou splněny definované asynchronní podmínky.

Typickým scénářem použití je dynamické načtení konfiguračních hodnot – například takzvaných "feature flags" – tedy příznaků určujících, jaké funkce mají být v dané instanci aplikace aktivní. Tato konfigurace může být uložena jako statický JSON soubor, například v adresáři /assets, a načtena pomocí služby HttpClient ještě před startem celé aplikace.

K tomu slouží specifická tovární funkce, která přijímá službu FeatureFlagService a instanci HttpClient, a vrací funkci, která provádí HTTP požadavek na příslušný konfigurační endpoint. Výsledek požadavku – mapu klíčů a jejich booleovských hodnot – se poté použije k interní konfiguraci dané služby. Celý tok je postaven na Observable, čímž je zajištěna kontrola nad ukončením asynchronní operace.

Tovární funkce se následně zaregistruje jako FactoryProvider v rámci APP_INITIALIZER, a Angular ji automaticky spustí ještě před samotným bootstrapem. Významné je, že tato inicializace může probíhat paralelně s dalšími inicializátory, čímž se významně zrychluje start celé aplikace.

Klíčovým přínosem tohoto přístupu je centralizace konfigurace v rámci služby. Místo statického importu konfiguračního objektu má vývojář k dispozici plnohodnotnou službu, která může být injektována do libovolné Angular komponenty či jiné služby, a která poskytuje metody jako isEnabled() pro kontrolu aktuálního stavu jednotlivých příznaků. Tento přístup posiluje princip oddělení odpovědností a podporuje lepší testovatelnost a údržbu kódu.

Zároveň však s sebou nese i určité nevýhody. Hlavní z nich je vyšší složitost ve srovnání s prostým importem statického konfiguračního objektu. Tato složitost je však ospravedlnitelná v případech, kdy je potřeba zajistit dynamickou nebo environmentálně závislou konfiguraci, případně když konfigurace závisí na jiných asynchronních procesech (např. autentizace, lokalizace).

Je důležité mít na paměti, že APP_INITIALIZER musí vždy vracet Promise nebo Observable, jinak Angular považuje daný initializer za synchrónní a nebude čekat na jeho dokončení. V praxi se často používá mapTo(undefined) nebo map(() => void 0), čímž se uzavírá datový tok bez dalšího zpracování.

Použití tohoto patternu se neomezuje jen na feature flags – je vhodný i pro jiné konfigurační scénáře, například načtení jazykových překladů, autentizačních tokenů, či bezpečnostních politik. V každém případě ale platí, že taková inicializace by měla být co nejrychlejší, neboť jinak dochází ke zbytečnému zpoždění startu aplikace.

Důležité je také dodržení správného pořadí závislostí. Služby používané v rámci APP_INITIALIZER musí být definovány tak, aby byly dostupné ještě před samotným bootstrapem, a zároveň nesmí být závislé na něčem, co samo vzniká až po bootstrapu – jinak hrozí cyklické závislosti nebo runtime chyby.

Když vývojář správně implementuje a strukturuje asynchronní inicializaci, získává do rukou nástroj s vysokou flexibilitou a kontrolou nad konfigurací aplikace. Umožňuje mu to efektivně reagovat na měnící se požadavky prostředí, přizpůsobovat chování aplikace bez potřeby rekompilace a udržovat čistý a konzistentní architektonický návrh.