Performance Strategies
ngx-theme-stack offers two ways to handle the initial theme application to prevent that annoying white flash.
Available Strategies
Section titled “Available Strategies”1. Critters (Default)
Section titled “1. Critters (Default)”Best for most apps (CSR, SSR, and SSG). It uses hidden markers to trick the Angular builder into inlining all your theme CSS variables directly in the HTML <head>.
Result: Zero network requests for CSS variables. The theme is applied even before the browser starts downloading external CSS files.
2. Blocking
Section titled “2. Blocking”Loads themes.css as a traditional render-blocking stylesheet. The browser blocks rendering until it’s downloaded, so there’s no flash — but it requires one network round-trip (then HTTP-cached).
Best for: apps with a strict CSP (Critters requires unsafe-inline in style-src), or apps with many themes where a cached external file is more efficient than inlining on every request.
How to Configure
Section titled “How to Configure”The strategy is set during installation, but can be updated in provideThemeStack:
provideThemeStack({ strategy: 'critters' // or 'blocking'})If you change the strategy manually, you must run the sync command to update the blocking script injected into your index.html. In most setups, this is handled automatically via package manager script hooks.
# Using the package.json script (recommended)npm run ngx-theme-stack:sync
# Directly using the schematicng generate ngx-theme-stack:sync --project YOUR_PROJECT_NAMEThe Anti-Flicker Script (Blocking Script)
Section titled “The Anti-Flicker Script (Blocking Script)”To achieve the “Zero Flicker” effect, the library injects a small synchronous script into the <head> of your index.html. This script runs before the browser paints any DOM elements.
What does it do exactly?
Section titled “What does it do exactly?”The script performs the following steps in milliseconds:
- Reads the saved preference from
localStorage. - If nothing is saved, it uses the
defaultTheme. - If the theme is
'system', it resolves the real preference usingmatchMedia. - Applies the theme to the
<html>(via class or attribute) and sets thecolor-schemeproperty.
The Injected Code
Section titled “The Injected Code”Here is the code (minified by the library) for your transparency:
(function() { try { var k = "your-storage-key"; var d = "your-default-theme"; var m = "your-mode"; var v = ["light", "dark", "system", "your-custom-themes"]; var t = localStorage.getItem(k) || d; var e = document.documentElement;
// Security validation (ensure format is correct and theme is configured) if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t) || v.indexOf(t) === -1) t = d;
// System resolution if (t === 'system') { t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; }
// Immediate DOM application if (m === 'class' || m === 'both') e.classList.add(t); if (m === 'attribute' || m === 'both') e.setAttribute('data-theme', t); if (t === 'dark' || t === 'light') e.style.setProperty('color-scheme', t); } catch (x) {}})();Preventing Flicker in Conditional Rendering (SSR)
Section titled “Preventing Flicker in Conditional Rendering (SSR)”It is important to understand the difference between styles and content:
- CSS Styles (Automatic): The library applies the theme to the
<html>before Angular loads. You don’t need to do anything to prevent flickering in your background or text colors if you use CSS variables. - Conditional Content (Manual): If you need to show a different icon or image based on the theme (e.g.,
@if (theme.isDark())), a small hydration flicker might occur on the server.
For these conditional content cases, use the isHydrated signal:
<!-- ✅ Recommended for ICONS or IMAGES -->@if (theme.isHydrated()) { <img [src]="theme.isDark() ? 'dark-logo.png' : 'light-logo.png'">} @else { <!-- Optional: A placeholder to avoid visual jumps --> <div class="logo-placeholder"></div>}The isHydrated signal ensures that conditional content is only shown once the library has correctly synchronized the client state with localStorage.