Working with databases

web
mobile

Author: Damian Gemza

-

Working with Firebase & IndexedDB databases

It is time to take advantage of the benefits of databases. In this lesson, we will focus on using two databases — Firebase and IndexedDB. At the end of this lesson your application will use the Firebase database to get data into the application, and in offline mode, it will use a data caching mechanism and IndexedDB.

In the app.component.html file, we have to change the *ngFor loop that generates the schedule-item components. In this line, we have to add an | async pipe. To do this, open this file and change the line below to the following one.


        <div *ngFor="let item of items">
          <app-schedule-item [value]="item"></app-schedule-item>
        </div>
      

        <div *ngFor="let item of items | async">
          <app-schedule-item [value]="item"></app-schedule-item>
        </div>
      

The file app.component.ts we have to rebuild in its entirety. To do this, paste the code below into this file:


        import {Component, OnInit, ViewChild} from '@angular/core';
        import {ModalDirective} from 'angular-bootstrap-md';
        import {FormControl} from '@angular/forms';
        import {IdbService} from './services/idb.service';
        import {FirebaseService} from './services/firebase.service';
        import {AngularFirestore} from '@angular/fire/firestore';
        import {Observable, of} from 'rxjs';
        import {map} from 'rxjs/operators';

        export interface Schedule {
          id?: string;
          time: string;
          subject: string;
          location?: string;
          description?: string;
        }

        @Component({
          selector: 'app-root',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.scss']
        })
        export class AppComponent implements OnInit {
          @ViewChild(ModalDirective) modal: ModalDirective;

          timeInput = new FormControl();
          subjectInput = new FormControl();
          locationInput = new FormControl();
          descriptionInput = new FormControl();

          networkMode = 'online';
          items: Observable<Schedule>;

            constructor(
            private idbService: IdbService,
            private firebase: FirebaseService,
            private db: AngularFirestore) {
              navigator.onLine === true ? this.networkMode = 'online' : this.networkMode = 'offline';

              this.idbService.connectToIDB();
              let onlineDataLength;

              this.idbService.getAllData('Items').then((items: any) => {
                onlineDataLength = items.length;
                if (this.networkMode === 'online' && onlineDataLength === 0) {
                  this.items = this.db.collection<Schedule>('Items', item => item.orderBy('time', 'asc'))
                    .snapshotChanges().pipe(map((actions: any) => {
                      return actions.map(a => {
                      const data = a.payload.doc.data() as any;
                      this.idbService.addItems('Items', data);
                      return {...data};
                    });
                    }));
                    } else {
                    this.items = of(items);
                  }

                  this.idbService.dataChanged().subscribe((data: any) => {
                    this.items = of(data);
                  });
                });
              }

              addNewItem() {
                const value: Schedule = {
                  id: null,
                  time: this.timeInput.value,
                  subject: this.subjectInput.value,
                  location: this.locationInput.value,
                  description: this.descriptionInput.value
              };

              if (this.networkMode === 'offline') {
                this.idbService.addItems('Sync-Items', value);
                this.idbService.addItems('Items', value);
              } else if (this.networkMode === 'online') {
                this.idbService.addItems('Items', value);
                this.idbService.getAllData('Items').then((data: any) => {
                  this.firebase.addItem({
                    id: data.length,
                    time: value.time,
                    subject: value.subject,
                    location: value.location,
                    description: value.description
                });
              });
              }

              this.timeInput.setValue('');
              this.subjectInput.setValue('');
              this.locationInput.setValue('');
              this.descriptionInput.setValue('');

              this.modal.hide();
              }

              getOnlineData() {
                return this.idbService.getAllData('Items');
              }

              getOfflineData() {
                return this.idbService.getAllData('Sync-Items');
              }

              mergeDatabases() {
                let offline;
                let online;

                this.getOfflineData().then((data: any) => {
                  offline = data;
                });
                this.getOnlineData().then((data: any) => {
                  online = data;
                offline.forEach((el: any, index: number) => {
                  if (el == offline[index]) {
                    this.firebase.addItem(el);
                    this.idbService.addItems('Items', el);
                    this.idbService.deleteItems('Sync-Items', el.id);
                  }
                });
              });
              }

              ngOnInit() {
                if (this.networkMode === 'online') {
                  this.mergeDatabases();
                }
              }

            }
      

Let's describe the changes that have occurred in this file:

In the items variable we changed the type to Observable<Schedule>, so we can use as async pipe while generating the schedules list.

In the constructor we injected three services: IdbService, FirebaseService, and AngularFirestore.

Then in the constructor we used the this.idbService.connectToIDB() method to connect to the IndexedDB database, and then we used the this.idbService.getAllData() method to retrieve data from the IndexedDB database.

In the this.idbService.getAllData() Promise we check if the application is running online, then we retrieve data from the Firebase database and assign it to the items variable. Otherwise, we use the of operator from the RxJS library to create observable data from the IndexedDB database and assign it to the items variable.

The last step in the constructor is to subscribe to the this.idbService.dataChanged() method in order to assign new data to the items variable each time.

In the addNewItem() method we added some code that checks if the application is running offline or online, and based on this check adds data to the offline (indexedDB) or online (Firebase) database.

In the ngOnInit hook we use the mergeDatabases() method to transfer the data added offline (IndexedDB) to the online database (Firebase).

Now you can build your application using the npm run pwa command, then open your browser at localhost:8080 and see how it works.

Your application should display one test schedule, and should look like in the picture below:

PWA Application

However, if your application reads old or incorrect data, clear the Cache data and IndexedDB data in your browser. Open Developer Tools (F12), then open the Application menu, click on the Clear storage button and then click the Clear site data button. The picture below shows how to do it in Google Chrome.

Clear data storage

We then need to check the operation of the application! Run it with the npm run pwa command, and see how it works in online and offline modes.

Switch the application to offline mode, add a new schedule, then switch it back to offline mode and refresh. You'll see that the data added offline is online!


Is it working for you? If not, you can check the code for this lesson on our repository


Rate this lesson

Previous lesson Download Next lesson

Spread the word:
Do you need help? Use our support forum

About the author

User avatar
Damian Gemza
Damian is a front-end developer. He is passionate about everything related to the world of Web Development. He is interested in Angular and everything that is connected with it. He knows and uses the Ionic framework and Progressive Web Apps technology. Recently he has started to write tutorials related to Web Development.