Reader Stacks

How to Store Data in the Browser's Local Storage With Angular

localStorage is plain browser JavaScript, not an Angular-specific API — wrapping it in a service, and remembering it only stores strings, are the two practical details that matter.

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.