Advanced
Database

Database

The app uses RXDB (opens in a new tab) to handle data querying and persistence

Import

The database module should first be imported into the main app module, calling the forRoot method

import { PicsaDb_V2_Module } from "@picsa/shared/modules";
@NgModule({
  imports: [PicsaDb_V2_Module.forRoot()],
})
export class AppModule {}

If the database is used in any child or lazy modules it should be imported regularly

import { PicsaDb_V2_Module } from "@picsa/shared/modules";
@NgModule({
  imports: [PicsaDb_V2_Module],
})
export class LazyModule {}

Once imported the database service can be used to create collections or query data

import { PicsaDatabase_V2_Service } from "@picsa/shared/services/core/db_v2";
 
export class MyService {
  constructor(private dbService: PicsaDatabase_V2_Service) {}
}

Schemas

In order to interact with data we first need to create a schema. A utility method exists to support registering schemas

In the example below we create a new database collection which supports entries with user_name and favourite_food properties

// Define schema to represent data
const schema = {
    schema: {
      version: 0,
      type: "object",
      properties: {
        user_name:{
          type: "string",
        }
        favourite_food: {
          type: "string",
        }
      },
    },
  }
// Register schema with database
await this.dbService.ensureCollections({
  my_collection:{schema}
})

For more info, see https://rxdb.info/rx-schema.html (opens in a new tab)

Querying Data

All data lives in collections. These can be queried using the find() method

const collection = this.dbService.db.collection["my_collection"];
const query = collection.find();

Once created a query can either be executed once to return all results, or a live-query created to stream new results whenever they change

// snapshot
this.data = await query.exec();
 
// or live-query
query.$.subscribe((v) => (this.data = v));

For more info, see https://rxdb.info/rx-query.html (opens in a new tab)

Advanced Use

Type Safety

We can use type interfaces both when registering a collection to ensure all properties are defined

import { RxJsonSchema } from 'rxdb';
 
interface ICollectionEntry {
  user_name:string;
  favourite_food:string
}
 
// Use the interface above to ensure properties are defined for the schema
const schema: RxJsonSchema<ICollectionEntry> = {
    schema: {
      version: 0,
      type: "object",
      properties: {
        user_name:{
          type: "string",
        }
        favourite_food: {
          type: "string",
        }
      },
    },
  }

We can also use typings when accessing a collection for queries

const collection = this.dbService.db.collection[
  "my_collection"
] as RxCollection<ICollectionEntry>;
 
// query will autocomplete to provide properties from the collection
this.data = await collection
  .find({ selector: { favourite_food: { $eq: "pizza" } } })
  .exec();

Migrations

(todo)

  • Ensure schema name versioned
  • Keep all schemas in codebase
  • Add migration functions

Avoiding data leaks

(todo)

User profile linking

(todo) Ideally all user-generated data should be linked to a user id Or separate DBs used for user

Server Sync

(todo) This may be possible through tools like https://github.com/marceljuenemann/rxdb-supabase (opens in a new tab)