Enhance storedSignal function to accept an Injector for improved context handling in localStorage persistence
This commit is contained in:
1 parent
275512d443
commit
7ffa57748b
2 files changed
+37
-23
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { Injectable, WritableSignal } from '@angular/core';
|
||||
import { Injectable, WritableSignal, inject, Injector } from '@angular/core';
|
||||
import { storedSignal } from '../shared/utils/stored-signal';
|
||||
|
||||
interface SettingDefinition {
|
||||
@@ -10,6 +10,8 @@ interface SettingDefinition {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CalendarSettingsService {
|
||||
private readonly injector = inject(Injector);
|
||||
|
||||
private readonly definitions = new Map<string, SettingDefinition>([
|
||||
[
|
||||
'showWeekNumbers',
|
||||
@@ -38,7 +40,7 @@ export class CalendarSettingsService {
|
||||
constructor() {
|
||||
for (const [id, def] of this.definitions) {
|
||||
const storageKey = `calendarSettings.${id}`;
|
||||
this.signals.set(id, storedSignal(storageKey, def.defaultValue));
|
||||
this.signals.set(id, storedSignal(storageKey, def.defaultValue, this.injector));
|
||||
}
|
||||
|
||||
this.settingsArray = Array.from(this.definitions.entries()).map(([id, def]) => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { signal, effect, WritableSignal } from '@angular/core';
|
||||
import { signal, effect, WritableSignal, Injector, runInInjectionContext } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Checks if localStorage is available in the current environment.
|
||||
@@ -24,24 +24,31 @@ function isLocalStorageAvailable(): boolean {
|
||||
* @template T - The type of value stored in the signal (must be JSON-serializable)
|
||||
* @param key - The localStorage key used to store the value
|
||||
* @param initialValue - The default value used when no stored value exists
|
||||
* @param injector - The Angular injector (use inject(Injector) to get current context)
|
||||
* @param debounceMs - Optional debounce delay in milliseconds (default: 300ms)
|
||||
* @returns A writable signal that persists to localStorage
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const darkMode = storedSignal('theme.darkMode', false);
|
||||
* const injector = inject(Injector);
|
||||
* const darkMode = storedSignal('theme.darkMode', false, injector);
|
||||
* darkMode.set(true); // Automatically saved to localStorage after 300ms
|
||||
* console.log(darkMode()); // true
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* - Must be called within an injection context (uses Angular's `effect`)
|
||||
* - Requires an Injector to create the effect in the proper context
|
||||
* - Values must be JSON-serializable (no functions, circular references, etc.)
|
||||
* - In SSR or when localStorage is unavailable, the signal works normally but won't persist
|
||||
* - Corrupted localStorage data is automatically cleared and reset to initial value
|
||||
* - localStorage writes are debounced to improve performance with frequent updates
|
||||
*/
|
||||
export function storedSignal<T>(key: string, initialValue: T, debounceMs = 300): WritableSignal<T> {
|
||||
export function storedSignal<T>(
|
||||
key: string,
|
||||
initialValue: T,
|
||||
injector: Injector,
|
||||
debounceMs = 300,
|
||||
): WritableSignal<T> {
|
||||
let parsedValue = initialValue;
|
||||
|
||||
if (isLocalStorageAvailable()) {
|
||||
@@ -67,25 +74,30 @@ export function storedSignal<T>(key: string, initialValue: T, debounceMs = 300):
|
||||
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
effect(() => {
|
||||
const value = s();
|
||||
runInInjectionContext(injector, () => {
|
||||
effect(() => {
|
||||
const value = s();
|
||||
|
||||
// Clear any pending timeout
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// Debounce the localStorage write
|
||||
timeoutId = setTimeout(() => {
|
||||
if (isLocalStorageAvailable()) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.warn(`Failed to save value for key "${key}" to localStorage.`, error);
|
||||
}
|
||||
// Clear any pending timeout
|
||||
if (timeoutId !== null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = null;
|
||||
}, debounceMs);
|
||||
|
||||
// Debounce the localStorage write
|
||||
timeoutId = setTimeout(() => {
|
||||
if (isLocalStorageAvailable()) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to save value for key "${key}" to localStorage.`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
timeoutId = null;
|
||||
}, debounceMs);
|
||||
});
|
||||
});
|
||||
|
||||
return s;
|
||||
|
||||
Reference in new issue
Block a user