Documentation Index
Fetch the complete documentation index at: https://mintlify.com/akbarsaputrait/ngememoize/llms.txt
Use this file to discover all available pages before exploring further.
Ngememoize provides several tools to help you understand cache behavior, track performance, and debug issues.
Debug labels
The debugLabel option enables console logging for cache operations:
import { Ngememoize } from 'ngememoize';
@Ngememoize({
debugLabel: 'calculateSubtotal'
})
calculateSubtotal(price: number, quantity: number): number {
return price * quantity;
}
When debugLabel is set, Ngememoize logs:
[Memoize: calculateSubtotal] Cache miss for key: 10-5
[Memoize: calculateSubtotal] Cache hit for key: 10-5
[Memoize: calculateSubtotal] Delete Cache for key: 10-5
From the source code (ngememoize.decorator.ts:48-52):
if (options.debugLabel) {
console.debug(
`[Memoize: ${options.debugLabel}] Cache ${cacheStatus} for key: ${key}`,
);
}
When cache entries are deleted
When maxAge is set and an entry expires (ngememoize.decorator.ts:55-62):
if (options.maxAge && now - cached.timestamp > options.maxAge) {
cache.delete(key);
if (options.debugLabel) {
console.debug(
`[Memoize: ${options.debugLabel}] Delete Cache for key: ${key}`,
);
}
}
Cache callbacks
Use onCacheHit and onCacheMiss callbacks to track cache behavior programmatically.
onCacheHit callback
Executed when a cached value is returned:
@Ngememoize({
onCacheHit: (key) => {
console.log(`🎯 Cache HIT: Subtotal calculation for ${key}`);
}
})
calculateSubtotal(price: number, quantity: number): number {
return price * quantity;
}
onCacheMiss callback
Executed when the function needs to recompute:
@Ngememoize({
onCacheMiss: (key) => {
console.log(`📝 Cache MISS: Computing new subtotal for ${key}`);
}
})
calculateSubtotal(price: number, quantity: number): number {
return price * quantity;
}
Combining callbacks
Real example from the Ngememoize source (product.component.ts:64-71):
import { Component } from '@angular/core';
import { Ngememoize } from 'ngememoize';
@Component({
selector: 'app-product',
standalone: true,
})
export class ProductComponent {
@Ngememoize({
maxAge: 5000,
keyGenerator: (price, quantity) => `subtotal-${price}-${quantity}`,
onCacheHit: (key) =>
console.log(`🎯 Cache HIT: Subtotal calculation for ${key as string}`),
onCacheMiss: (key) =>
console.log(`📝 Cache MISS: Computing new subtotal for ${key as string}`),
})
calculateSubtotal(price: number, quantity: number): number {
console.log(
`💰 Computing subtotal for price: $${price} × ${quantity} items`,
);
return Number((price * quantity).toFixed(2));
}
}
Console output:
📝 Cache MISS: Computing new subtotal for subtotal-10-5
💰 Computing subtotal for price: $10 × 5 items
🎯 Cache HIT: Subtotal calculation for subtotal-10-5
Using getCacheStats()
The NgememoizeService provides the getCacheStats() method to retrieve cache statistics.
Injecting the service
import { Component, inject } from '@angular/core';
import { NgememoizeService } from 'ngememoize';
@Component({
selector: 'app-product',
standalone: true,
})
export class ProductComponent {
memoizeService = inject(NgememoizeService);
showStats() {
const stats = this.memoizeService.getCacheStats();
console.log('Cache Statistics:', stats);
}
}
CacheStats interface
The getCacheStats() method returns a record of statistics:
interface CacheStats {
size: number; // Current number of cached entries
lastAccessed: number; // Timestamp of most recent access
hitRate: number; // Percentage of cache hits (0-1)
missRate: number; // Percentage of cache misses (0-1)
}
Example output:
{
"ProductComponent_calculateSubtotal": {
"size": 3,
"lastAccessed": 1709481234567,
"hitRate": 0.75,
"missRate": 0.25
},
"ProductComponent_calculateDiscount": {
"size": 5,
"lastAccessed": 1709481234890,
"hitRate": 0.6,
"missRate": 0.4
}
}
Implementation details
From ngememoize.service.ts:67-84:
getCacheStats(): Record<string, CacheStats> {
const stats: Record<string, CacheStats> = {};
this.caches.forEach((cache, key) => {
const entries = Array.from(cache.values());
const cacheStats = this.stats.get(key) || { hits: 0, misses: 0 };
const total = cacheStats.hits + cacheStats.misses;
stats[key] = {
size: cache.size,
lastAccessed: Math.max(...entries.map(e => e.timestamp)),
hitRate: total ? cacheStats.hits / total : 0,
missRate: total ? cacheStats.misses / total : 0,
};
});
return stats;
}
Viewing all caches
Use getAllCache() to inspect cache contents:
const allCaches = this.memoizeService.getAllCache();
console.log('All Caches:', allCaches);
From product.component.ts:61:
calculateTotal() {
const startTime = performance.now();
// ... perform calculations ...
const endTime = performance.now();
console.log(
`⏱️ Total calculation time: ${(endTime - startTime).toFixed(2)}ms`,
);
console.log('Ngememoize Caches:', this.memoizeService.getAllCache());
}
Measuring execution time
Track performance improvements with performance.now():
calculateTotal() {
console.log('\n🧮 Starting price calculation...');
const startTime = performance.now();
this.subtotal = this.calculateSubtotal(
this.product.basePrice,
this.product.quantity,
);
this.discount = this.calculateDiscount(
this.subtotal,
this.product.discountCode ?? '',
this.product.quantity,
);
this.shippingCost = this.calculateShipping(
this.product.shipping ?? '',
this.subtotal,
);
this.total = this.subtotal - this.discount + this.shippingCost;
const endTime = performance.now();
console.log(
`⏱️ Total calculation time: ${(endTime - startTime).toFixed(2)}ms`,
);
}
Interpreting hit rates
- High hit rate (>0.7): Memoization is effective, many cache hits
- Medium hit rate (0.3-0.7): Some benefit from caching
- Low hit rate (<0.3): Consider adjusting
maxAge, maxSize, or keyGenerator
A low hit rate might indicate that your function is called with unique arguments frequently. This is normal for certain use cases.
Common issues and solutions
Issue: Cache never hits
Symptoms:
- Every call shows “Cache miss”
- Hit rate is 0%
Possible causes:
- Arguments are objects that serialize differently:
// Problem: Objects with same values serialize differently
this.calculate({ price: 10, qty: 5 }); // miss
this.calculate({ price: 10, qty: 5 }); // miss (different object reference)
Solution: Use a custom keyGenerator:
@Ngememoize({
keyGenerator: (params) => `${params.price}-${params.qty}`
})
calculate(params: { price: number; qty: number }): number {
return params.price * params.qty;
}
- Function includes timestamp or random values:
// Problem: timestamp changes every call
this.calculate(10, Date.now()); // Always misses
Solution: Exclude volatile parameters from key:
@Ngememoize({
keyGenerator: (value, timestamp) => `${value}` // Ignore timestamp
})
calculate(value: number, timestamp: number): number {
return value * 2;
}
Issue: Stale data returned
Symptoms:
- Old values returned when source data changes
Solution: Set maxAge to expire cache periodically:
@Ngememoize({
maxAge: 5000 // Refresh every 5 seconds
})
getData(id: string): Data {
return this.dataSource.get(id);
}
Issue: Memory usage grows over time
Symptoms:
- Application memory increases
- Many cache entries accumulate
Solution: Set maxSize to limit cache entries:
@Ngememoize({
maxSize: 100 // Keep only 100 most recent
})
searchProducts(query: string): Product[] {
return this.products.filter(p => p.name.includes(query));
}
Without maxSize or maxAge, caches grow indefinitely. Always set at least one limit for production code.
Issue: Cache identifier conflicts
Ngememoize generates cache identifiers using the pattern:
const cacheIdentifier = `${target.constructor.name}_${propertyKey}`;
From ngememoize.decorator.ts:134.
If you have multiple instances of the same class, they share the same cache. This is usually desired but can cause issues if instances should have separate caches.
Solution: Use dependency injection to ensure singleton behavior:
@Injectable({
providedIn: 'root', // Singleton
})
export class DataService {
@Ngememoize()
getData(id: string): Data {
// ...
}
}
Debugging workflow
Enable debug logging
Add debugLabel to your memoized methods:@Ngememoize({
debugLabel: 'calculatePrice'
})
calculatePrice(amount: number): number {
return amount * 1.1;
}
Check console output
Look for cache hit/miss patterns:[Memoize: calculatePrice] Cache miss for key: 100
[Memoize: calculatePrice] Cache hit for key: 100
[Memoize: calculatePrice] Cache hit for key: 100
Review cache statistics
Call getCacheStats() to see hit rates:const stats = this.memoizeService.getCacheStats();
console.log(stats);
Optimize configuration
Adjust options based on findings:
- Low hit rate: Add custom
keyGenerator
- Stale data: Decrease
maxAge
- Memory issues: Add
maxSize limit
Production debugging
For production environments, replace console.log with proper logging:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MetricsService {
recordCacheHit(key: string) {
// Send to analytics service
}
recordCacheMiss(key: string) {
// Send to analytics service
}
}
@Component({ /* ... */ })
export class ProductComponent {
metrics = inject(MetricsService);
@Ngememoize({
onCacheHit: (key) => this.metrics.recordCacheHit(key as string),
onCacheMiss: (key) => this.metrics.recordCacheMiss(key as string),
})
calculatePrice(amount: number): number {
return amount * 1.1;
}
}
Next steps