Search code examples
angularfire2firebase-tools

Can't get Firebase emulators to work with AngularFire 7


Good talk yesterday at the Firebase Summit about emulators! I was able to get the Functions emulator to work with AngularFire 6. I can't get the Firestore emulator or the Functions emulator to work with AngularFire 7. Here's my app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { initializeApp,provideFirebaseApp } from '@angular/fire/app';
import { environment } from '../environments/environment';
import { provideFirestore,getFirestore } from '@angular/fire/firestore';

import { USE_EMULATOR as USE_FIRESTORE_EMULATOR } from '@angular/fire/compat/functions';
import { USE_EMULATOR as USE_FUNCTIONS_EMULATOR } from '@angular/fire/compat/functions';

import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => getFirestore()),
  ],
  providers: [
    { provide: USE_FIRESTORE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 8080] : undefined },
    { provide: USE_FUNCTIONS_EMULATOR, useValue: environment.useEmulators ? ['localhost', 5001] : undefined }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

There's a smell here. I'm initializing Firebase using AngularFire 7 but I'm importing the emulator from AngularFire 6.1.0. Firebase can be initialized with AngularFire 6 or AngularFire 7 but not both, i.e., you can't mix AngularFire 6 and 7.

How do I import the emulators without using AngularFire 6?

In environments.ts I made a property useEmulators:

export const environment = {
  firebase: {
    projectId: 'my-awesome-project',
    appId: '1:234567890:web',
    storageBucket: 'my-awesome-project.appspot.com',
    apiKey: 'ABCdef',
    authDomain: 'my-awesome-project.firebaseapp.com',
    messagingSenderId: '0987654321',
  },
  production: false,
  useEmulators: true
};

My Cloud Function runs great in the cloud but doesn't run in the emulators.

Each time I make a change in a Cloud Function, deploy the update to the cloud, wait a minute for the deploy to propagate, test my function, and wait for the logs to show up in the Firebase Console is ten minutes. I'm looking forward to using the emulators to speed up this development cycle.

Here's the rest of my code. I doubt there's anything wrong with these files.

The Cloud Function triggers from writing a message to Firestore, changes the message to uppercase, and writes the uppercase message to a new field in the document.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.uppercaseMe = functions.firestore.document('Triggers/{docId}').onCreate((snap, context) => {
    var original = snap.data().message;
    functions.logger.log('Uppercasing', context.params.docId, original);
    var uppercase = original.toUpperCase();
    return snap.ref.set({ uppercase }, { merge: true });
});

The HTML view has a form for submitting a message. It displays the data that was written to Firestore and then displays the results from the Cloud Function.

<form (ngSubmit)="triggerMe()">
    <input type="text" [(ngModel)]="message" name="message" placeholder="Message" required>
    <button type="submit" value="Submit">Submit</button>
</form>

<div>{{ data$ }}</div>
<div>{{ upperca$e }}</div>

The app.component.ts controller writes the message to Firestore, reads back the message from Firestore, then sets up a document listener to wait for the cloud function to write a new field to the document.

import { Component } from '@angular/core';
import { Firestore, doc, getDoc, collection, addDoc, onSnapshot } from '@angular/fire/firestore';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  data$: any;
  docSnap: any;
  message: string | null = null;
  upperca$e: string | null = null;
  unsubMessage$: any;

  constructor(public firestore: Firestore) {}

  async triggerMe() {
    try {
      // write to Firestore
      const docRef = await addDoc(collection(this.firestore, 'Triggers'), {
        message: this.message,
      });
      this.message = null; // clear form fields
      // read from Firestore
      this.docSnap = await getDoc(doc(this.firestore, 'Triggers', docRef.id));
      this.data$ = this.docSnap.data().message;
      // document listener
      this.unsubMessage$ = onSnapshot(doc(this.firestore, 'Triggers', docRef.id), (snapshot: any) => {
        this.upperca$e = snapshot.data().uppercase;
      });
    } catch (error) {
      console.error(error);
    }
  }
}

Solution

  • Firebase emulators work independently of Angular or other apps! I reread the documentation and learned that you just spin up the emulators,

    firebase emulators:start
    

    open your browser to http://localhost:4000, and you can write data in Firestore and then see the results of your function appear in Firestore. You can also read the logs. This only works with triggerable functions, not with callable functions.

    Amazing what you can learn by reading the documentation. :-)