3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 4

Setting up a Development Environment for Firebase

Using the Firebase Local Emulator Suite

EXTENDED

Setting up a Development Environment for Firebase

Since we will be integrating with Firebase for this application, there are a few more steps we need to complete in order to:

  • Set up local emulators so we don’t have to use real Firebase services during development
  • Install the @angular/fire library
  • Set up a Firebase project

We will be stepping through doing all of this in this lesson, and by the end of the lesson we will have a nice base to work from.

Install Firebase Tools

Install the firebase-tools package globally:

npm install -g firebase-tools

This will allow us to use the Firebase CLI to interact with various Firebase features. To begin, we are going to login with a Firebase account.

Run the following command:

firebase login

NOTE: If you do not already have a Firebase account you can create one here: firebase.google.com

After authenticating with your Firebase account you will be able to do things like create/select projects, which will be important for the next step.

Installing AngularFire

It is possible to the Firebase JavaScript SDK directly in your project, but the @angular/fire library is basically an abstraction on top of the standard SDK that makes it a lot easier to integrate with an Angular application.

Run the following command:

ng add @angular/fire

This will allow you to select which features you want to use. We want to deselect ng deploy -- hosting and select both:

  • Authentication
  • Firestore

Proceed through the prompts until you get to a section to select a project, and then choose [CREATE NEW PROJECT] and give it a name. As well as the Firebase project itself, you also need to create an app within that project. At the next prompt, choose [CREATE NEW APP] and call it what you like (you can give this the same name as your project if you want).

After you do this, you should see that the following files are updated:

UPDATE .gitignore (453 bytes)
UPDATE src/app/app.module.ts (971 bytes)
UPDATE src/environments/environment.ts (987 bytes)
UPDATE src/environments/environment.prod.ts (376 bytes)

Create a Firestore Database

Before we can move on to the next step, we need to manually create a new Firestore database inside of our Firebase project. To do that, you will need to go to the Firebase console and select your project.

You should then select Firestore Database under Build from the menu and then Create database. When asked whether to start in production mode or test mode you should choose test mode. The difference is that production mode will prevent access to your database (including you) until you explicitly change the security rules. In test mode we will be able to access our database for some amount of time which will make our initial development easier (as we don’t need to worry about security rules until later). However, if you are starting in test mode you do need to make sure you do not actually deploy your application without first implementing security rules.

You can finish creating the database by clicking Next and choosing a location for your database.

Enable Authentication

Whilst we are already in the Firebase console, we will take the opportunity to enable Authentication as well. Go to the Authentication (under Build as well) section from your Firebase project dashboard, choose Get started, and select the Email/Password option. Enable Email/Password and then click Save.

Additional Configuration with the Firebase CLI

Although the ng add schematic handles a lot of our configuration for us, there are some thing it doesn’t do that we still want to add like:

  • A local security rules file
  • Local emulators

If we also use the standard Firebase CLI to do some configuration for us, we can also get these elements added in.

Run the following command:

firebase init

Make the following selections:

  • Firestore
  • Emulators

When prompted choose Use an existing project. Find and select the project you just created. When prompted, keep the default names/options for any files just by hitting Enter.

When you get to the Emulators Setup stage make sure to choose:

  • Authentication Emulator
  • Firestore Emulator

Again, use the default values for the ports. When prompted, say yes to enabling the Emulator UI - this provides us with an interface similar to the Firebase console that we can access when running the emulators locally. Make sure you say yes to downloading the emulators.

At this point, you should have several new files added to your application:

  • .firebaserc
  • firebase.json
  • firestore.indexes.json
  • firestore.rules

Note that every time you update the security rules file, you will need to deploy it in order for those rules to go live. After making a change, you can either run:

firebase deploy

Which will deploy everything related to your Firebase project, or if you specifically want to deploy only the rules you can run:

firebase deploy --only firestore:rules

Configuring a Development/Production Environment for Firebase

Now we are going to have to do a little manual configuration ourselves. The goal here is that we want to:

  1. Use the emulators for Firestore and Authentication during development
  2. Use Firestore and Authentication from our real Firebase project during production

We also want this to happen automatically. We don’t want to have to remember to switch out configuration details when we are creating a production build or when we want to switch to development - this is a recipe for disaster.

Angular already has a built in mechanism for managing a development/production environment. You will find these two files in your project:

  • environment.ts
  • environment.prod.ts

Depending on whether you have built your application with the --prod flag or not, it will either include the environment.ts file or the environment.prod.ts file in the build. This provides a convenient way for us to supply different configurations depending on the type of build. We are going to utilise this to achieve what we want with regard to Firebase.

First, let’s set up our production environment.

Modify the environments/environment.prod.ts file to reflect the following:

export const environment = {
  firebase: {
    projectId: '******',
    appId: '***********',
    storageBucket: '**********',
    apiKey: '********',
    authDomain: '********',
    messagingSenderId: '****',
  },
  production: true,
  useEmulators: false,
};

IMPORTANT: You will need to replace these configuration details with your own Firebase configuration - since we used the ng add schematic, this data should already be in these files. However, you also can get this information from your Firebase dashboard by clicking on the application you created (it should display as 1 app). Click the settings icon on the app, and scroll down to JavaScript code that contains the firebaseConfig.

Notice that we have also added a useEmulators property - in a production environment we want that to be false. We are going to use this to enable/disable the emulators in our root module.

Modify the environments/environment.ts file to reflect the following:

export const environment = {
  firebase: {
    projectId: 'demo-project',
    appId: '******',
    storageBucket: '********',
    apiKey: '*****',
    authDomain: '*****',
    messagingSenderId: '********',
  },
  production: false,
  useEmulators: true,
};

Once again, replace with your own configuration but make sure to keep the projectId as demo-project for the development environment. When the emulators see a projectId that uses the demo-* naming convention, they will prevent access to any real Firebase services (which is what we want for development). Also note that useEmulators here is true.

Now let’s set up our root module. There should already be some configuration for Firebase in app.module.ts:

    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideAuth(() => getAuth()),
    provideFirestore(() => getFirestore()),

But we are going to need to modify this so that it uses the emulators for development.

Modify app.module.ts to reflect the following:

import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { environment } from '../environments/environment';
import { provideAuth, getAuth, connectAuthEmulator } from '@angular/fire/auth';
import {
  provideFirestore,
  getFirestore,
  Firestore,
  initializeFirestore,
  connectFirestoreEmulator,
} from '@angular/fire/firestore';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => {
      let firestore: Firestore;
      if (environment.useEmulators) {
        // Long polling required for Cypress
        firestore = initializeFirestore(getApp(), {
          experimentalForceLongPolling: true,
        });
        connectFirestoreEmulator(firestore, 'localhost', 8080);
      } else {
        firestore = getFirestore();
      }
      return firestore;
    }),
    provideAuth(() => {
      const auth = getAuth();
      if (environment.useEmulators) {
        connectAuthEmulator(auth, 'http://localhost:9099', {
          disableWarnings: true,
        });
      }
      return auth;
    }),
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

I’ve included the entire configuration here including the import for convenience, but the important parts are here:

    provideFirestore(() => {
      let firestore: Firestore;
      if (environment.useEmulators) {
        // Long polling required for Cypress
        firestore = initializeFirestore(getApp(), {
          experimentalForceLongPolling: true,
        });
        connectFirestoreEmulator(firestore, 'localhost', 8080);
      } else {
        firestore = getFirestore();
      }
      return firestore;
    }),
    provideAuth(() => {
      const auth = getAuth();
      if (environment.useEmulators) {
        connectAuthEmulator(auth, 'http://localhost:9099', {
          disableWarnings: true,
        });
      }
      return auth;
    }),

When providing Firestore and Auth to our application we are checking the useEmulators property we set up in our environment configurations. If useEmulators is set to true we connect to the emulators, otherwise we do not.

Using the Emulators

IMPORTANT: You will need Java installed on your machine in order to ues the emulators. Please check the documentation for information on required Java versions.

An important thing we need to keep in mind when using the emulators is that we actually need to have them running. To do that, we can run the following command:

firebase emulators:start

…and then in a separate terminal window run your normal application serve command. But, having to run two separate commands to run your application is a bit annoying, and I guarantee you will forget to start the emulators at least some of the time.

Instead, we can create a start script that we run to both start the emulators and serve our application. We will get to that in a moment, for now let’s just have a bit of a poke around.

Since we enabled the Emulator UI, after running the command to start the emulators you should be able to go to:

http://localhost:4000

to see the Firebase Emulator Suite dashboard. You might notice this looks somewhat similar to the normal Firebase dashboard. We can go into different services like Firestore and Authentication and interact with them in a similar way to using the standard dashboard.

Adding a Start Script

Now let’s set up that start script. First, stop the emulators by hitting Ctrl + C.

Modify the start script in package.json (under "scripts") to reflect the following:

"start": "firebase emulators:exec --project=demo-project --ui 'ionic serve'",

The exec command will finish starting the emulators with the options we give it, and then once it is done it will run our command: ionic serve. This way, all we need to do is run:

npm start

and it will both start the emulators and serve our application.