@molecule/app-angular
Framework · framework · App (browser) · v1.0.2 · Apache-2.0
Angular framework bindings for molecule.dev
npm install @molecule/app-angularHow it works
@molecule/app-angular adapts the @molecule/* cores to the framework framework on the app (browser) side.
// main.ts — wire concrete providers into Angular DI:
import { bootstrapApplication } from '@angular/platform-browser'
import { provideMolecule } from '@molecule/app-angular'
import { createJWTAuthClient } from '@molecule/app-auth'
import { provider as stateProvider } from '@molecule/app-state-zustand'
import { provider as themeProvider } from '@molecule/app-theme-css-variables'
const authClient = createJWTAuthClient({ baseURL: '/api' })
bootstrapApplication(AppComponent, {
providers: [
provideMolecule({
state: stateProvider,
auth: authClient,
theme: themeProvider,
}),
],
})
// dashboard.component.ts — inject the Molecule services:
import { Component, inject } from '@angular/core'
import { MoleculeAuthService, MoleculeThemeService, t } from '@molecule/app-angular'
@Component({
selector: 'app-dashboard',
template: `
<div [style.background]="(theme$ | async)?.colors.background">
<h1>{{ t('dashboard.welcome', {}, { defaultValue: 'Welcome!' }) }}</h1>
<p>{{ (user$ | async)?.name }}</p>
<button (click)="logout()">
{{ t('auth.logout', {}, { defaultValue: 'Log out' }) }}
</button>
</div>
`,
})
class DashboardComponent {
// Expose the reactive t() so template bindings re-evaluate on locale change.
protected readonly t = t
private authService = inject(MoleculeAuthService)
private themeService = inject(MoleculeThemeService)
user$ = this.authService.user$ // Observable<UserProfile | null>
theme$ = this.themeService.theme$ // Observable<Theme>
logout(): void {
void this.authService.logout()
}
}Works with: @molecule/app-auth, @molecule/app-device, @molecule/app-forms, @molecule/app-http, @molecule/app-i18n, @molecule/app-logger, @molecule/app-platform, @molecule/app-push, @molecule/app-routing, @molecule/app-state, @molecule/app-storage, @molecule/app-theme, @molecule/app-ui, @molecule/app-utilities, @molecule/app-version
Reference
Angular framework bindings for molecule.dev.
Provides Angular-specific services and providers for all molecule core interfaces. This package enables the use of molecule's framework-agnostic interfaces with Angular's idioms (services, DI, RxJS observables, etc.).
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
// main.ts — wire concrete providers into Angular DI:
import { bootstrapApplication } from '@angular/platform-browser'
import { provideMolecule } from '@molecule/app-angular'
import { createJWTAuthClient } from '@molecule/app-auth'
import { provider as stateProvider } from '@molecule/app-state-zustand'
import { provider as themeProvider } from '@molecule/app-theme-css-variables'
const authClient = createJWTAuthClient({ baseURL: '/api' })
bootstrapApplication(AppComponent, {
providers: [
provideMolecule({
state: stateProvider,
auth: authClient,
theme: themeProvider,
}),
],
})
// dashboard.component.ts — inject the Molecule services:
import { Component, inject } from '@angular/core'
import { MoleculeAuthService, MoleculeThemeService, t } from '@molecule/app-angular'
@Component({
selector: 'app-dashboard',
template: `
<div [style.background]="(theme$ | async)?.colors.background">
<h1>{{ t('dashboard.welcome', {}, { defaultValue: 'Welcome!' }) }}</h1>
<p>{{ (user$ | async)?.name }}</p>
<button (click)="logout()">
{{ t('auth.logout', {}, { defaultValue: 'Log out' }) }}
</button>
</div>
`,
})
class DashboardComponent {
// Expose the reactive t() so template bindings re-evaluate on locale change.
protected readonly t = t
private authService = inject(MoleculeAuthService)
private themeService = inject(MoleculeThemeService)
user$ = this.authService.user$ // Observable<UserProfile | null>
theme$ = this.themeService.theme$ // Observable<Theme>
logout(): void {
void this.authService.logout()
}
}
Type
framework
Installation
npm install @molecule/app-angular @angular/core @molecule/app-auth @molecule/app-device @molecule/app-forms @molecule/app-http @molecule/app-i18n @molecule/app-logger @molecule/app-platform @molecule/app-push @molecule/app-routing @molecule/app-state @molecule/app-storage @molecule/app-theme @molecule/app-ui @molecule/app-utilities @molecule/app-version rxjs
API
Interfaces
AuthState
Reactive authentication state snapshot (initialized, authenticated, user, loading, and error).
interface AuthState<T = UserProfile> {
/**
* The session's state as one value — render from this instead of combining
* `initialized`/`authenticated`/`loading`. See `AuthStatus`.
*/
status: AuthStatus
/**
* Whether auth state has been initialized.
*/
initialized: boolean
/**
* Whether the user is authenticated.
*/
authenticated: boolean
/**
* Current user (if authenticated).
*/
user: T | null
/**
* Whether an auth operation is in progress.
*/
loading: boolean
/**
* Last auth error (if any).
*/
error: string | null
}
AuthClient
Auth client interface that all auth bond packages must implement. Provides login/logout/register flows, token management, profile updates, and auth state subscription.
interface AuthClient<T = UserProfile> {
/**
* Returns the current authentication state snapshot.
*/
getState(): AuthState<T>
/**
* Returns whether the user is currently authenticated.
*/
isAuthenticated(): boolean
/**
* Gets the current user.
*/
getUser(): T | null
/**
* Updates the cached user object (state + persistent storage) without
* hitting the network. Intended for local refreshes after a per-app
* mutation (e.g., the user just PATCHed their own profile and the
* server returned the canonical row). Does NOT change tokens.
*/
setUser(user: T | null): void
/**
* Gets the current access token.
*/
getAccessToken(): string | null
/**
* Stores the access token in the configured token storage adapter (in-memory
* by default). Use this to seed the token after an out-of-band exchange (e.g.
* the OAuth code→token redirect) instead of writing to `localStorage` directly,
* which would violate the in-memory-default storage contract and make the bearer
* token JS-readable (XSS-exfiltratable). Pass `null` to clear it.
*/
setAccessToken(token: string | null): void
/**
* Gets the refresh token.
*/
getRefreshToken(): string | null
/**
* Logs in with credentials.
*/
login(credentials: LoginCredentials): Promise<AuthResult<T>>
/**
* Logs out the current user.
*/
logout(): Promise<void>
/**
* Registers a new user.
*/
register(data: RegisterData): Promise<AuthResult<T>>
/**
* Refreshes the access token.
*/
refresh(): Promise<AuthResult<T>>
/**
* Requests a password reset.
*/
requestPasswordReset(data: PasswordResetRequest): Promise<void>
/**
* Confirms a password reset.
*/
confirmPasswordReset(data: PasswordResetConfirm): Promise<void>
/**
* Updates the current user's profile.
*/
updateProfile(data: Partial<T>): Promise<T>
/**
* Changes the current user's password.
*/
changePassword(oldPassword: string, newPassword: string): Promise<void>
/**
* Initializes auth state (e.g., from stored tokens).
*/
initialize(): Promise<void>
/**
* Re-checks a restored session against the server, in the background — the
* boot-time validation an app with a stored token needs when a revoked or
* expired token must sign the user out. Call it AFTER the first render
* (`initialize()` restores synchronously from storage; this costs one request).
*
* - The server confirms the user: the session is refreshed and `AuthState.status`
* stays `'authenticated'`.
* - The server answers 4xx: the token is dead — the stored session is cleared and
* the status becomes `'unauthenticated'`.
* - The server cannot answer (network failure or 5xx): the session is KEPT and the
* status becomes `'unavailable'`, so the app can retry instead of signing the
* user out over a bad connection.
*
* Do not hand-write this beside the client: a parallel token/user store for a
* "validating"/"unavailable" flag is the race this method removes.
*
* Optional: a provider with no restorable session has nothing to validate.
*/
validateSession?(): Promise<void>
/**
* Resolves when the session restore started by `initialize()` has settled — the point
* after which the client knows whether this visitor is signed in.
*
* Resolves IMMEDIATELY when no restore is in flight: before `initialize()` is ever
* called, and after it has finished. A restore that failed resolves too (it settled the
* question), so an HTTP client can await this before every request without risking one
* held forever. That is what makes the fetch-before-restore race impossible: a page that
* fetches viewer-dependent data on mount waits here instead of going out anonymously
* (see `setAuthGate` in `@molecule/app-http`).
*
* Optional: a provider that restores no session has nothing to wait for. Without it, an
* HTTP client bonded to that provider sends first-paint requests unauthenticated.
*
* A provider whose restore sends a request THROUGH the gated HTTP client must send that
* request with `skipAuthGate: true` (`RequestConfig` in `@molecule/app-http`): the gate
* waits on this promise, so holding the restore's own request makes the gate wait on
* itself and stalls every request it holds until it times out. `createJWTAuthClient`'s
* restore does not need it — it uses `fetch`, not the bonded client.
*/
whenRestored?(): Promise<void>
/**
* Subscribes to auth state changes.
*/
subscribe(callback: (state: AuthState<T>) => void): () => void
/**
* Subscribes to auth state changes (alias for subscribe).
*/
onAuthChange(callback: (state: AuthState<T>) => void): () => void
/**
* Gets the current access token (alias for getAccessToken).
*/
getToken?(): string | null
/**
* Adds an auth event listener.
*/
addEventListener(listener: AuthEventListener): () => void
/**
* Destroys the auth client.
*/
destroy(): void
}
FormController
Form controller interface.
All form providers must implement this interface.
interface FormController<T extends Record<string, unknown> = Record<string, unknown>> {
/**
* Gets the current form state.
*/
getState(): FormState<T>
/**
* Gets the value of a specific field.
*/
getValue(name: string): unknown
getValue<K extends keyof T>(name: K): T[K]
/**
* Gets all form values.
*/
getValues(): T
/**
* Sets the value of a specific field.
*/
setValue(
name: string,
value: unknown,
options?: {
shouldValidate?: boolean
shouldDirty?: boolean
shouldTouch?: boolean
},
): void
/**
* Sets multiple values at once.
*/
setValues(
values: Partial<T>,
options?: {
shouldValidate?: boolean
},
): void
/**
* Gets the error for a specific field.
*/
getError(name: string): string | undefined
/**
* Sets the error for a specific field.
*/
setError(name: string, error: string | undefined): void
/**
* Clears the error for a specific field.
*/
clearError<K extends keyof T>(name: K): void
/**
* Clears all errors.
*/
clearErrors(): void
/**
* Gets the field state for a specific field.
*/
getFieldState<K extends keyof T>(name: K): FieldState<T[K]>
/**
* Registers a field for form management.
*/
register(nameOrOptions: string | RegisterOptions, options?: RegisterOptions): FieldRegistration
/**
* Unregisters a field.
*/
unregister(name: string): void
/**
* Validates a specific field.
*/
validateField<K extends keyof T>(name: K): Promise<boolean>
/**
* Validates all fields.
*/
validate(): Promise<boolean>
/**
* Resets the form to initial values.
*/
reset(values?: Partial<T>): void
/**
* Handles form submission.
*/
handleSubmit(
onSubmit: (values: T) => void | Promise<void>,
onError?: (errors: Partial<Record<keyof T, string>>) => void,
): (event?: { preventDefault?: () => void }) => Promise<void>
/**
* Sets focus to a field.
*/
setFocus(name: keyof T): void
/**
* Subscribes to form state changes.
*/
subscribe(callback: (state: FormState<T>) => void): () => void
/**
* Destroys the form controller.
*/
destroy(): void
}
FormOptions
Form creation options.
interface FormOptions<T extends Record<string, unknown>> {
/**
* Default values.
*/
defaultValues?: Partial<T>
/**
* Validation mode.
*/
mode?: 'onSubmit' | 'onChange' | 'onBlur' | 'all'
/**
* Revalidation mode.
*/
reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit'
/**
* Whether to focus the first error field on submit.
*/
shouldFocusError?: boolean
/**
* Form-level validation function.
*/
validate?: (
values: T,
) => Partial<Record<keyof T, string>> | Promise<Partial<Record<keyof T, string>>>
}
HttpClient
HTTP client interface.
All HTTP providers must implement this interface.
interface HttpClient {
/**
* Base URL for all requests.
*/
baseURL: string
/**
* Default headers for all requests.
*/
defaultHeaders: Record<string, string>
/**
* Makes a generic HTTP request. `data` is a field of THIS config object — the same body
* `post(url, data, config)` takes as its second argument.
*/
request<T = unknown>(config: FullRequestConfig): Promise<HttpResponse<T>>
/**
* Makes a GET request.
*/
get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
/**
* Makes a POST request. `data` is the request BODY, not a config object: the wire body is
* `data` itself, so `post(url, { data: body })` sends `{"data":body}` — pass `body` directly.
* A config (headers, params) is the THIRD argument.
*/
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
/**
* Makes a PUT request. `data` is the request BODY (second argument) — never `{ data: body }`,
* which sends `{"data":body}`; a config is the THIRD argument.
*/
put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
/**
* Makes a PATCH request. `data` is the request BODY (second argument) — never `{ data: body }`,
* which sends `{"data":body}`; a config is the THIRD argument.
*/
patch<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
/**
* Makes a DELETE request.
*/
delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
/**
* Adds a request interceptor.
* Returns a function to remove the interceptor.
*/
addRequestInterceptor(interceptor: RequestInterceptor): () => void
/**
* Adds a response interceptor.
* Returns a function to remove the interceptor.
*/
addResponseInterceptor(interceptor: ResponseInterceptor): () => void
/**
* Adds an error interceptor.
* Returns a function to remove the interceptor.
*/
addErrorInterceptor(interceptor: ErrorInterceptor): () => void
/**
* Sets the authorization token.
*/
setAuthToken(token: string | null): void
/**
* Returns the current authorization token, or `null` if not set.
*/
getAuthToken(): string | null
/**
* Registers a handler for authentication errors (401).
*
* @returns An unsubscribe function.
*/
onAuthError(handler: () => void): () => void
}
I18nProvider
i18n provider interface.
All i18n providers must implement this interface.
interface I18nProvider {
/**
* Gets the current locale.
*/
getLocale(): string
/**
* Sets the current locale.
*
* **Fleet contract:** every conformant provider (the core simple provider,
* `@molecule/api-i18n-simple`, `@molecule/app-i18n-i18next`, and
* `@molecule/app-i18n-react-i18next`) MUST throw `Error('Locale "<code>"
* not found')` when `locale` is not registered — via the constructor's
* `initialLocales`/`locales` config, `addLocale()`, or `addTranslations()`
* (all three register a locale). It must NOT silently degrade to
* fallback-locale text while `getLocale()` reports the unregistered code —
* that divergence makes a misconfigured locale switch indistinguishable
* from a working one until a user notices the wrong language on screen.
*/
setLocale(locale: string): Promise<void>
/**
* Gets all available locales.
*/
getLocales(): LocaleConfig[]
/**
* Adds a locale.
*/
addLocale(config: LocaleConfig): void
/**
* Removes a locale by code, notifying subscribers so language pickers
* built on `onLocaleChange` re-render their list. If the removed locale
* is currently active, the caller is responsible for switching to a
* fallback (e.g. `'en'`) BEFORE calling this — the provider will not
* auto-fall-back on its own.
*
* Returns `true` if the locale was registered and removed, `false`
* otherwise.
*/
removeLocale(code: string): boolean
/**
* Adds translations to a locale. Auto-creates the locale if it doesn't exist.
*
* **Fleet contract:** merges are DEEP, not a shallow spread — registering
* two calls (e.g. two modules) that share a top-level namespace key merges
* their subtrees instead of the second call clobbering the first's nested
* translations wholesale. `@molecule/api-i18n-simple` implements the same
* contract on the API side.
*/
addTranslations(locale: string, translations: Translations, namespace?: string): void
/**
* Translates a key with optional interpolation values and pluralization.
*
* **Fleet plural contract (matches i18next's own key resolution order):**
* when `options.count` is provided, the plural-suffixed key
* (`` `${key}_${pluralForm}` ``, e.g. `item_one`/`item_few`/…, falling back
* to `` `${key}_other` ``) is looked up FIRST and wins over the base `key`
* if BOTH are registered. Only when no plural-suffixed key exists at all
* does resolution fall back to the base key. A catalog that ships both
* `item` and `item_one`/`item_other` therefore pluralizes identically
* whichever provider is bonded.
*
* @returns The translated string, or the default value / key if not found.
*/
t(
key: string,
values?: InterpolationValues,
options?: {
defaultValue?: string
count?: number
},
): string
/**
* Checks if a translation key exists.
*
* **Fleet contract:** follows the SAME locale-resolution chain as `t()` —
* the active locale, then the English fallback — so `exists(key) === true`
* whenever `t(key)` would render real translated text (not the raw key or
* an inline `defaultValue`). Do not narrow this to "only the active
* locale's own catalog"; that made `exists()` return `false` for keys `t()`
* happily rendered via the English fallback, and the answer differed by
* provider.
*
* @returns `true` if the key has a translation.
*/
exists(key: string): boolean
/**
* Formats a number according to the current locale.
*
* @returns The locale-formatted number string.
*/
formatNumber(value: number, options?: NumberFormatOptions): string
/**
* Formats a date according to the current locale.
*
* @returns The locale-formatted date string.
*/
formatDate(value: Date | number | string, options?: DateFormatOptions): string
/**
* Formats a relative time (e.g. "2 hours ago").
*
* @returns The locale-formatted relative time string.
*/
formatRelativeTime(
value: Date | number,
options?: {
unit?: Intl.RelativeTimeFormatUnit
},
): string
/**
* Formats a list (e.g. "A, B, and C").
*
* @returns The locale-formatted list string.
*/
formatList(
values: string[],
options?: {
type?: 'conjunction' | 'disjunction' | 'unit'
},
): string
/**
* Subscribes to locale changes.
*
* @returns An unsubscribe function.
*/
onLocaleChange(listener: (locale: string) => void): () => void
/**
* Gets the text direction for the current locale.
*
* @returns `'ltr'` or `'rtl'`.
*/
getDirection(): 'ltr' | 'rtl'
/**
* Checks if a translation key exists (alias for exists).
*/
hasKey?(key: string): boolean
/**
* Checks if the provider is ready.
*/
isReady?(): boolean
/**
* Registers a callback for when the provider is ready.
*/
onReady?(callback: () => void): () => void
/**
* Registers a lazily-loaded content module for automatic reload on locale changes.
* All registered content is reloaded during `setLocale()` before listeners fire,
* ensuring content is available on the first re-render with no flash.
*
* Idempotent: registering the same module name twice is a no-op.
*/
registerContent?(module: string, loader: (locale: string) => Promise<void>): void
}
Logger
Logger instance with leveled logging methods, child logger creation, and transport management.
interface Logger {
/**
* Logs a trace message.
*/
trace(message: string, ...args: unknown[]): void
/**
* Logs a debug message.
*/
debug(message: string, ...args: unknown[]): void
/**
* Logs an info message.
*/
info(message: string, ...args: unknown[]): void
/**
* Logs a warning message.
*/
warn(message: string, ...args: unknown[]): void
/**
* Logs an error message.
*/
error(message: string | Error, ...args: unknown[]): void
/**
* Sets the log level.
*/
setLevel(level: LogLevel): void
/**
* Gets the current log level.
*/
getLevel(): LogLevel
/**
* Creates a child logger with a namespace.
*/
child(name: string, context?: Record<string, unknown>): Logger
/**
* Adds additional context to the logger.
*/
withContext(context: Record<string, unknown>): Logger
/**
* Adds a transport.
*/
addTransport(transport: LogTransport): () => void
/**
* Removes a transport.
*/
removeTransport(transport: LogTransport): void
}
LoggerProvider
Logger provider interface that all logger bond packages must implement. Creates and manages logger instances and global log configuration.
interface LoggerProvider {
/**
* Gets a logger by name, or the root logger if no name given.
*/
getLogger(name?: string): Logger
/**
* Creates a named logger.
*/
createLogger(nameOrConfig: string | LoggerConfig, config?: LoggerConfig): Logger
/**
* Sets the global log level.
*/
setLevel(level: LogLevel): void
/**
* Gets the global log level.
*/
getLevel(): LogLevel
/**
* Adds a global transport.
*/
addTransport(transport: LogTransport): () => void
/**
* Enables logging.
*/
enable(): void
/**
* Disables logging.
*/
disable(): void
/**
* Checks if logging is enabled.
*
* @returns `true` if logging is currently enabled.
*/
isEnabled(): boolean
}
RouteLocation
Current URL decomposed into pathname, search string, hash, navigation state, and unique key.
interface RouteLocation {
/**
* Current pathname.
*/
pathname: string
/**
* Query string (including leading ?).
*/
search: string
/**
* Hash (including leading #).
*/
hash: string
/**
* State data passed with navigation.
*/
state?: unknown
/**
* Unique key for this location.
*/
key?: string
}
Router
Client-side router providing navigation, guards, route matching, and history control.
All routing providers must implement this interface.
interface Router {
/**
* Returns the current route location (pathname, search, hash, state).
*/
getLocation(): RouteLocation
/**
* Gets the current route params.
*/
getParams<T extends RouteParams = RouteParams>(): T
/**
* Gets the current query params.
*/
getQuery(): QueryParams
/**
* Gets a specific query parameter.
*/
getQueryParam(key: string): string | undefined
/**
* Gets the current hash.
*/
getHash(): string
/**
* Navigates to a path.
*/
navigate(path: string, options?: NavigateOptions): void
/**
* Navigates to a named route.
*/
navigateTo(
name: string,
params?: RouteParams,
query?: QueryParams,
options?: NavigateOptions,
): void
/**
* Goes back in history.
*/
back(): void
/**
* Goes forward in history.
*/
forward(): void
/**
* Goes to a specific point in history.
*/
go(delta: number): void
/**
* Updates the current query params.
*/
setQuery(params: QueryParams, options?: NavigateOptions): void
/**
* Updates a specific query parameter.
*/
setQueryParam(key: string, value: string | undefined, options?: NavigateOptions): void
/**
* Updates the current hash.
*/
setHash(hash: string, options?: NavigateOptions): void
/**
* Checks if a path matches the current location.
*
* @returns `true` if the path matches the current route.
*/
isActive(path: string, exact?: boolean): boolean
/**
* Matches a path pattern against a pathname.
*/
matchPath<Params extends RouteParams = RouteParams>(
pattern: string,
pathname: string,
): RouteMatch<Params> | null
/**
* Generates a URL from a named route.
*/
generatePath(name: string, params?: RouteParams, query?: QueryParams): string
/**
* Subscribes to route changes.
*/
subscribe(listener: RouteChangeListener): () => void
/**
* Adds a navigation guard.
*/
addGuard(guard: NavigationGuard): () => void
/**
* Registers route definitions.
*/
registerRoutes(routes: RouteDefinition[]): void
/**
* Gets all registered routes.
*/
getRoutes(): RouteDefinition[]
/**
* Destroys the router.
*/
destroy(): void
}
StoreConfig
Configuration for creating a store (initial state, optional name, and middleware chain).
interface StoreConfig<T> {
/**
* Initial state value.
*/
initialState: T
/**
* Optional name for debugging.
*/
name?: string
/**
* Optional middleware functions.
*/
middleware?: StoreMiddleware<T>[]
}
Store
Reactive state container with getState, setState, subscribe, and destroy.
All state management providers must implement this interface.
interface Store<T> {
/**
* Gets the current state.
*/
getState(): T
/**
* Sets the state (partial or via updater function).
*/
setState(partial: Partial<T> | ((state: T) => Partial<T>)): void
/**
* Subscribes to state changes.
* Returns an unsubscribe function.
*/
subscribe(listener: StateListener<T>): () => void
/**
* Destroys the store and cleans up subscriptions.
*/
destroy(): void
}
StateProvider
State provider interface that all state management bond packages must implement. Provides the store creation factory.
interface StateProvider {
/**
* Creates a new store.
*/
createStore<T>(config: StoreConfig<T>): Store<T>
}
StorageProvider
Storage provider interface.
All storage providers must implement this interface.
interface StorageProvider {
/**
* Gets a value from storage.
*/
get<T = unknown>(key: string): Promise<T | null>
/**
* Sets a value in storage.
*/
set<T = unknown>(key: string, value: T): Promise<void>
/**
* Removes a value from storage.
*/
remove(key: string): Promise<void>
/**
* Clears all values from storage.
*/
clear(): Promise<void>
/**
* Gets all keys in storage.
*/
keys(): Promise<string[]>
/**
* Gets multiple values from storage.
*/
getMany?<T = unknown>(keys: string[]): Promise<Map<string, T | null>>
/**
* Sets multiple values in storage.
*/
setMany?<T = unknown>(entries: Array<[string, T]>): Promise<void>
/**
* Removes multiple values from storage.
*/
removeMany?(keys: string[]): Promise<void>
}
ThemeProvider
Manages theme state including the active theme, mode toggling, and change subscriptions.
interface ThemeProvider {
/**
* Returns the currently active theme.
*/
getTheme(): Theme
/**
* Sets the active theme by reference or by name.
*
* @param theme - A `Theme` object or a theme name string to activate.
*/
setTheme(theme: Theme | string): void
/**
* Toggles between light and dark mode for the active theme.
*/
toggleMode(): void
/**
* Subscribes to theme changes. The callback fires whenever
* `setTheme()` or `toggleMode()` is called.
*
* @param callback - Invoked with the new theme after each change.
* @returns An unsubscribe function.
*/
subscribe(callback: (theme: Theme) => void): () => void
/**
* Returns all registered themes. Optional — not all providers
* support multiple themes.
*/
getThemes?(): Theme[]
/**
* Returns the theme a render WITHOUT a browser produces — a server or
* build-time render, which has no storage and no `prefers-color-scheme`
* to read. A hydrating client renders this theme first, so its markup
* matches what the server sent, and switches to `getTheme()` right
* after. Optional — a provider whose theme cannot differ between the two
* need not implement it.
*/
getServerTheme?(): Theme
}
Theme
Complete theme definition.
interface Theme {
name: string
mode: 'light' | 'dark'
colors: ThemeColors
breakpoints: ThemeBreakpoints
spacing: ThemeSpacing
typography: ThemeTypography
borderRadius: ThemeBorderRadius
shadows: ThemeShadows
transitions: ThemeTransitions
zIndex: ThemeZIndex
}
AsyncStateManager
Async state manager.
interface AsyncStateManager<T> {
state$: Observable<T>
getState: () => T
setState: (value: T | ((prev: T) => T) | Promise<T | ((prev: T) => T)>) => void
extendState: (
partial:
Partial<T> | ((prev: T) => Partial<T>) | Promise<Partial<T> | ((prev: T) => Partial<T>)>,
) => void
destroy: () => void
}
CapacitorAppManager
Capacitor app manager interface.
interface CapacitorAppManager {
state$: Observable<CapacitorAppState>
ready$: Observable<boolean>
initialize: () => Promise<void>
destroy: () => void
}
ChangePasswordStateManager
Change password state manager.
interface ChangePasswordStateManager {
state$: Observable<PromiseState<void>>
getState: () => PromiseState<void>
changePassword: (oldPassword: string, newPassword: string) => Promise<void>
reset: () => void
destroy: () => void
}
DeviceService
Device service interface.
interface DeviceService {
deviceInfo: DeviceInfo
screenInfo: ScreenInfo
hardwareInfo: HardwareInfo
featureSupport: FeatureSupport
supports: (feature: keyof FeatureSupport) => boolean
isOnline: () => boolean
isStandalone: () => boolean
language: string
languages: string[]
}
HttpState
State for HTTP requests.
interface HttpState<T> {
data: T | null
loading: boolean
error: Error | null
}
LoginStateManager
Login state manager.
interface LoginStateManager<T = unknown> {
state$: Observable<PromiseState<AuthResult<T>>>
getState: () => PromiseState<AuthResult<T>>
login: (credentials: LoginCredentials) => Promise<AuthResult<T>>
reset: () => void
destroy: () => void
}
OAuthOptions
OAuth configuration options.
interface OAuthOptions {
/** Base URL for the API server (e.g. `https://api.example.com`). */
baseURL?: string
/** List of supported OAuth provider names (e.g. `['github', 'google']`). */
oauthProviders?: string[]
/** Path prefix for OAuth initiation routes. Defaults to `/oauth`. */
oauthEndpoint?: string
/** Path for the OAuth login POST endpoint. Defaults to `/users/log-in/oauth`. */
loginEndpoint?: string
/** Called after a successful OAuth login (session established). */
onSuccess?: () => void
/**
* Called with a failure message when the OAuth login fails. Failures are
* also emitted on `OAuthStateManager.error$`.
*/
onError?: (error: string) => void
/**
* Auth client used to establish the session after the code exchange. This
* is THE way to wire session establishment in Angular: inject the
* `AUTH_CLIENT` token and pass the client here. When omitted, the helper
* falls back to the server-established httpOnly-cookie session (see
* `OAuthStateManager.handleCallback`).
*/
authClient?: AuthClient<unknown>
}
OAuthStateManager
OAuth state manager.
interface OAuthStateManager {
/** Observable of the configured OAuth provider names. */
providers$: Observable<string[]>
/**
* Observable of OAuth failure messages (from the callback code exchange).
* Completed by `OAuthStateManager.destroy`.
*/
error$: Observable<string>
/** Returns the configured OAuth provider names. */
getProviders: () => string[]
/** Builds the OAuth initiation URL for a provider. */
getOAuthUrl: (provider: string) => string
/** Starts the full-page redirect flow for a provider. */
redirect: (provider: string) => void
/**
* Handles the OAuth callback: exchanges the `code` URL parameter for a
* session. Invoked automatically at creation (see `createOAuthState`);
* exposed for callers that need to re-run it manually. No-ops unless
* running in a browser with a `code` URL parameter and a stashed provider.
*
* When an auth client was provided, the session is established locally
* (`setAccessToken` + `setUser` + `initialize`). When no client is
* available, the server has already established the httpOnly-cookie
* session during the exchange, so a user-carrying response still counts
* as success.
*/
handleCallback: () => Promise<void>
/** Completes the `providers$` and `error$` observables. */
destroy: () => void
}
PasswordResetStateManager
Password reset state manager.
interface PasswordResetStateManager {
requestState$: Observable<PromiseState<void>>
confirmState$: Observable<PromiseState<void>>
getRequestState: () => PromiseState<void>
getConfirmState: () => PromiseState<void>
requestReset: (data: PasswordResetRequest) => Promise<void>
confirmReset: (data: PasswordResetConfirm) => Promise<void>
reset: () => void
destroy: () => void
}
PlatformService
Platform service interface.
interface PlatformService {
platform: Platform
isNative: boolean
isMobile: boolean
isDesktop: boolean
isWeb: boolean
isDevelopment: boolean
isProduction: boolean
isPlatform: (...platforms: Platform[]) => boolean
}
PromiseStateManager
Promise state manager.
interface PromiseStateManager<T> {
state$: Observable<PromiseState<T>>
getState: () => PromiseState<T>
call: (...args: any[]) => Promise<T>
cancel: (message?: string) => void
reset: () => void
destroy: () => void
}
Returns: The result.
PushService
Push service interface.
interface PushService {
permission$: Observable<PermissionStatus | null>
token$: Observable<PushToken | null>
getPermission: () => PermissionStatus | null
getToken: () => PushToken | null
checkPermission: () => Promise<PermissionStatus>
requestPermission: () => Promise<PermissionStatus>
register: (options?: PushRegisterOptions) => Promise<PushToken>
unregister: () => Promise<void>
onNotificationReceived: (listener: NotificationReceivedListener) => () => void
onNotificationAction: (listener: NotificationActionListener) => () => void
onTokenChange: (listener: TokenChangeListener) => () => void
setBadge: (count: number) => Promise<void>
clearBadge: () => Promise<void>
destroy: () => void
}
SignupStateManager
Signup state manager.
interface SignupStateManager<T = unknown> {
state$: Observable<PromiseState<AuthResult<T>>>
getState: () => PromiseState<AuthResult<T>>
signup: (data: RegisterData) => Promise<AuthResult<T>>
reset: () => void
destroy: () => void
}
StorageValueState
State for a storage value.
interface StorageValueState<T> {
value: T | undefined
loading: boolean
error: Error | null
}
VersionService
Version service interface.
interface VersionService {
state$: Observable<VersionState>
isUpdateAvailable$: Observable<boolean>
isChecking$: Observable<boolean>
isServiceWorkerWaiting$: Observable<boolean>
newVersion$: Observable<string | undefined>
getState: () => VersionState
checkForUpdates: () => Promise<boolean>
applyUpdate: (options?: { force?: boolean }) => void
dismissUpdate: () => void
startPeriodicChecks: (options?: UpdateCheckOptions) => void
stopPeriodicChecks: () => void
destroy: () => void
}
MoleculeModuleConfig
Configuration for molecule Angular module.
interface MoleculeModuleConfig {
state?: StateProvider
auth?: AuthClient<unknown>
theme?: ThemeProvider
router?: Router
i18n?: I18nProvider
http?: HttpClient
storage?: StorageProvider
logger?: LoggerProvider
}
MoleculeTokens
Injection tokens for molecule services.
interface MoleculeTokens {
STATE_PROVIDER: symbol
AUTH_CLIENT: symbol
THEME_PROVIDER: symbol
ROUTER: symbol
I18N_PROVIDER: symbol
HTTP_CLIENT: symbol
STORAGE_PROVIDER: symbol
LOGGER_PROVIDER: symbol
}
Types
RouteParams
URL path parameter key-value map extracted from dynamic route segments (e.g. { id: '123' }).
type RouteParams = Record<string, string>
QueryParams
URL query string parameter map (single values or arrays for repeated keys).
type QueryParams = Record<string, string | string[] | undefined>
Classes
MoleculeAuthService
Angular service for authentication.
Wraps molecule auth client and exposes state as RxJS observables.
@Injectable({
providedIn: 'root',
})
export class MoleculeAuthService<T = unknown> implements OnDestroy {
private stateSubject: BehaviorSubject<AuthState<T>>
private unsubscribe: (() => void) | null = null
/**
* Observable of the full auth state.
*/
state$: Observable<AuthState<T>>
/**
* Observable of the authenticated user.
*/
user$: Observable<T | null>
/**
* Observable of authentication status.
*/
isAuthenticated$: Observable<boolean>
/**
* Observable of loading status.
*/
isLoading$: Observable<boolean>
constructor(@Inject(AUTH_CLIENT) private client: AuthClient<T>) {
this.stateSubject = new BehaviorSubject<AuthState<T>>(client.getState())
this.unsubscribe = client.onAuthChange(() => {
this.stateSubject.next(client.getState())
})
this.state$ = this.stateSubject.asObservable()
this.user$ = this.state$.pipe(
map((state) => state.user),
distinctUntilChanged(),
)
this.isAuthenticated$ = this.state$.pipe(
map((state) => state.authenticated),
distinctUntilChanged(),
)
this.isLoading$ = this.state$.pipe(
map((state) => state.loading),
distinctUntilChanged(),
)
}
/**
* Get the current auth state snapshot.
* @returns The current authentication state including user, loading, and authenticated flags.
*/
get state(): AuthState<T> {
return this.client.getState()
}
/**
* Get the current user snapshot.
* @returns The authenticated user object, or `null` if not authenticated.
*/
get user(): T | null {
return this.state.user
}
/**
* Check if user is currently authenticated.
* @returns `true` if the user is authenticated, `false` otherwise.
*/
get isAuthenticated(): boolean {
return this.state.authenticated
}
/**
* Login with credentials.
*
* @param credentials - The email/password or provider-specific login credentials.
* @returns A promise that resolves to the authentication result with user data and tokens.
*/
login(credentials: LoginCredentials): Promise<AuthResult<T>> {
return this.client.login(credentials)
}
/**
* Logout the current user.
* @returns A promise that resolves when the user has been logged out.
*/
logout(): Promise<void> {
return this.client.logout()
}
/**
* Register a new user.
*
* @param data - The registration payload including email, password, and profile fields.
* @returns A promise that resolves to the authentication result for the newly registered user.
*/
register(data: RegisterData): Promise<AuthResult<T>> {
return this.client.register(data)
}
/**
* Refresh the authentication token.
* @returns A promise that resolves to the authentication result with refreshed tokens.
*/
refresh(): Promise<AuthResult<T>> {
return this.client.refresh()
}
/**
* Clean up auth state subscription on service destruction.
*/
ngOnDestroy(): void {
this.unsubscribe?.()
this.stateSubject.complete()
}
}
MoleculeFormInstance
Wrapper around a FormController that provides RxJS observables and synchronous accessors for use in Angular components.
class MoleculeFormInstance<T extends Record<string, unknown>> {
private stateSubject: BehaviorSubject<FormState<T>>
private unsubscribe: (() => void) | null = null
/**
* Observable of the full form state.
*/
state$: Observable<FormState<T>>
/**
* Observable of form validity.
*/
isValid$: Observable<boolean>
/**
* Observable of form dirty state.
*/
isDirty$: Observable<boolean>
/**
* Observable of form submitting state.
*/
isSubmitting$: Observable<boolean>
/**
* Observable of field errors.
*/
errors$: Observable<Partial<Record<keyof T, string>>>
constructor(private controller: FormController<T>) {
this.stateSubject = new BehaviorSubject<FormState<T>>(controller.getState())
this.unsubscribe = controller.subscribe((state: FormState<T>) => {
this.stateSubject.next(state)
})
this.state$ = this.stateSubject.asObservable()
this.isValid$ = this.state$.pipe(
map((state) => state.isValid),
distinctUntilChanged(),
)
this.isDirty$ = this.state$.pipe(
map((state) => state.isDirty),
distinctUntilChanged(),
)
this.isSubmitting$ = this.state$.pipe(
map((state) => state.isSubmitting),
distinctUntilChanged(),
)
this.errors$ = this.state$.pipe(
map((state) => state.errors),
distinctUntilChanged(),
)
}
/**
* Get the current form state snapshot.
* @returns The current form state including values, errors, validity, and submission status.
*/
get state(): FormState<T> {
return this.controller.getState()
}
/**
* Get the current form validity snapshot.
* @returns `true` if all fields pass validation, `false` otherwise.
*/
get isValid(): boolean {
return this.controller.getState().isValid
}
/**
* Get the current form dirty state snapshot.
* @returns `true` if any field has been modified from its default value, `false` otherwise.
*/
get isDirty(): boolean {
return this.controller.getState().isDirty
}
/**
* Get the current form submitting state snapshot.
* @returns `true` if the form is currently being submitted, `false` otherwise.
*/
get isSubmitting(): boolean {
return this.controller.getState().isSubmitting
}
/**
* Register a field for form management.
*
* @param name - The field name corresponding to a key in the form values type.
* @param options - Registration options including validation rules and default value.
* @returns The field registration with value, onChange, onBlur, and ref bindings.
*/
register(name: keyof T, options?: Omit<RegisterOptions, 'name'>): FieldRegistration {
return this.controller.register({ ...options, name: name as string } as RegisterOptions)
}
/**
* Get the value of a specific field.
*
* @param name - The field name corresponding to a key in the form values type.
* @returns The current value of the specified field.
*/
getValue<K extends keyof T>(name: K): T[K] {
return this.controller.getValue(name)
}
/**
* Set the value of a specific field.
*
* @param name - The field name corresponding to a key in the form values type.
* @param value - The new value to set for the field.
*/
setValue<K extends keyof T>(name: K, value: T[K]): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.controller.setValue(name as any, value as any)
}
/**
* Get the error for a specific field.
*
* @param name - The field name to retrieve the error for.
* @returns The error message for the field, or `undefined` if the field has no error.
*/
getError(name: keyof T): string | undefined {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this.controller.getError(name as any)
}
/**
* Set the error for a specific field.
*
* @param name - The field name to set or clear the error for.
* @param error - The error message to set, or `undefined` to clear the error.
*/
setError(name: keyof T, error: string | undefined): void {
if (error !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.controller.setError(name as any, error)
} else {
this.controller.clearError(name)
}
}
/**
* Clear all field errors.
*/
clearErrors(): void {
this.controller.clearErrors()
}
/**
* Create a submit handler function.
*
* @param onSubmit - Callback invoked with validated form values on successful submission.
* @param onError - Optional callback invoked with field errors when validation fails.
* @returns A submit handler function that can be called from form submit events.
*/
handleSubmit(
onSubmit: (values: T) => void | Promise<void>,
onError?: (errors: Partial<Record<keyof T, string>>) => void,
): (event?: { preventDefault?: () => void }) => Promise<void> {
return this.controller.handleSubmit(onSubmit, onError)
}
/**
* Reset the form to initial values.
*
* @param values - Optional partial values to use as the new initial state.
*/
reset(values?: Partial<T>): void {
this.controller.reset(values)
}
/**
* Validate all fields.
*
* @returns A promise that resolves to `true` if all fields are valid, `false` otherwise.
*/
validate(): Promise<boolean> {
return this.controller.validate()
}
/**
* Destroy the form instance and clean up subscriptions.
*/
destroy(): void {
this.unsubscribe?.()
this.unsubscribe = null
this.stateSubject.complete()
this.controller.destroy()
}
}
MoleculeFormsService
Angular service for form handling.
Wraps molecule form providers and creates form instances that expose state as RxJS observables.
@Injectable({
providedIn: 'root',
})
export class MoleculeFormsService implements OnDestroy {
private instances: MoleculeFormInstance<Record<string, unknown>>[] = []
/**
* Create a form instance from a provider.
*
* @param provider - The form provider that creates form controllers.
* @param options - Form configuration including default values and validation mode.
* @returns A new form instance with RxJS observables for reactive state tracking.
*/
createForm<T extends Record<string, unknown>>(
provider: FormProvider,
options: FormOptions<T>,
): MoleculeFormInstance<T> {
const controller = provider.createForm(options)
const instance = new MoleculeFormInstance<T>(controller)
this.instances.push(instance as MoleculeFormInstance<Record<string, unknown>>)
return instance
}
/**
* Destroy all managed form instances on service destruction.
*/
ngOnDestroy(): void {
for (const instance of this.instances) {
instance.destroy()
}
this.instances = []
}
}
MoleculeHttpService
Angular service for HTTP requests.
Wraps molecule HTTP client and returns RxJS observables.
@Injectable({
providedIn: 'root',
})
/**
* Molecule Http Service class.
*/
export class MoleculeHttpService {
constructor(@Inject(HTTP_CLIENT) private client: HttpClient) {}
/**
* Make a GET request.
*
* @param url - Request URL
* @param config - Request configuration
* @returns Observable of response data
*/
get<T>(url: string, config?: RequestConfig): Observable<T> {
return from(this.client.get<T>(url, config)).pipe(map((res) => res.data))
}
/**
* Make a POST request.
*
* @param url - Request URL
* @param data - Request body
* @param config - Request configuration
* @returns Observable of response data
*/
post<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
return from(this.client.post<T>(url, data, config)).pipe(map((res) => res.data))
}
/**
* Make a PUT request.
*
* @param url - Request URL
* @param data - Request body
* @param config - Request configuration
* @returns Observable of response data
*/
put<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
return from(this.client.put<T>(url, data, config)).pipe(map((res) => res.data))
}
/**
* Make a PATCH request.
*
* @param url - Request URL
* @param data - Request body
* @param config - Request configuration
* @returns Observable of response data
*/
patch<T>(url: string, data?: unknown, config?: RequestConfig): Observable<T> {
return from(this.client.patch<T>(url, data, config)).pipe(map((res) => res.data))
}
/**
* Make a DELETE request.
*
* @param url - Request URL
* @param config - Request configuration
* @returns Observable of response data
*/
delete<T>(url: string, config?: RequestConfig): Observable<T> {
return from(this.client.delete<T>(url, config)).pipe(map((res) => res.data))
}
/**
* Get the full response (including headers, status, etc.).
*
* @param method - HTTP method
* @param url - Request URL
* @param config - Request configuration
* @returns Observable of full response
*/
request<T>(
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
url: string,
config?: RequestConfig,
): Observable<HttpResponse<T>> {
const promise = (async () => {
switch (method) {
case 'GET':
return this.client.get<T>(url, config)
case 'POST':
return this.client.post<T>(
url,
(config as RequestConfig & { data?: unknown })?.data,
config,
)
case 'PUT':
return this.client.put<T>(
url,
(config as RequestConfig & { data?: unknown })?.data,
config,
)
case 'PATCH':
return this.client.patch<T>(
url,
(config as RequestConfig & { data?: unknown })?.data,
config,
)
case 'DELETE':
return this.client.delete<T>(url, config)
}
})()
return from(promise)
}
/**
* Create a state subject for managing request state.
*
* @returns The created instance.
*/
createState<T>(): BehaviorSubject<HttpState<T>> {
return new BehaviorSubject<HttpState<T>>({
data: null,
loading: false,
error: null,
})
}
/**
* Make a GET request with state management.
*
* @param url - Request URL
* @param state - State subject to update
* @param config - Request configuration
*/
getWithState<T>(url: string, state: BehaviorSubject<HttpState<T>>, config?: RequestConfig): void {
state.next({ ...state.value, loading: true, error: null })
this.get<T>(url, config)
.pipe(
tap((data) => state.next({ data, loading: false, error: null })),
catchError((error) => {
state.next({ data: null, loading: false, error })
throw error
}),
)
.subscribe()
}
}
MoleculeI18nService
Angular service for internationalization.
Wraps molecule i18n provider and exposes state as RxJS observables.
@Injectable({
providedIn: 'root',
})
export class MoleculeI18nService implements OnDestroy {
private localeSubject: BehaviorSubject<string>
private directionSubject: BehaviorSubject<'ltr' | 'rtl'>
private localesSubject: BehaviorSubject<LocaleConfig[]>
private unsubscribe: (() => void) | null = null
/**
* Observable of the current locale code.
*/
locale$: Observable<string>
/**
* Observable of the text direction.
*/
direction$: Observable<'ltr' | 'rtl'>
/**
* Observable of available locales.
*/
locales$: Observable<LocaleConfig[]>
constructor(@Inject(I18N_PROVIDER) private provider: I18nProvider) {
this.localeSubject = new BehaviorSubject<string>(provider.getLocale())
this.directionSubject = new BehaviorSubject<'ltr' | 'rtl'>(provider.getDirection())
this.localesSubject = new BehaviorSubject<LocaleConfig[]>(provider.getLocales())
this.unsubscribe = provider.onLocaleChange(() => {
this.localeSubject.next(provider.getLocale())
this.directionSubject.next(provider.getDirection())
this.localesSubject.next(provider.getLocales())
})
this.locale$ = this.localeSubject.asObservable()
this.direction$ = this.directionSubject.asObservable()
this.locales$ = this.localesSubject.asObservable()
}
/**
* Get the current locale code snapshot.
* @returns The active locale code (e.g. `'en'`, `'fr'`).
*/
get locale(): string {
return this.provider.getLocale()
}
/**
* Get the current text direction snapshot.
* @returns The text direction for the active locale, either `'ltr'` or `'rtl'`.
*/
get direction(): 'ltr' | 'rtl' {
return this.provider.getDirection()
}
/**
* Translate a key.
*
* @param key - The dot-notation translation key to look up.
* @param values - Optional interpolation values to substitute into the translated string.
* @returns The translated and interpolated string for the current locale.
*/
t(key: string, values?: InterpolationValues): string {
return this.provider.t(key, values)
}
/**
* Set the current locale.
*
* @param code - The locale code to switch to (e.g. `'en'`, `'fr'`).
*/
setLocale(code: string): void {
this.provider.setLocale(code)
}
/**
* Format a number.
*
* @param value - The number to format.
* @param options - Locale-aware formatting options such as style, currency, and precision.
* @returns The locale-formatted number string.
*/
formatNumber(value: number, options?: NumberFormatOptions): string {
return this.provider.formatNumber(value, options)
}
/**
* Format a date.
*
* @param value - The date to format, as a Date object, timestamp, or ISO string.
* @param options - Locale-aware formatting options such as date style and time zone.
* @returns The locale-formatted date string.
*/
formatDate(value: Date | number | string, options?: DateFormatOptions): string {
return this.provider.formatDate(value, options)
}
/**
* Clean up locale change subscription and complete all subjects on service destruction.
*/
ngOnDestroy(): void {
this.unsubscribe?.()
this.localeSubject.complete()
this.directionSubject.complete()
this.localesSubject.complete()
}
}
MoleculeLoggerService
Angular service for logging.
Wraps molecule logger provider.
@Injectable({
providedIn: 'root',
})
export class MoleculeLoggerService {
constructor(@Inject(LOGGER_PROVIDER) private provider: LoggerProvider) {}
/**
* Get a logger instance.
*
* @param name - Logger name (usually component/service name)
* @returns The result.
*/
getLogger(name?: string): Logger {
return this.provider.getLogger(name)
}
/**
* Create a logger with custom configuration.
*
* @param config - Logger configuration
* @returns The created instance.
*/
createLogger(config: LoggerConfig): Logger {
return this.provider.createLogger(config)
}
/**
* Set the global log level.
*
* @param level - Log level
*/
setLevel(level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'): void {
this.provider.setLevel(level)
}
/**
* Get the current global log level.
*
* @returns The result.
*/
getLevel(): string {
return this.provider.getLevel()
}
/**
* Enable logging.
*/
enable(): void {
this.provider.enable()
}
/**
* Disable logging.
*/
disable(): void {
this.provider.disable()
}
/**
* Check if logging is enabled.
*
* @returns Whether logging is enabled
*/
isEnabled(): boolean {
return this.provider.isEnabled()
}
}
MoleculeRouterService
Angular service for routing.
Wraps molecule router and exposes state as RxJS observables.
@Injectable({
providedIn: 'root',
})
/**
* Molecule Router Service class.
*/
export class MoleculeRouterService implements OnDestroy {
private locationSubject: BehaviorSubject<RouteLocation>
private paramsSubject: BehaviorSubject<RouteParams>
private querySubject: BehaviorSubject<QueryParams>
private unsubscribe: (() => void) | null = null
/**
* Observable of the current location.
*/
location$: Observable<RouteLocation>
/**
* Observable of route parameters.
*/
params$: Observable<RouteParams>
/**
* Observable of query parameters.
*/
query$: Observable<QueryParams>
/**
* Observable of the current pathname.
*/
pathname$: Observable<string>
constructor(@Inject(ROUTER) private router: Router) {
this.locationSubject = new BehaviorSubject<RouteLocation>(router.getLocation())
this.paramsSubject = new BehaviorSubject<RouteParams>(router.getParams())
this.querySubject = new BehaviorSubject<QueryParams>(router.getQuery())
this.unsubscribe = router.subscribe(() => {
this.locationSubject.next(router.getLocation())
this.paramsSubject.next(router.getParams())
this.querySubject.next(router.getQuery())
})
this.location$ = this.locationSubject.asObservable()
this.params$ = this.paramsSubject.asObservable()
this.query$ = this.querySubject.asObservable()
this.pathname$ = this.location$.pipe(
map((loc) => loc.pathname),
distinctUntilChanged(),
)
}
/**
* Get the current location snapshot.
* @returns The result.
*/
get location(): RouteLocation {
return this.router.getLocation()
}
/**
* Get the current params snapshot.
* @returns The result.
*/
get params(): RouteParams {
return this.router.getParams()
}
/**
* Get the current query snapshot.
* @returns The result.
*/
get query(): QueryParams {
return this.router.getQuery()
}
/**
* Navigate to a path.
*
* @param path - Target path
* @param options - Navigation options
*/
navigate(path: string, options?: NavigateOptions): void {
this.router.navigate(path, options)
}
/**
* Navigate to a named route.
*
* @param name - Route name
* @param params - Route parameters
* @param query - Query parameters
* @param options - Navigation options
*/
navigateTo(
name: string,
params?: RouteParams,
query?: QueryParams,
options?: NavigateOptions,
): void {
this.router.navigateTo(name, params, query, options)
}
/**
* Go back in history.
*/
back(): void {
this.router.back()
}
/**
* Go forward in history.
*/
forward(): void {
this.router.forward()
}
/**
* Check if a path is currently active.
*
* @param path - Path to check
* @param exact - Whether to match exactly
* @returns The result.
*/
isActive(path: string, exact = false): boolean {
return this.router.isActive(path, exact)
}
/**
* Cleans up subscriptions and completes subjects on service destruction.
*/
ngOnDestroy(): void {
this.unsubscribe?.()
this.locationSubject.complete()
this.paramsSubject.complete()
this.querySubject.complete()
}
}
MoleculeStateService
Angular service for state management.
Wraps molecule state stores and exposes them as RxJS observables.
@Injectable({
providedIn: 'root',
})
/**
* Molecule State Service class.
*/
export class MoleculeStateService implements OnDestroy {
private subscriptions = new Map<Store<unknown>, () => void>()
private subjects = new Map<Store<unknown>, BehaviorSubject<unknown>>()
constructor(@Inject(STATE_PROVIDER) private provider: StateProvider) {}
/**
* Get an observable of the full store state.
*
* @param store - The store to observe
* @returns Observable of the store state
*/
observe<T>(store: Store<T>): Observable<T> {
return this.getOrCreateSubject(store).asObservable() as Observable<T>
}
/**
* Select a piece of state from a store.
*
* @param store - The store to select from
* @param selector - Selector function
* @returns Observable of the selected state
*/
select<T, S>(store: Store<T>, selector: (state: T) => S): Observable<S> {
return this.observe(store).pipe(map(selector), distinctUntilChanged())
}
/**
* Get the current snapshot of a store's state.
*
* @param store - The store
* @returns The result.
*/
snapshot<T>(store: Store<T>): T {
return store.getState()
}
/**
* Update a store's state.
*
* @param store - The store to update
* @param partial - Partial state or updater function
*/
update<T>(store: Store<T>, partial: Partial<T> | ((state: T) => Partial<T>)): void {
store.setState(partial)
}
/**
* Gets or creates a BehaviorSubject for the given store.
* @param store - The data store to observe.
* @returns The BehaviorSubject tracking the store state.
*/
private getOrCreateSubject<T>(store: Store<T>): BehaviorSubject<T> {
let subject = this.subjects.get(store as Store<unknown>) as BehaviorSubject<T> | undefined
if (!subject) {
subject = new BehaviorSubject<T>(store.getState())
this.subjects.set(store as Store<unknown>, subject as BehaviorSubject<unknown>)
const unsubscribe = store.subscribe(() => {
subject!.next(store.getState())
})
this.subscriptions.set(store as Store<unknown>, unsubscribe)
}
return subject
}
/**
* Cleans up all store subscriptions and completes subjects on service destruction.
*/
ngOnDestroy(): void {
// Clean up all subscriptions
this.subscriptions.forEach((unsubscribe) => unsubscribe())
this.subscriptions.clear()
// Complete all subjects
this.subjects.forEach((subject) => subject.complete())
this.subjects.clear()
}
}
MoleculeStorageService
Angular service for storage.
Wraps molecule storage provider and returns RxJS observables.
@Injectable({
providedIn: 'root',
})
/**
* Molecule Storage Service class.
*/
export class MoleculeStorageService {
constructor(@Inject(STORAGE_PROVIDER) private provider: StorageProvider) {}
/**
* Get a value from storage.
*
* @param key - Storage key
* @returns Observable of the value
*/
get<T>(key: string): Observable<T | null> {
return from(this.provider.get<T>(key))
}
/**
* Set a value in storage.
*
* @param key - Storage key
* @param value - Value to store
* @returns Observable that completes when done
*/
set<T>(key: string, value: T): Observable<void> {
return from(this.provider.set(key, value))
}
/**
* Remove a value from storage.
*
* @param key - Storage key
* @returns Observable that completes when done
*/
remove(key: string): Observable<void> {
return from(this.provider.remove(key))
}
/**
* Clear all values from storage.
*
* @returns Observable that completes when done
*/
clear(): Observable<void> {
return from(this.provider.clear())
}
/**
* Get all keys in storage.
*
* @returns Observable of key array
*/
keys(): Observable<string[]> {
return from(this.provider.keys())
}
/**
* Create a reactive storage value with auto-loading.
*
* @param key - Storage key
* @param defaultValue - Default value if not found
* @returns BehaviorSubject that syncs with storage
*/
createValue<T>(key: string, defaultValue: T): BehaviorSubject<StorageValueState<T>> {
const state = new BehaviorSubject<StorageValueState<T>>({
value: defaultValue,
loading: true,
error: null,
})
// Load initial value
this.get<T>(key).subscribe({
next: (value) => {
state.next({
value: value ?? defaultValue,
loading: false,
error: null,
})
},
error: (error) => {
state.next({
value: defaultValue,
loading: false,
error,
})
},
})
return state
}
/**
* Update a reactive storage value.
*
* @param key - Storage key
* @param value - New value
* @param state - State subject to update
*/
updateValue<T>(key: string, value: T, state: BehaviorSubject<StorageValueState<T>>): void {
state.next({ ...state.value, loading: true })
this.set(key, value).subscribe({
next: () => {
state.next({ value, loading: false, error: null })
},
error: (error) => {
state.next({ ...state.value, loading: false, error })
},
})
}
}
MoleculeThemeService
Angular service for theming.
Wraps molecule theme provider and exposes state as RxJS observables.
@Injectable({
providedIn: 'root',
})
/**
* Molecule Theme Service class.
*/
export class MoleculeThemeService implements OnDestroy {
private themeSubject: BehaviorSubject<Theme>
private themeNameSubject: BehaviorSubject<string>
private unsubscribe: (() => void) | null = null
/**
* Observable of the current theme.
*/
theme$: Observable<Theme>
/**
* Observable of the current theme name.
*/
themeName$: Observable<string>
/**
* Observable of the theme mode (light/dark).
*/
mode$: Observable<'light' | 'dark'>
constructor(@Inject(THEME_PROVIDER) private provider: ThemeProvider) {
this.themeSubject = new BehaviorSubject<Theme>(provider.getTheme())
this.themeNameSubject = new BehaviorSubject<string>(provider.getTheme().name)
this.unsubscribe = provider.subscribe(() => {
this.themeSubject.next(provider.getTheme())
this.themeNameSubject.next(provider.getTheme().name)
})
this.theme$ = this.themeSubject.asObservable()
this.themeName$ = this.themeNameSubject.asObservable()
this.mode$ = this.theme$.pipe(
map((theme: Theme) => theme.mode),
distinctUntilChanged(),
)
}
/**
* Get the current theme snapshot.
* @returns The result.
*/
get theme(): Theme {
return this.provider.getTheme()
}
/**
* Get the current theme name snapshot.
* @returns The result.
*/
get themeName(): string {
return this.provider.getTheme().name
}
/**
* Get available themes.
* @returns The result.
*/
get themes(): Theme[] {
return this.provider.getThemes?.() ?? []
}
/**
* Set the current theme.
*
* @param name - Theme name
*/
setTheme(name: string): void {
this.provider.setTheme(name)
}
/**
* Toggle to the next theme.
*/
toggleTheme(): void {
const themes = this.themes
const currentName = this.themeName
const currentIndex = themes.findIndex((t: Theme) => t.name === currentName)
const nextIndex = (currentIndex + 1) % themes.length
this.provider.setTheme(themes[nextIndex])
}
/**
* Cleans up subscriptions and completes subjects on service destruction.
*/
ngOnDestroy(): void {
this.unsubscribe?.()
this.themeSubject.complete()
this.themeNameSubject.complete()
}
}
Functions
bumpLocaleVersion()
Bump the locale version signal, causing all template bindings that use
the reactive t() to re-evaluate on the next change detection cycle.
Called automatically by the ENVIRONMENT_INITIALIZER in provideMolecule.
function bumpLocaleVersion(): void
t(key, values, options)
Translate a key using the current locale.
This is a signal-aware wrapper around @molecule/app-i18n's t().
Reading the internal locale signal establishes an Angular reactivity
dependency, so template bindings that call this function will be
re-evaluated when the locale changes.
function t(
key: string,
values?: InterpolationValues,
options?: { defaultValue?: string; count?: number },
): string
key— Translation keyvalues— Interpolation valuesoptions— Options (defaultValue, count).options.defaultValue— Fallback string when no translation is found.options.count— Pluralization count.
Returns: Translated string
provideState(provider)
Provide state management.
function provideState(provider: StateProvider): EnvironmentProviders
provider— State provider
Returns: Environment providers
provideAuth(client)
Registers an AuthClient as an Angular environment provider for dependency injection.
function provideAuth(client: AuthClient<T>): EnvironmentProviders
client— Auth client
Returns: Environment providers
provideTheme(provider)
Registers a ThemeProvider as an Angular environment provider for dependency injection.
function provideTheme(provider: ThemeProvider): EnvironmentProviders
provider— Theme provider
Returns: Environment providers
provideRouter(router)
Registers a Router as an Angular environment provider for dependency injection.
function provideRouter(router: Router): EnvironmentProviders
router— Router instance
Returns: Environment providers
provideI18n(provider)
Registers an I18nProvider as an Angular environment provider for dependency injection.
function provideI18n(provider: I18nProvider): EnvironmentProviders
provider— I18n provider
Returns: Environment providers
provideHttp(client)
Provide HTTP client.
function provideHttp(client: HttpClient): EnvironmentProviders
client— HTTP client
Returns: Environment providers
provideStorage(provider)
Registers a StorageProvider as an Angular environment provider for dependency injection.
function provideStorage(provider: StorageProvider): EnvironmentProviders
provider— Storage provider
Returns: Environment providers
provideLogger(provider)
Registers a LoggerProvider as an Angular environment provider for dependency injection.
function provideLogger(provider: LoggerProvider): EnvironmentProviders
provider— Logger provider
Returns: Environment providers
provideMolecule(config)
Provide all molecule services at once.
function provideMolecule(config: MoleculeModuleConfig): EnvironmentProviders
config— Configuration with all providers
Returns: Environment providers
createAsyncState(initialState)
Creates an async-capable state manager.
function createAsyncState(initialState: T): AsyncStateManager<T>
initialState— Initial state value
Returns: The created instance.
createCapacitorAppState(options)
Creates an Angular Capacitor app service with reactive state.
Wraps createCapacitorApp from @molecule/app-platform and exposes
state changes as RxJS observables.
function createCapacitorAppState(options?: CapacitorAppOptions): CapacitorAppManager
options— Capacitor app configuration options
Returns: Capacitor app manager with observables and action methods
createChangePasswordState(client)
Creates a change password state manager with async state tracking.
function createChangePasswordState(client: AuthClient<UserProfile>): ChangePasswordStateManager
client— Auth client
Returns: Change password state manager
createDeviceService()
Creates an Angular device service with static device information.
function createDeviceService(): DeviceService
Returns: Device service with device, screen, hardware, and feature info
createLoginState(client)
Creates a login state manager with async state tracking.
function createLoginState(client: AuthClient<T>): LoginStateManager<T>
client— Auth client
Returns: Login state manager
createOAuthState(options)
Creates an OAuth state manager.
Automatically handles OAuth callbacks: when created in a browser, it
invokes OAuthStateManager.handleCallback immediately (a guarded
no-op when the URL carries no code parameter).
function createOAuthState(options?: OAuthOptions): OAuthStateManager
options— OAuth configuration
Returns: The created instance.
createPasswordResetState(client)
Creates a password reset state manager with async state tracking.
function createPasswordResetState(client: AuthClient<UserProfile>): PasswordResetStateManager
client— Auth client
Returns: Password reset state manager
createPlatformService()
Creates an Angular platform service with static platform information.
function createPlatformService(): PlatformService
Returns: Platform service with platform flags and isPlatform check
createPromiseState(asyncFn)
Creates a promise state manager for tracking async function state.
function createPromiseState(asyncFn: T): PromiseStateManager<Awaited<ReturnType<T>>>
asyncFn— The async function to track
Returns: Promise state manager with observable state
createPushService()
Creates an Angular push notifications service with reactive state.
function createPushService(): PushService
Returns: Push service with observables and action methods
createSignupState(client)
Creates a signup state manager with async state tracking.
function createSignupState(client: AuthClient<T>): SignupStateManager<T>
client— Auth client
Returns: Signup state manager
createVersionService()
Creates an Angular version service with reactive state.
function createVersionService(): VersionService
Returns: The created instance.
Constants
STATE_PROVIDER
Injection token for state provider.
const STATE_PROVIDER: InjectionToken<StateProvider>
AUTH_CLIENT
Injection token for auth client.
const AUTH_CLIENT: InjectionToken<AuthClient<unknown>>
THEME_PROVIDER
Injection token for theme provider.
const THEME_PROVIDER: InjectionToken<ThemeProvider>
ROUTER
Injection token for router.
const ROUTER: InjectionToken<Router>
I18N_PROVIDER
Injection token for i18n provider.
const I18N_PROVIDER: InjectionToken<I18nProvider>
HTTP_CLIENT
Injection token for HTTP client.
const HTTP_CLIENT: InjectionToken<HttpClient>
STORAGE_PROVIDER
Injection token for storage provider.
const STORAGE_PROVIDER: InjectionToken<StorageProvider>
LOGGER_PROVIDER
Injection token for logger provider.
const LOGGER_PROVIDER: InjectionToken<LoggerProvider>
Injection Notes
Requirements
Peer dependencies:
@angular/core22.0.0@molecule/app-auth^1.0.1@molecule/app-device^1.0.1@molecule/app-forms^1.0.1@molecule/app-http^1.0.1@molecule/app-i18n^1.0.1@molecule/app-logger^1.0.1@molecule/app-platform^1.0.1@molecule/app-push^1.0.1@molecule/app-routing^1.0.1@molecule/app-state^1.0.1@molecule/app-storage^1.0.1@molecule/app-theme^1.0.1@molecule/app-ui^1.0.1@molecule/app-utilities^1.0.1@molecule/app-version^1.0.1rxjs^7.8.0
Runtime Dependencies
-
@angular/core -
@molecule/app-auth -
@molecule/app-device -
@molecule/app-forms -
@molecule/app-http -
@molecule/app-i18n -
@molecule/app-logger -
@molecule/app-platform -
@molecule/app-push -
@molecule/app-routing -
@molecule/app-state -
@molecule/app-storage -
@molecule/app-theme -
@molecule/app-ui -
@molecule/app-utilities -
@molecule/app-version -
rxjs -
Peer requirements: Angular 22 (
@angular/coreis pinned to 22.0.0) and rxjs 7.8+. -
Translations: import
tfrom THIS package, not from@molecule/app-i18n. The re-exportedtreads an internal Angular signal, so template bindings that CALL it (expose it on the component as above) re-evaluate automatically when the locale changes; the plain app-i18nt— or a one-time field assignment liketitle = t(...)— renders once and goes stale. The signal is bumped byprovideMolecule— pass youri18nprovider there (or callbumpLocaleVersion()from your own locale-change hook) for the reactivity to fire. -
provideMoleculeonly registers the providers you pass; injecting a Molecule service whose token was never provided fails at DI time. Per-concern helpers (provideAuth,provideTheme, ...) exist for piecemeal setup.