npm package: @jvccode/firestore-factory
Description: Allows you to easily get firestores instances created for many firebase projects
Latest version: 19.0.1

# Firestore-Factory

Firestore-Factory is a library that allows you to connect to multiple Firebase projects and multiple Firestore instances (databases).
This library was built with version 18.0.1 of [Angular CLI](https://github.com/angular/angular-cli) and version 18.0.1 of @angular/fire.
This library is in development, so not all features may work correctly yet.

## How does it works

1.- Run `npm i @jvccode/firestore-factory` in your project to get the library.

2.- This library requires a configuration object that you can add into environment.ts file.

the "firebaseFactoryProviderConfig" object is an array of firebase project configurations as follows:

export const environment = {
    production: false,
    firebaseFactoryProviderConfig: [
        // the below config is the basic configuration
        // the first config will be the default configuration
        {
            // projectName and firebaseConfig are required and is the data you get from the firebase console
            projectName: 'projectName1', 
            firebaseConfig: {
                apiKey: 'xxxxxxxxxxx',
                authDomain: 'xxxxxxxxxxx',
                databaseURL: 'xxxxxxxxxxx',
                projectId: 'xxxxxxxxxxx',
                storageBucket: 'xxxxxxxxxxx',
                messagingSenderId: 'xxxxxxxxxxx',
                appId: 'xxxxxxxxxxx',
                measurementId: 'xxxxxxxxxxx'
            }
        },
        // the second config contains all of the posible values you can set
        {
            // projectName and firebaseConfig are required and is the data you get from the firebase console
            projectName: 'projectName2',
            firebaseConfig: {
                apiKey: 'xxxxxxxxxxx',
                authDomain: 'xxxxxxxxxxx',
                databaseURL: 'xxxxxxxxxxx',
                projectId: 'xxxxxxxxxxx',
                storageBucket: 'xxxxxxxxxxx',
                messagingSenderId: 'xxxxxxxxxxx',
                appId: 'xxxxxxxxxxx',
                measurementId: 'xxxxxxxxxxx'
            },
            // if you include authentication then firebase authentication will be provided as a service
            authentication: {
                enabled: true,
                // emulatorConfig is optional
                emulatorConfig: {
                    url: 'url'
                }
            },
            // if you include realTimeDatabase then firebase realTimeDatabase will be provided as one instance (default)
            realTimeDatabase: {
                enabled: true,
                // emulatorConfig is optional
                emulatorConfig: {
                    host: 'localhost',
                    port: "8090",
                    // options is optional and could the values be: mockUserToken?: string | EmulatorMockTokenOptions | undefined
                    options: {mockUserToken:"xxxxxxx"}
                }
            },
            // if you include storage then firebase storage will be provided as a service
            storage: {
                enabled: true,
                // emulatorConfig is optional
                emulatorConfig: {
                    host: 'localhost',
                    port: "8091",
                    // options is optional and could the values be: mockUserToken?: string | EmulatorMockTokenOptions | undefined
                    options: {mockUserToken:"xxxxxxx"}
                }
            },
            // if you include firestoreDatabase then firebase firestoreDatabase will be provided as a service
            // you need to specify the instances (firestore databases) you want to connect
            // you need to provide the databaseId for each one, and for the default use 'default' as the id.
            // you can also provide a name so that will be easy to identify your database your wannt to interact with.
            // if you don't provide a name, the databaseId will be used as a name
            // ** if you don't include firestoreDatabase, the framework will create the default instance
            firestoreDatabase: {
                instances: [
                    {
                        // by default firebase firestore creates the first instance with database id = 'default'
                        databaseId: 'default',
                        databaseName: 'defaultName',
                        // this is optional and if provided as true, the will enable the Multi Tab Persistency
                        enableMultiTabPersistency: true,
                        // emulatorConfig is optional
                        emulatorConfig: {
                            host: 'localhost',
                            port: "8092",
                            // options is optional and could the values be: mockUserToken?: string | EmulatorMockTokenOptions | undefined
                            options: {mockUserToken:"xxxxxxx"}
                        }
                    },
                    {
                        databaseId: 'SecondDatabase',
                        databaseName: 'lookups',
                        enableMultiTabPersistency: true,
                    },
                    {
                        databaseId: 'thirdDatabase',
                        databaseName: 'settings'
                    }
                ]
            },
            // if you include any of the below configurations then firebase RemoteConfig, Analitycs, Messaging, etc will be provided as one instance
            enableRemoteConfig: true,
            enableAnalitycs: true,
            enableMessaging: true,
            enablePerformance: true,
            enableFunctions: true,
            enableAppCheck: true,
            enableVertexAI: true
        }
    ]
};

3.- if you are using modules in your angular application, then add the following in your app.module.ts file:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FirestoreFactoryModule } from '@jvccode/firestore-factory';

import { environment } from 'src/environments/environment';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FirestoreFactoryModule.initializeFactoryService(environment.firebaseFactoryProviderConfig)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

4.- if you are using standalone version then do the following:

in app.config.ts file, add the initializer in the providers section  as follows:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }), 
    provideRouter(routes), 
    importProvidersFrom(FirestoreFactoryModule.initializeFactoryService(environment.firebaseFactoryProviderConfig)),
    provideAnimations()
  ]
};

## How to use it

FirestoreFactoryModule exposes a service called FirebaseFactoryService, so you can inject it into the constructor of your component/class:

  constructor(private firebaseFactory: FirebaseFactoryService){}

The FirebaseFactoryService exposes the method getServiceProvider(projectName?: string), this method will return the firebaseProvider for the corresponding firebase project. If you don't provide any project name, by default the FirebaseFactoryService will return the first instance of the firebaseFactoryProviderConfig.

  const myFirstFirebaseProjectInstance = this.firebaseFactory.getServiceProvider('projectName1'); 
  OR
  const myFirstFirebaseProjectInstance = this.firebaseFactory.getServiceProvider();  both will return the first firebase instance as "projectName1" is the first configuration:

     firebaseFactoryProviderConfig: [
        // the below config is the basic configuration
        // the first config will be the default configuration
        {
            // projectName and firebaseConfig are required and is the data you get from the firebase console
            projectName: 'projectName1', 
            firebaseConfig: {
                apiKey: 'xxxxxxxxxxx',
                authDomain: 'xxxxxxxxxxx',
                databaseURL: 'xxxxxxxxxxx',
                projectId: 'xxxxxxxxxxx',
                storageBucket: 'xxxxxxxxxxx',
                messagingSenderId: 'xxxxxxxxxxx',
                appId: 'xxxxxxxxxxx',
                measurementId: 'xxxxxxxxxxx'
            }
        },
        // the second config contains all of the posible values you can set
        {
            // projectName and firebaseConfig are required and is the data you get from the firebase console
            projectName: 'projectName2',
            firebaseConfig: {
                apiKey: 'xxxxxxxxxxx',
                authDomain: 'xxxxxxxxxxx',
                databaseURL: 'xxxxxxxxxxx',
                projectId: 'xxxxxxxxxxx',
                storageBucket: 'xxxxxxxxxxx',
                messagingSenderId: 'xxxxxxxxxxx',
                appId: 'xxxxxxxxxxx',
                measurementId: 'xxxxxxxxxxx'
            },
    ...

  const myProject2 = this.firebaseFactory.getServiceProvider('projectName2'); // if you want to get another instance which is not the default one, the you will need to provide the name that matches the name in the firebaseFactoryProviderConfig

    // the second config contains all of the posible values you can set
        {
            // projectName and firebaseConfig are required and is the data you get from the firebase console
            projectName: 'projectName2',
            firebaseConfig: {
                apiKey: 'xxxxxxxxxxx',
                authDomain: 'xxxxxxxxxxx',
                databaseURL: 'xxxxxxxxxxx',
                projectId: 'xxxxxxxxxxx',
                storageBucket: 'xxxxxxxxxxx',
                messagingSenderId: 'xxxxxxxxxxx',
                appId: 'xxxxxxxxxxx',
                measurementId: 'xxxxxxxxxxx'
            },
    ...

Once you get the specific firebase service provider, then you can get the firestore collections providers.

    const myFirstFirebaseProjectInstance = this.firebaseFactory.getServiceProvider();

if you have only 1 database in your configuration, you can get the provider as:
    // fromFirestore expects the (dataBaseName?: string) param, so if you do not provide any database name, then the service will return the default instance
    const myUsersCollection = myFirstFirebaseProjectInstance.fromFirestore().getCollectionProvider('users');
    OR
    const myUsersCollection = myFirstFirebaseProjectInstance.fromFirestore().getCollectionProvider('users');

if you have multiple databases, then you need to provide the name of the database you want to get the collection reference:
    const myUsersCollection = myFirstFirebaseProjectInstance.fromFirestore('staging').getCollectionProvider('users');

With the collectionProvider you can query the data, below are some examples:

    this.myUsersCollection
      .getCollectionData()
      .subscribe( users => {
        console.log('whole users collection');
        console.log(users);
      });

    this.myUsersCollection
      .where('rol','==','Admin')
      .getCollectionSnapShot()
      .subscribe( users => {
        console.log('Users where rol == admin');
        // this response includes the complete firestore snapshot data like "changeType", "id", etc
      });

    this.myUsersCollection
      .getCollectionSnapShot({ orderBy: {propertyName:'firstName', sortOrder:"desc"}})
      .subscribe( users => {
        console.log('Snapshot -> users with filters=null (no where statement), includedId= undefined, orderby firstName, desc');
        console.log(users);
      });

    this.myUsersCollection
      .where('rol','==','Agent')
      .getCollectionData({ includeId: true, orderBy: {propertyName:'firstName', sortOrder:"asc"}})
      .subscribe( users => {
        console.log('users with filters= role: Agente, includedId= true, orderby firstName, asc');
        console.log(users);
      });
    
     this.myUsersCollection
      .where('rol','==','Operator')
      .getFirstOrDefault({ includeId: true, orderBy: {propertyName:'firstName', sortOrder:"asc"}})
      .subscribe( users => {
        console.log('users first or default');
        console.log(users);
      });

      this.myUsersCollection
      .where('isActive', '==',true)
      .getCollectionDataGroupedBy({
        includeId:true,
        groupBy:{
          properties:['rol', 'emailVerified'], 
          orderBy: {
            propertyName:'firstName', 
            sortOrder:"asc"
          },
          sortGroupOrder:'asc'
        }
      })
      .subscribe( users => {
        console.log('users grouped by [rol, emailVerified]');
        console.log(users);
      });