Browser localStorage is plain JavaScript, not an Angular-specific API — using it from an Angular app is mostly about wrapping it in a service for testability, and remembering it can only ever store strings.
Basic localStorage usage
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme'); // 'dark', or null if never set
localStorage.removeItem('theme');
localStorage.clear(); // removes everything
Wrapping it in an injectable service
@Injectable({ providedIn: 'root' })
export class LocalStorageService {
set(key: string, value: any): void {
localStorage.setItem(key, JSON.stringify(value));
}
get(key: string): T | null {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}
remove(key: string): void {
localStorage.removeItem(key);
}
}
Wrapping direct localStorage calls in a service, rather than calling it from components directly, keeps storage access mockable in unit tests and gives one place to change the underlying storage mechanism later if needed (switching to sessionStorage, or adding encryption, for instance).
Storing objects and arrays, not just strings
this.storage.set('user', { name: 'Alex', id: 42 });
const user = this.storage.get<{ name: string; id: number }>('user');
localStorage can only store strings natively — JSON.stringify()/JSON.parse() in the service above is what allows storing and retrieving actual objects and arrays transparently, rather than the raw string "[object Object]" that direct storage of an object would otherwise produce.
Handling server-side rendering (Angular Universal) safely
import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID, Inject } from '@angular/core';
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
set(key: string, value: any): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(key, JSON.stringify(value));
}
}
localStorage doesn't exist in a server-side rendering context (there's no browser there) — checking isPlatformBrowser() before touching it prevents a genuine runtime error when the same code runs during server-side rendering with Angular Universal.
Real limitations worth knowing
localStorage has a storage size limit (typically around 5-10MB depending on the browser), is synchronous (which can briefly block the main thread for very large reads/writes), isn't accessible across different origins/subdomains, and offers no built-in expiration — for larger amounts of data, or genuine expiration needs, IndexedDB or a server-side session are usually more appropriate tools than stretching localStorage beyond what it's well suited for.
Security note: never store sensitive data in localStorage
localStorage is readable by any JavaScript running on the page, including a successful XSS attack's injected script — genuinely sensitive data like an auth token is generally safer in an httpOnly cookie (which JavaScript can't read at all) than in localStorage, a real security trade-off worth understanding before defaulting to localStorage for authentication state specifically.