How to Connect Firebase to Angular Project - Do It Right
How to Connect Firebase to Angular Project - Do It Right

Frequently, developers prefer not to set up their own API or implement their own database. Instead, they seek services that can simply deliver data for their client applications.

This is why, in this post, you will learn how to implement an Angular Firebase project. This approach enables easy integration of a backend with Firebase, allowing seamless binding to the Angular application.

website

Just to remind you, Firebase is a service where you can create your own project, configure the database inside, change your database inside the browser, create tables, columns, or make requests to this API through the Firebase library.

Firebase is a possibility to work with the backend without learning backend or digging deep into it.

Firebase also allows you to access your API and database without the need to host it yourself.

Initial Project

Now the question is, what project do we want to implement?

initial project

Here, I've already prepared a full todo list project for us, which we previously discussed in one of my posts. The main point is that it's a client application built with Angular. We can filter our todos, mark them as completed, remove todos, and update todos—all within the client.

All this functionality operates solely on the client side; we don't store this data in a database. So, the goal of this post is to implement an Angular Firebase project where we will bind all these changes to a real database in Firebase.

Configuring Database

Our first step is to create an account inside Firebase. You don't need to pay anything upfront, as it is completely possible to work with Firebase on a free tier.

First of all, we must create a new project.

new project

After this, we must navigate to the left side and in the Build section, choose "Firestore database".

firestore database

During the creation process, we want to choose "Start in Test mode". This option allows for quick setup and also enables us to view, edit, and delete data easily.

online creation

Now, in our online database, we can create several records. We want a collection called todos with records containing fields such as text: string and isCompleted: boolean. Let's add multiple records for our project.

Configuring Packages

We have successfully created records inside Firebase. Now, the question is: how can we access them? To do that, we must install a package in our Angular application.

npm i @angular/fire

This is an official package by Angular, which is beneficial because we can be assured that it will never be abandoned.

Now, we want to set up Firebase in our application. However, in order to do that, we must obtain all API keys for Firebase.

To do this, we must click on "create app" inside the Firebase admin panel and select that we need an app for the web.

new app

We can provide any name, and after creating the app, you will see a firebaseConfig object with all the API keys.

// src/app/app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';

import { routes } from './app.routes';

const firebaseConfig = {
  apiKey: 'AIzaSyCutXisV_k6wM8DKzcjNNiVkqzixKJ2ruQ',
  authDomain: 'angular-todo-26dfc.firebaseapp.com',
  projectId: 'angular-todo-2gdfc',
  storageBucket: 'angular-todo-26dfc.appspot.com',
  messagingSenderId: '520833002805',
  appId: '1:52083d002805:web:8ac50ebdfa0cb84c35f07c',
};

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    importProvidersFrom([
      provideFirebaseApp(() => initializeApp(firebaseConfig)),
      provideFirestore(() => getFirestore()),
    ]),
  ],
};

In our app.config.ts, we pasted the entire firebaseConfig of our app and also added provideFirebaseApp and provideFirestore. Firstly, we need to configure our Firebase App, and secondly, to add Firebase database in our Angular application.

Firebase Service

Now, we need to start using Firebase in our application. To do that, I want to create a Firebase service.

// src/app/todos/services/todosFirebase.service.ts
import {
  Firestore,
  collection,
  collectionData
} from '@angular/fire/firestore';
@Injectable({ providedIn: 'root' })
export class TodosFirebaseService {
  firestore = inject(Firestore);
  todosCollection = collection(this.firestore, 'todos');

  getTodos(): Observable<TodoInterface[]> {
    return collectionData(this.todosCollection, {
      idField: 'id',
    }) as Observable<TodoInterface[]>;
  }
}

Firstly, we inject Firestore in our service. Secondly, we create a todosCollection, which references the todos collection inside Firebase, enabling us to perform all our operations with it.

Then, we define our first function, getTodos. We use the collectionData function to retrieve all records, including the id field.

Now, we want to use this method initially in our Todos project to retrieve the initial todos from the database.

// src/app/todos/todos.component.ts
export class TodosComponent implements OnInit {
  todosService = inject(TodosService);
  todosFirebaseService = inject(TodosFirebaseService);

  ngOnInit(): void {
    this.todosFirebaseService.getTodos().subscribe((todos) => {
      this.todosService.todosSig.set(todos);
    });
  }
}

Here, we call the getTodos function, which returns an array of todos from Firebase. We can use our old todosService signal to set the todos value in the application.

getting data

As you can see, we retrieve the initial todos from the database, and these are exactly the records that we created in Firebase previously.

Adding Todo

The next thing we want to implement is the addition of our todo. Let's return to our Firebase service and create a new method for this purpose.

// src/app/todos/services/todosFirebase.service.ts
...
@Injectable({ providedIn: 'root' })
export class TodosFirebaseService {
  ...
  addTodo(text: string): Observable<string> {
    const todoToCreate = { text, isCompleted: false };
    const promise = addDoc(this.todosCollection, todoToCreate).then(
      (response) => response.id
    );
    return from(promise);
  }
}

The addTodo method receives text as a parameter and creates a todo by calling the addDon function. Importantly, we receive a Promise from the Firebase library, not an Observable. To work seamlessly with Angular, we convert the Promise to an Observable using the from function.

Now, we need to update our HeaderComponent, as this is where we create new todos.

// src/app/todos/components/header/header.component.ts
...
@Component({
  selector: 'app-todos-header',
  templateUrl: './header.component.html',
  standalone: true,
})
export class HeaderComponent {
  todosService = inject(TodosService);
  todosFirebaseService = inject(TodosFirebaseService);
  ...
  addTodo(): void {
    this.todosFirebaseService.addTodo(this.text).subscribe((addedTodoId) => {
      this.todosService.addTodo(this.text, addedTodoId);
      this.text = '';
    });
  }
}

We first call the addTodo method from the Firebase service, which returns the ID of the created record. Then, we call our old addTodo method, which updates our todos signal.

add todo

As you can see, now we can create new todos, and they stay in the list even after page reload. Additionally, we can jump to the Firebase admin panel and see that we have received one new record in our database.

Removing Todos

The next thing we need is the removal of the todo. Let's add a new function in our Firebase service for this purpose.

// src/app/todos/services/todosFirebase.service.ts
@Injectable({ providedIn: 'root' })
export class TodosFirebaseService {
  ...
  removeTodo(todoId: string): Observable<void> {
    const docRef = doc(this.firestore, 'todos/' + todoId);
    const promise = deleteDoc(docRef);
    return from(promise);
  }
}

We want to remove a todo by its unique ID. To do that, we must find the corresponding document in the database using its ID and then call the deleteDoc function. Additionally, we convert the Promise returned by deleteDoc to an Observable afterwards.

// src/app/todos/components/todo/todo.component.ts
@Component({
  selector: 'app-todos-todo',
  templateUrl: './todo.component.html',
  standalone: true,
  imports: [CommonModule],
})
export class TodoComponent implements OnInit, OnChanges {
  ...
  todosFirebaseService = inject(TodosFirebaseService);
  ...
  removeTodo(): void {
    this.todosFirebaseService.removeTodo(this.todo.id).subscribe(() => {
      this.todosService.removeTodo(this.todo.id);
    });
  }
}

Inside our TodoComponent, we follow the same process as before. We first call the remove function from Firebase, and upon success, we update our signal value in Angular.

As you can see, now we can remove todos, and the changes are mirrored in the Firebase database.

Updating Todos

The last thing that we need is an update function for our Todo. Importantly, we want to use it for updating text, toggling todo status, and even toggling all todos.

// src/app/todos/services/todosFirebase.service.ts
@Injectable({ providedIn: 'root' })
export class TodosFirebaseService {
  ...
  updateTodo(
    todoId: string,
    dataToUpdate: { text: string; isCompleted: boolean }
  ): Observable<void> {
    const docRef = doc(this.firestore, 'todos/' + todoId);
    const promise = setDoc(docRef, dataToUpdate);
    return from(promise);
  }
}

We need to obtain the ID of the todo that we want to update and the data that we want to change. After that, we find the corresponding record in the database and call the setDoc function to update this record.

Now, let's use this function for toggling our todo first.

// src/app/todos/components/todo/todo.component.ts
@Component({
  selector: 'app-todos-todo',
  templateUrl: './todo.component.html',
  standalone: true,
  imports: [CommonModule],
})
export class TodoComponent implements OnInit, OnChanges {
  ...
  todosFirebaseService = inject(TodosFirebaseService);
  ...
  toggleTodo(): void {
    const dataToUpdate = {
      text: this.todo.text,
      isCompleted: !this.todo.isCompleted,
    };
    this.todosFirebaseService
      .updateTodo(this.todo.id, dataToUpdate)
      .subscribe(() => {
        this.todosService.toggleTodo(this.todo.id);
      });
  }
}

We prepare our dataToUpdate, call Firebase first, and then our own toggleTodo function, which updates the client value in the signal.

We also want to update the todo within the same component.

// src/app/todos/components/todo/todo.component.ts
@Component({
  selector: 'app-todos-todo',
  templateUrl: './todo.component.html',
  standalone: true,
  imports: [CommonModule],
})
export class TodoComponent implements OnInit, OnChanges {
  ...
  todosFirebaseService = inject(TodosFirebaseService);
  ...
  changeTodo(): void {
    const dataToUpdate = {
      text: this.editingText,
      isCompleted: this.todo.isCompleted,
    };

    this.todosFirebaseService
      .updateTodo(this.todo.id, dataToUpdate)
      .subscribe(() => {
        this.todosService.changeTodo(this.todo.id, this.editingText);
      });

    this.setEditingId.emit(null);
  }
}

Same process here, we prepare new data and call the methods sequentially.

Toggle All

The only remaining part is to toggle all todos.

// src/app/todos/components/main/main.component.ts
@Component({
  selector: 'app-todos-main',
  templateUrl: './main.component.html',
  standalone: true,
  imports: [CommonModule, TodoComponent],
})
export class MainComponent {
  toggleAllTodos(event: Event): void {
    const target = event.target as HTMLInputElement;
    const requests$ = this.todosService.todosSig().map((todo) => {
      return this.todosFirebaseService.updateTodo(todo.id, {
        text: todo.text,
        isCompleted: target.checked,
      });
    });
    forkJoin(requests$).subscribe(() => {
      this.todosService.toggleAll(target.checked);
    });
  }
}

This function is more complex. For every todo, we want to call our updateTodo function, and then, when all requests are completed, we want to toggle values on the client. Firstly, we collect our requests together by mapping through the array of todos. Then, we use forkJoin to wait for the success of all requests.

toggle all

As you can see, all our features are now working with Firebase.

And actually, if you want to learn Angular with NgRx from an empty folder to a fully functional production application, make sure to check out my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done