Ionic Storage vs LocalStorage — Difference and When to Use Each

 

Ionic Storage vs LocalStorage — What's the Difference and When to Use What

In this article we going to learn the difference between Ionic Storage and LocalStorage — what each one does, how they work under the hood, and when to pick one over the other in our Ionic app.

Most developers start with LocalStorage. It's built into the browser, no setup needed, we just call localStorage.setItem() and move on. Works great for quick demos. Then the app goes to production on Android, users start complaining that their data disappears, or we try to store a large object and it silently fails. That's when we start looking for a better solution.

Ionic Storage is that better solution for mobile apps. It uses native storage engines when available — SQLite on mobile devices — and falls back to IndexedDB or LocalStorage in the browser. Same API everywhere, much better performance and reliability.

Let's understand both properly and then figure out when each one is the right choice.

This tutorial shows how to:

  • Understand how LocalStorage works and its limitations
  • Understand how Ionic Storage works and what it uses under the hood
  • Install and configure Ionic Storage with the SQLite driver
  • Use Ionic Storage to get, set, and remove data
  • Store complex objects and arrays
  • Understand data persistence, capacity, and security differences
  • Know when to use LocalStorage and when Ionic Storage is the better choice

How LocalStorage Works

LocalStorage is a Web Storage API built into every browser. It stores key-value pairs where both key and value must be strings. It's synchronous — meaning every read and write blocks the main thread while it runs.

// Storing data
localStorage.setItem('username', 'amit');
localStorage.setItem('theme', 'dark');

// Reading data
const username = localStorage.getItem('username');

// Removing data
localStorage.removeItem('username');

// Clear everything
localStorage.clear();

Storing objects requires JSON serialization :

const user = { name: 'Amit', role: 'admin' };
localStorage.setItem('user', JSON.stringify(user));

const stored = JSON.parse(localStorage.getItem('user') || '{}');

LocalStorage limitations :

  • 5-10 MB storage limit depending on the browser. On some mobile browsers even less. Trying to store more fails silently or throws a quota exceeded error.
  • Strings only — objects need JSON.stringify/parse on every read and write
  • Synchronous — blocks the UI thread. On older phones with a lot of data, this causes visible lag
  • Not available in Web Workers — can't access it from background threads
  • Cleared by browser — when users clear browser data, site data, or in private/incognito mode, LocalStorage is gone
  • Same data across tabs — all tabs for the same origin share the same LocalStorage. Can cause unexpected behaviour in some apps.

On iOS, Safari has an aggressive storage eviction policy. If the device is low on space and our app hasn't been used recently, iOS can clear LocalStorage without warning. Users lose their data with no explanation.


How Ionic Storage Works

Ionic Storage is a key-value storage library built specifically for Ionic and Capacitor apps. The important difference — it uses different storage engines depending on the platform.

The storage priority order is :

  1. SQLite (via @capacitor-community/sqlite driver) — used on Android and iOS on real devices. Fastest, most reliable, no size limits worth worrying about.
  2. IndexedDB — used in the browser and as a fallback. Much larger capacity than LocalStorage, asynchronous.
  3. LocalStorage — last resort fallback. Only used if nothing else is available.

This means our code is identical everywhere but on a real Android or iOS device, the data is stored in a proper SQLite database. That's important — SQLite data persists across app updates, doesn't get cleared when users clear browser cache, and can handle megabytes of data without complaints.

The Ionic Storage API is promise-based (async/await) which means it never blocks the main thread.


Step 1 : Install Ionic Storage

Install the package :

npm install @ionic/storage-angular

For the SQLite driver on native devices, install the Capacitor SQLite community plugin :

npm install @capacitor-community/sqlite
npx cap sync

Step 2 : Configure Ionic Storage in the App

In app.module.ts for NgModule apps :

import { IonicStorageModule } from '@ionic/storage-angular';
import { Drivers } from '@ionic/storage';

@NgModule({
  imports: [
    IonicStorageModule.forRoot({
      name: '__myappdb',
      driverOrder: [Drivers.SecureStorage, Drivers.IndexedDB, Drivers.LocalStorage]
    })
  ]
})
export class AppModule {}

For standalone Angular apps, configure in app.config.ts :

import { provideIonicAngular } from '@ionic/angular/standalone';
import { IonicStorageModule } from '@ionic/storage-angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideIonicAngular(),
    importProvidersFrom(
      IonicStorageModule.forRoot({
        name: '__myappdb',
        driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage]
      })
    )
  ]
};

The name is the database name. The driverOrder tells Ionic Storage which storage engines to try, in order. It picks the first available one.


Step 3 : Create a StorageService

Rather than injecting Storage directly into every component, wrap it in a service. This keeps storage logic in one place :

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage-angular';

@Injectable({ providedIn: 'root' })
export class StorageService {
  private _storage: Storage | null = null;

  constructor(private storage: Storage) {
    this.init();
  }

  private async init() {
    this._storage = await this.storage.create();
  }

  async set(key: string, value: any): Promise<void> {
    await this._storage?.set(key, value);
  }

  async get<T>(key: string): Promise<T | null> {
    return await this._storage?.get(key) ?? null;
  }

  async remove(key: string): Promise<void> {
    await this._storage?.remove(key);
  }

  async clear(): Promise<void> {
    await this._storage?.clear();
  }

  async keys(): Promise<string[]> {
    return await this._storage?.keys() ?? [];
  }
}

The init() method calls storage.create() which initialises the storage engine. We must call this before using any storage methods — the _storage variable is null until init completes. Wrapping in a service means we only call create() once for the whole app.


Step 4 : Store and Retrieve Data

Now use the service in our components :

import { Component, OnInit } from '@angular/core';
import { StorageService } from '../services/storage.service';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `
    <ion-content class="ion-padding">
      <ion-button (click)="saveUser()">Save User</ion-button>
      <ion-button (click)="loadUser()">Load User</ion-button>
      <p>Loaded user : {{ loadedUser | json }}</p>
    </ion-content>
  `
})
export class HomePage implements OnInit {
  loadedUser: any = null;

  constructor(private storageService: StorageService) {}

  async saveUser() {
    const user = {
      id: 1,
      name: 'Amit Gautam',
      email: 'amit@example.com',
      role: 'admin',
      preferences: {
        theme: 'dark',
        language: 'en',
        notifications: true
      }
    };

    await this.storageService.set('current_user', user);
    console.log('User saved');
  }

  async loadUser() {
    this.loadedUser = await this.storageService.get('current_user');
    console.log('Loaded user:', this.loadedUser);
  }
}

Notice we didn't call JSON.stringify() before saving or JSON.parse() after loading. Ionic Storage handles serialization automatically. We can store objects, arrays, numbers, booleans — anything. It comes back in the same type we stored it.


Step 5 : Practical Storage Examples

Storing auth token :

// Save token after login
await this.storageService.set('auth_token', response.token);
await this.storageService.set('token_expiry', response.expiresAt);

// Read token for API calls
const token = await this.storageService.get<string>('auth_token');

Storing user preferences :

const preferences = {
  theme: 'dark',
  language: 'en',
  fontSize: 'medium',
  notifications: true
};

await this.storageService.set('user_preferences', preferences);

// Later
const prefs = await this.storageService.get<typeof preferences>('user_preferences');

Storing a list of items for offline use :

// Cache product list
await this.storageService.set('products_cache', productList);
await this.storageService.set('products_cache_time', Date.now());

// Check cache before API call
const cachedProducts = await this.storageService.get<Product[]>('products_cache');
const cacheTime = await this.storageService.get<number>('products_cache_time');

const cacheAge = Date.now() - (cacheTime ?? 0);
const isCacheValid = cacheAge < 5 * 60 * 1000; // 5 minutes

if (cachedProducts && isCacheValid) {
  return cachedProducts;
} else {
  // Fetch fresh data from API
}

This cache pattern is very common in Ionic apps. We load data from storage on app start for instant display, then refresh from the API in the background.


Step 6 : Direct Comparison — LocalStorage vs Ionic Storage

Here's the same operation written both ways so we can see the difference directly :

Saving an object :

// LocalStorage — manual JSON handling
localStorage.setItem('user', JSON.stringify(userObject));

// Ionic Storage — automatic serialization
await this.storageService.set('user', userObject);

Reading an object :

// LocalStorage — manual JSON parsing + null check
const raw = localStorage.getItem('user');
const user = raw ? JSON.parse(raw) : null;

// Ionic Storage — returns the object directly
const user = await this.storageService.get<User>('user');

Handling errors :

// LocalStorage — throws QuotaExceededError silently in some browsers
localStorage.setItem('large_data', hugeString); // May fail silently

// Ionic Storage — async, catchable
try {
  await this.storageService.set('large_data', hugeObject);
} catch (error) {
  console.error('Storage failed:', error);
}

Storage Comparison Table

Feature LocalStorage Ionic Storage
Storage Engine Browser only SQLite / IndexedDB / LocalStorage
Capacity 5-10 MB 50MB+ (device dependent)
Data Types Strings only Any — objects, arrays, numbers
API Style Synchronous Async (Promise-based)
iOS Eviction Risk High Low (SQLite persists)
Works on Native Limited Full support
Setup Required None Yes — install + configure
JSON Handling Manual Automatic
Performance Blocking Non-blocking

When to Use LocalStorage

LocalStorage still has its place. Use it when :

  • We're building a pure web app that doesn't use Capacitor and runs only in the browser
  • We need to store very small amounts of data — a theme preference, a flag, a language setting — maybe a few hundred bytes
  • We need data to be accessible immediately, synchronously, without async/await
  • We're building a quick prototype and don't want to spend time on storage setup
// Totally fine for small, simple values in a web app
localStorage.setItem('preferred_theme', 'dark');
localStorage.setItem('accepted_cookies', 'true');

For these simple cases, setting up Ionic Storage is overkill.


When to Use Ionic Storage

Use Ionic Storage when :

  • We're building a native mobile app with Capacitor for Android or iOS
  • We need to store objects, arrays, or complex data without manual serialization
  • We need to store more than a few KB of data — user data, cached API responses, offline content
  • We need data to survive app updates and device restarts reliably
  • We need async storage operations that don't block the UI thread
  • We're storing auth tokens and sensitive session data that must not disappear unexpectedly

If our Ionic app is going to the Play Store or App Store — use Ionic Storage. Period. The reliability difference on real mobile devices is significant.


Common Issues

Storage returns null on first load

storage.create() is async. If we call get() before create() finishes, we get null back. Our StorageService init() handles this by calling create() in the constructor. But if a component calls get() before the service finishes initialising, the storage might not be ready yet. Add a ready check or use a BehaviorSubject to signal when storage is initialized.

Data not persisting on iOS after app update

On very old versions of Ionic Storage using the deprecated Cordova SQLite plugin, data could be lost. Using the modern @ionic/storage-angular with @capacitor-community/sqlite driver, SQLite data persists across app updates on iOS and Android.

"Storage not created" error

We're calling storage methods before storage.create() has resolved. Make sure init() is awaited before any get/set calls. In our StorageService, the _storage null check handles this gracefully — if storage isn't ready yet, operations are silently skipped. For a more robust solution, queue operations until storage is ready.

Large data sets are slow

Ionic Storage is a key-value store — it's not designed for querying thousands of records. If we're storing large datasets that need filtering and sorting, consider using @capacitor-community/sqlite directly with SQL queries instead of the key-value Storage API.


Summary

We learned the difference between LocalStorage and Ionic Storage and when to use each. We covered :

  • How LocalStorage works — strings only, synchronous, 5-10 MB limit, eviction risk on iOS
  • How Ionic Storage works — uses SQLite on native, IndexedDB in browser, async API, any data type
  • Installing and configuring Ionic Storage with the SQLite driver
  • Creating a StorageService wrapper to initialize storage once and reuse everywhere
  • Storing and retrieving objects, arrays, and complex data without JSON.stringify/parse
  • Practical patterns — auth token storage, user preferences, offline cache with expiry
  • Direct code comparison showing the difference between the two APIs
  • Feature comparison table covering capacity, performance, data types, and iOS reliability
  • When LocalStorage is fine vs when Ionic Storage is the right choice
  • Common issues like null on first load and data not persisting

The short version — for anything beyond a tiny web app, use Ionic Storage. The setup cost is about 15 minutes and the reliability improvement on real mobile devices is worth it every time.

I hope you like this article...

Happy coding! 🚀

Tags: Ionic, LocalStorage, Ionic Storage, SQLite, IndexedDB, Capacitor, Angular, Mobile Storage, Offline Storage

Post a Comment

0 Comments