Skip to content

A service that wraps IndexedDB database in an Angular service. It exposes very simple observables API to enable the usage of IndexedDB without most of it plumbing.

License

Notifications You must be signed in to change notification settings

assuncaocharles/ngx-indexed-db

Repository files navigation

ngx-indexed-db

All Contributors

Known Vulnerabilities CodeFactor Build Status CI

ngx-indexed-db is a service (CSR & SSR) that wraps IndexedDB database in an Angular service combined with the power of observables.

Installation

$ npm install ngx-indexed-db

OR

$ yarn add ngx-indexed-db

Usage

With Module

Import the NgxIndexedDBModule and initiate it:

import { NgxIndexedDBModule, DBConfig } from 'ngx-indexed-db';

const dbConfig: DBConfig  = {
  name: 'MyDb',
  version: 1,
  objectStoresMeta: [{
    store: 'people',
    storeConfig: { keyPath: 'id', autoIncrement: true },
    storeSchema: [
      { name: 'name', keypath: 'name', options: { unique: false } },
      { name: 'email', keypath: 'email', options: { unique: false } }
    ]
  }]
};

@NgModule({
  ...
  imports: [
    ...
    NgxIndexedDBModule.forRoot(dbConfig)
  ],
  ...
})

With Standalone API

Use provideIndexedDb and set it up:

import { provideIndexedDb, DBConfig } from 'ngx-indexed-db';

const dbConfig: DBConfig  = {
  name: 'MyDb',
  version: 1,
  objectStoresMeta: [{
    store: 'people',
    storeConfig: { keyPath: 'id', autoIncrement: true },
    storeSchema: [
      { name: 'name', keypath: 'name', options: { unique: false } },
      { name: 'email', keypath: 'email', options: { unique: false } }
    ]
  }]
};

const appConfig: ApplicationConfig = {
  providers: [...,provideIndexedDb(dbConfig),...]
}

OR

@NgModule({
  ...
 providers:[
    ...
    provideIndexedDb(dbConfig)
  ],
  ...
})

SSR

Starting from version 19.2.0, ngx-indexed-db fully supports Server-Side Rendering (SSR). This enhancement prevents issues related to the absence of window.indexedDB in server environments.

Additionally, you can provide a custom implementation of IndexedDB using an injection token. This allows greater flexibility, especially when mocking IndexedDB for testing or in non-browser environments (like SSR).

const SERVER_INDEXED_DB = new InjectionToken<IDBFactory>('Server Indexed Db');

Migrations

import { NgxIndexedDBModule, DBConfig } from 'ngx-indexed-db';

// Ahead of time compiles requires an exported function for factories
export function migrationFactory() {
  // The animal table was added with version 2 but none of the existing tables or data needed
  // to be modified so a migrator for that version is not included.
  return {
    1: (db, transaction) => {
      const store = transaction.objectStore('people');
      store.createIndex('country', 'country', { unique: false });
    },
    3: (db, transaction) => {
      const store = transaction.objectStore('people');
      store.createIndex('age', 'age', { unique: false });
    }
  };
}

const dbConfig: DBConfig  = {
  name: 'MyDb',
  version: 3,
  objectStoresMeta: [{
    store: 'people',
    storeConfig: { keyPath: 'id', autoIncrement: true },
    storeSchema: [
      { name: 'name', keypath: 'name', options: { unique: false } },
      { name: 'email', keypath: 'email', options: { unique: false } }
    ]
  }, {
    // animals added in version 2
    store: 'animals',
    storeConfig: { keyPath: 'id', autoIncrement: true },
    storeSchema: [
      { name: 'name', keypath: 'name', options: { unique: true } },
    ]
  }],
  // provide the migration factory to the DBConfig
  migrationFactory
};

@NgModule({
  ...
  imports: [
    ...
    NgxIndexedDBModule.forRoot(dbConfig)
  ],
  ...
})

NgxIndexedDB service

Import and inject the service:

import { NgxIndexedDBService } from 'ngx-indexed-db';

...
  export class AppComponent {
    #dbService = inject(NgxIndexedDBService);
  }

API

We cover several common methods used to work with the IndexedDB

add(storeName: string, value: T, key?: any): Observable<T & {id: any}>

Adds new entry in the store and returns item added

  • @param storeName The name of the store to add the item
  • @param value The entry to be added
  • @param key The optional key for the entry

It publishes in the observable the key value of the entry

this.dbService
  .add('people', {
    name: `Bruce Wayne`,
    email: `[email protected]`,
  })
  .subscribe((key) => {
    console.log('key: ', key);
  });

In the previous example I'm using undefined as the key because the key is configured in the objectStore as auto-generated.

bulkAdd(storeName: string, values: Array<T & { key?: any }>): Observable<number[]>

Adds new entries in the store and returns its key

  • @param storeName The name of the store to add the item
  • @param values The entries to be added containing optional key attribute
this.dbService
  .bulkAdd('people', [
    {
      name: `charles number ${Math.random() * 10}`,
      email: `email number ${Math.random() * 10}`,
    },
    {
      name: `charles number ${Math.random() * 10}`,
      email: `email number ${Math.random() * 10}`,
    },
  ])
  .subscribe((result) => {
    console.log('result: ', result);
  });

bulkDelete(storeName: string, keys: Key[]): Observable<number[]>

Delete multiple items in the store

  • @param storeName The name of the store to delete the items
  • @param keys The entries keys to be deleted
  this.dbService.bulkDelete('people', [5, 6]).subscribe((result) => {
    console.log('result: ', result);
  });

bulkGet(storeName: string, keys: Array): Observable<T[]>

Retrieve multiple entries in the store

  • @param storeName The name of the store to retrieve the items
  • @param keys The ids entries to be retrieve
this.dbService.bulkGet('people', [1, 3, 5]).subscribe((result) => {
    console.log('results: ', result);
  });

bulkPut(storeName: string, values: Array<T & { key?: any }>): Observable<number[]>

Adds or updates a record in store with the given value and key. Return all items present in the store

  • @param storeName The name of the store to update
  • @param items The values to update in the DB

@Return The return value is an Observable with the primary key of the object that was last in given array

@error If the call to bulkPut fails the transaction will be aborted and previously inserted entities will be deleted

this.dbService.bulkPut('people', people).subscribe((result) => {
  console.log('result: ', result);
});

update(storeName: string, value: T): Observable<T[]>

Adds or updates a record in store with the given value and key. Return item updated

  • @param storeName The name of the store to update
  • @param value The new value for the entry
this.dbService
  .update('people', {
    id: 1,
    email: '[email protected]',
    name: 'Luke Skywalker',
  })
  .subscribe((storeData) => {
    console.log('storeData: ', storeData);
  });

getByKey(storeName: string, key: IDBValidKey): Observable

Returns entry by key.

  • @param storeName The name of the store to query
  • @param key The entry key
this.dbService.getByKey('people', 1).subscribe((people) => {
  console.log(people);
});

getAll(storeName: string): Observable<T[]>

Return all elements from one store

  • @param storeName The name of the store to select the items
this.dbService.getAll('people').subscribe((peoples) => {
  console.log(peoples);
});

getByIndex(storeName: string, indexName: string, key: IDBValidKey): Observable

Returns entry by index.

  • @param storeName The name of the store to query
  • @param indexName The index name to filter
  • @param key The entry key.
this.dbService.getByIndex('people', 'name', 'Dave').subscribe((people) => {
  console.log(people);
});

createObjectStore(storeSchema: ObjectStoreMeta, migrationFactory?: () => { [key: number]: (db: IDBDatabase, transaction: IDBTransaction) => void }): void

Allows to crate a new object store ad-hoc

  • @param storeName The name of the store to be created
  • @param migrationFactory The migration factory if exists
const storeSchema: ObjectStoreMeta = {
  store: 'people',
  storeConfig: { keyPath: 'id', autoIncrement: true },
  storeSchema: [
    { name: 'name', keypath: 'name', options: { unique: false } },
    { name: 'email', keypath: 'email', options: { unique: false } },
  ],
};

this.dbService.createObjectStore(storeSchema);

count(storeName: string, query?: IDBValidKey | IDBKeyRange): Observable

Returns the number of rows in a store.

  • @param storeName The name of the store to query
  • @param query The key or key range criteria to apply.
this.dbService.count('people').subscribe((peopleCount) => {
  console.log(peopleCount);
});

countByIndex(storeName: string, indexName: string, query?: IDBValidKey | IDBKeyRange): Observable

Returns the number of records within a key range.

  • @param storeName The name of the store to query
  • @param indexName The index name to filter
  • @param query The key or key range criteria to apply.
this.dbService.countByIndex('people', 'email').subscribe((peopleCount) => {
  console.log(peopleCount);
});

deleteObjectStore(storeName: string): Observable

Delete the store by name.

  • @param storeName The name of the store to query
this.dbService.deleteObjectStore(this.storneNameToDelete);

delete(storeName: string, key: Key): Observable<T[]>

Returns all items from the store after delete.

  • @param storeName The name of the store to have the entry deleted
  • @param key The key of the entry to be deleted
this.dbService.delete('people', 3).subscribe((allPeople) => {
  console.log('all people:', allPeople);
});

deleteByKey(storeName: string, key: Key): Observable

Returns if the delete completes successfully.

  • @param storeName The name of the store to have the entry deleted
  • @param key The key of the entry to be deleted
this.dbService.deleteByKey('people', 3).subscribe((status) => {
  console.log('Deleted?:', status);
});

openCursor<V = any, P extends IDBValidKey = IDBValidKey, K extends IDBValidKey = IDBValidKey>({

storeName: string, query?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection, mode: DBMode = DBMode.readonly }): Observable<NgxIDBCursorWithValue<V, P, K>>

Opens a cursor. If no matching data are present, the observable is completed immediately.

  • @param options The options to open the cursor
  • @param options.storeName The name of the store to have the entries deleted
  • @param options.query The key or key range criteria to apply
  • @param options.direction A string telling the cursor which direction to travel
  • @param options.mode The transaction mode.
this.dbService.openCursor('people', IDBKeyRange.bound("A", "F")).subscribe((cursor) => {
  console.log(cursor.value);
});

openCursorByIndex<V = any, P extends IDBValidKey = IDBValidKey, K extends IDBValidKey = IDBValidKey>({

storeName: string, indexName: string, query?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection, mode: DBMode = DBMode.readonly }): Observable<NgxIDBCursorWithValue<V, P, K>>

Open a cursor by index filter. If no matching data are present, the observable is completed immediately.

  • @param options The options to open the cursor
  • @param options.storeName The name of the store to query
  • @param options.indexName The index name to filter
  • @param options.query The key or key range criteria to apply
  • @param options.direction A string telling the cursor which direction to travel
  • @param options.mode The transaction mode.
this.dbService.openCursorByIndex('people', 'name', IDBKeyRange.only('john')).subscribe((cursor) => {
  console.log(cursor.value);
});

getAllByIndex(storeName: string, indexName: string, query?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection): Observable<T[]>

Returns all items by an index.

  • @param storeName The name of the store to query
  • @param indexName The index name to filter
  • @param query The key or key range criteria to apply
  • @param direction A string telling the cursor which direction to travel.
this.dbService.getAllByIndex('people', 'name', IDBKeyRange.only('john')).subscribe((allPeopleByIndex) => {
  console.log('All: ', allPeopleByIndex);
});

getAllKeysByIndex

(storeName: string, indexName: string, query?: IDBValidKey | IDBKeyRange | null, direction?: IDBCursorDirection): Observable<IndexKey<P, K>[]>

Returns all items by an index.

  • @param storeName The name of the store to query
  • @param indexName The index name to filter
  • @param query The range value and criteria to apply on the index
  • @param direction A string telling the cursor which direction to travel.
this.dbService.getAllKeysByIndex('people', 'name', IDBKeyRange.only('john')).subscribe((keys) => {
  console.log(keys);
});

getDatabaseVersion(): Observable

Returns the current database version.

this.dbService.getDatabaseVersion().pipe(
  tap(response => console.log('Versione database => ', response)),
  catchError(err => {
    console.error('Error recover version => ', err);
    return throwError(err);
  })
).subscribe();

clear(storeName: string): Observable

Returns true if successfully delete all entries from the store.

  • @param storeName The name of the store to have the entries deleted
this.dbService.clear('people').subscribe((successDeleted) => {
  console.log('success? ', successDeleted);
});

deleteDatabase(): Observable

Returns true if successfully delete the DB.

this.dbService.deleteDatabase().subscribe((deleted) => {
  console.log('Database deleted successfully: ', deleted);
});

getAllObjectStoreNames(): Observable<string[]>

Returns all object store names.

this.dbService.getAllObjectStoreNames().subscribe((storeNames) => {
  console.log('storeNames: ', storeNames);
});

License

Released under the terms of the MIT License.

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Angelo Parziale

🚧 💻

Charles Assunção

💻 📖 🚧

coolweb

🚧

This project follows the all-contributors specification. Contributions of any kind welcome!

About

A service that wraps IndexedDB database in an Angular service. It exposes very simple observables API to enable the usage of IndexedDB without most of it plumbing.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published