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.
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?
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.
After this, we must navigate to the left side and in the Build section, choose "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.
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.
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.
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.
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.
As you can see, all our features are now working with Firebase.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done