Angular+Firebase

Building a Web App with Angular 4 and Firebase — Part 2

In our last post, we set up a new Angular/Firebase project, and we were ready to start work on our first component: a project listing.

If it’s been a while, go ahead and fire up your command line again. Then change to your project’s directory (angular-firebase-tutorial/project-manager if you named your directories as we did). Then run ng serve -o to build the code and open it in your browser.

The full source code for this tutorial is available on GitHub in case you want to compare what you have after finishing reading and following along.

Updating the App Component

Open the /src/app/app.component.ts file to see that an Angular component consists of 3 basic parts: one or more imports, the @Component decorator, and a class definition.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

At a minimum, a component will need to import Component from @angular/core. We will be importing many other dependencies over the course of this tutorial.

Notice the @Component decorator before the class definition. An object with three properties is used in the decorator. The selector property tells Angular what HTML tag will be used to represent the component. It is set to app-root, and if you look at the index.html file, you can see the <app-root> tag being used in the <body>. The templateUrl property is where the component’s HTML is found. The styleUrls property is where any style sheets used by the component can be found. There are other properties as well, but these three are the most common.

Inside the class itself, we see a property called title that is currently set to app. Change its value to Project Manager and save your changes. Your browser should refresh, and the heading should change to “Welcome to Project Manager!” Let’s open the HTML template to see how that happened.

Open the file mentioned in the @Component decorator’s templateUrl property: /src/app/app.component.html. There’s an <h1> element that says: Welcome to {{title}}! Leave that as it is, but delete all the other HTML in the file. So it should look like this:

<h1>
  Welcome to {{title}}!
</h1>

The double curly brackets surrounding the title property indicate that one-way data-binding is being used. This means if the title property is updated at any time, the change will be reflected in the HTML DOM. Any property of the class — public or private — can be displayed in this manner.

This is all we’re going to change in the App component for now. Next we’ll create our own component for our project listing.

Creating the Projects Component

We’re going to use Angular CLI to scaffold out our new component and save us some typing. Enter the following command.

ng generate component projects --spec=false

This will create a directory with three new files: the component class, its HTML, and a style sheet. We passed the --spec=false parameter because we aren’t interested in unit testing our component, but generally you would want to write tests as you go.

Project files

You may not have noticed, but in addition to the new files that were generated, the /src/app/app.module.ts file was also updated. New components must be declared in your app.module.ts file before they can be used, and the generator takes care of that for you.

Open the new /src/app/projects/projects.component.ts file. It looks similar to the app.component.ts file, except that it has an import for OnInit. When a class implements OnInit, a method called ngOnInit is used to run code when the component is initialized. This is a very common practice, which is why it was included by default. We’ll add some code to the ngOnInit method later.

Take a look at the projects.component.html file. It’s just a paragraph that says, “projects works,” but we’ll be changing that soon.

<p>
  projects works!
</p>

We’ve scaffolded out our Projects component, but it’s not being used anywhere at the moment. Let’s add it inside of our App component. In app.component.html, add a <app-projects> tag below the <h1> tag.

<h1>
 Welcome to {{title}}!
</h1>

<app-projects></app-projects>

Save your changes and take a look in your browser to see the “projects works!” text below the heading.

Browser rendering

Hopefully, now you’re getting a feel for how components and their HTML templates work. Each component is represented by an HTML tag (e.g. app-projects) that is determined by the selector property in the @Component decorator. When the HTML tag is added to the index.html page or inside of another component’s template, the component’s HTML template will be rendered there.

Before we can create our projects list, we’ll need some sample data in our database.

Adding Sample Data to Firebase

Head back to your Firebase console, select the project you created if necessary, and go to the Database section. Click the Get Started button if you haven’t already, and you’ll be greeted with an empty database that shows your project name with a null value.

If you’re familiar with traditional relational databases, you may be wondering how you can create tables and add records to them. Remember, Firebase is more like a JSON tree than a typical database. We want to store our projects, so let’s create a new database object for them.

Hover over the null value, and you’ll see a + and an X. Click the + and enter projects for the name field. Leave the value field empty. Our projects won’t have a single value. They will be objects with properties like title, description, and tasks. For now, we’re just going to add a title to our projects.

Firebase projects item

Click the + next to your new projects row. In the next name field, enter 1 which will act as an ID/key for the project. These keys will be autogenerated by our code in the future, but for now, we’re just hard-coding them. Click the + icon beside this new field to add another child and name this one title and give it a value of My First Project.

Firebase projects and children

Click Add to finalize your changes.

Now, add a couple more projects using the same process:

  1. Click + next to the projects row.
  2. Assign a unique key to the child row (e.g. 2).
  3. Click + next to the key row.
  4. Enter title for the name field and a fitting title for the value (e.g. My Second Project).
  5. Click the Add button to save.

Firebase projects tree

Firebase Authentication

Earlier, we enabled Google authentication with Firebase. However, at this point, our Angular app does not have any code allowing users to log in with their Google account. We’ll definitely want to set that up, but for now, it’ll be easier to allow anyone to read and write to our database.

While still in the Database section of your Firebase console, go to the Rules section, and replace the current rules with the following:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

We’ll come back later and reset these rules to prevent unauthorized access.

Creating the Project Listing

Now that we have some sample data, let’s go back to our Projects component and pull in that data. Open /src/app/projects/projects.component.ts.

First, we want to import two more dependencies. Add the following two imports below the first one.

import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

The AngularFireDatabase dependency will allow us to make queries against our database. The Observable dependency allows us to make use of observables, which are similar to JavaScript promises, except that we can maintain a constant connection to our database instead of sending new requests every time we want to check for an update.

To make use of AngularFireDatabase, we need to assign it to a variable in our constructor method. Make the following change.

constructor(
 private _db: AngularFireDatabase
) { }

If you’re new to TypeScript, we’re creating a private field called _db of type AngularFireDatabase. The underscore in the variable name is a common convention for private variables in JavaScript.

Now we need to create a variable to store our list of projects. We’ll call it projectsObservable because it will be an observable that receives data from our database. We’ll also add some code to our ngOnInit method to query the database and store the result in our observable.

import { Component, OnInit } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

@Component({
 selector: 'app-projects',
 templateUrl: './projects.component.html',
 styleUrls: ['./projects.component.css']
})
export class ProjectsComponent implements OnInit {
 projectsObservable: Observable<any[]>;

 constructor(
   private _db: AngularFireDatabase
 ) { }

 ngOnInit() {
   // Get projects
   this.projectsObservable = this._db.list('projects').valueChanges();
 }
}

Notice that we use the list() method of our AngularFireDatabase object to retrieve a list of data — in this case, our projects. The data will not only include the top-level project data but every descendant as well. This will become important later as we continue to structure our database in a manner that performs and scales well.

If you want to know more about the database methods that are available, see the official AngularFire repository on GitHub.

We now have an observable that will report on our project data, and we can subscribe to that stream of data. We’ll add another property below our projectsObservable property called projects, and when new data comes in from the observable, we’ll assign it to that property.

export class ProjectsComponent implements OnInit {
 projectsObservable: Observable<any[]>;
 projects: any[];

 constructor(
   private _db: AngularFireDatabase
 ) { }

 ngOnInit() {
   // Get projects
   this.projectsObservable = this._db.list('projects').valueChanges();

   this.projectsObservable.subscribe(projects => {
     this.projects = projects;
   });
 }
}

It’s time to display our project titles in our HTML template. Open /src/app/projects/projects.component.html and replace everything with the following HTML.

<h2>Projects</h2>

<ul>
 <li *ngFor="let project of projects">
   {{project.title}}
 </li>
</ul>

The ngFor directive is how we loop through arrays. The syntax is similar to a “for…in” loop in JavaScript. For each project in our array of projects, we display the project’s title.

Save your changes and make sure that your app is now showing a list of projects from the database.

Browser output of project

Correcting Bad Practices

We’ve engaged in some bad practices so far in order to get at the important stuff without slowing down momentum. However, we should really go back and refactor a couple things to make further development easier.

A Model for the Project Type

First, you’ll notice that we declared our projectsObservable and projects variables using the any type. The any type basically defeats the purpose of using TypeScript, since you’re saying that the variable can by any type. To take advantage of strong typing (and code suggestions in your editor if supported), we’ll want to create a new model for our project type.

Create a new file called project.model.ts in the /src/app/projects directory, and add the following to it.

export interface Project {
  title: string;
}

We are creating our model as an interface instead of a class, because we will never instantiate it or use any other features that classes provide.

Back in projects.component.ts, we’ll import our new model and swap out those any types.

import { Component, OnInit } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

import { Project } from './project.model';

@Component({
 selector: 'app-projects',
 templateUrl: './projects.component.html',
 styleUrls: ['./projects.component.css']
})
export class ProjectsComponent implements OnInit {
 projectsObservable: Observable<Project[]>;
 projects: Project[];

 constructor(
   private _db: AngularFireDatabase
 ) { }

 ngOnInit() {
   // Get projects
   this.projectsObservable = this._db.list('projects').valueChanges();

   this.projectsObservable.subscribe(projects => {
     this.projects = projects;
   });
 }
}

Move Firebase Config to External File

If we want to add our code to a version control system, we shouldn’t be placing configuration that is likely to be different for each user into important files. In this case, our Firebase credentials shouldn’t be in version control. Let’s move them to another file.

Create a new file in your /src/app directory called app.config.ts. Then move your Firebase configuration from app.module.ts to app.config.ts.

export const firebaseConfig = {
  apiKey: 'Your API Key',
  authDomain: 'your-app.firebaseapp.com',
  databaseURL: 'https://your-app.firebaseio.com',
  projectId: 'your-project-id',
  storageBucket: 'your-project.appspot.com',
  messagingSenderId: 'Your Sender ID'
};

In order for this configuration to be imported into app.module.ts, we needed to export it. Notice the export before the variable definition above. Lastly, we import the config in app.module.ts.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireAuthModule } from 'angularfire2/auth';

import { firebaseConfig } from './app.config';

import { AppComponent } from './app.component';
import { ProjectsComponent } from './projects/projects.component';

import { DataService } from './data.service';

A Service for Data Retrieval

Using AngularFireDatabase in our Projects component is fine if that were the only component where we’d ever use it, but we’ll be asking for and updating the database in multiple areas of our application. Therefore, it makes sense to create a shared data service class.

We’ll scaffold this out using Angular CLI again. Enter the following command.

ng generate service data --spec=false

Afterward, you’ll have a new file at src/app/data.service.ts. Go ahead and open it. You’ll see that it imports Injectable and uses the @Injectable decorator. This is what allows a service to be used by other classes when called in that class’s constructor. It tells Angular to “inject” an instance of it when the constructor is called.

Unlike when we generated a new component, Angular CLI does not automatically hook up the service in NgModule, so we’ll need to do that manually. Open /src/app/app.module.ts, and add an import for the new service.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireAuthModule } from 'angularfire2/auth';

import { AppComponent } from './app.component';
import { ProjectsComponent } from './projects/projects.component';

import { DataService } from './data.service';

We will add the DataService to our providers array instead of the declarations array (where the Projects component was added).

@NgModule({
 declarations: [
   AppComponent,
   ProjectsComponent
 ],
 imports: [
   BrowserModule,
   AngularFireModule.initializeApp(firebaseConfig),
   AngularFireDatabaseModule,
   AngularFireAuthModule
 ],
 providers: [
   DataService
 ],
 bootstrap: [AppComponent]
})
export class AppModule { }

Back in our new data.service.ts file, we need to import AngularFireDatabase and Observable, then get a reference to AngularFireDatabase in our constructor.

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class DataService {

 constructor(
   private _db: AngularFireDatabase
 ) { }
}

Then, we need to add our first method which will perform the AngularFireDatabase.list() call.

constructor(
   private _db: AngularFireDatabase
 ) { }

 /**
  * Returns an observable of database objects below a given path.
  * @param path The path to the database object.
  */
 getList<T>(path: string): Observable<T[]> {
   return this._db.list(path).valueChanges();
 }
Note: If you aren’t familiar with generics in TypeScript (the T in the method signature), they are basically a means of specifying the type of the return value you want. In our Projects component, we want a list of projects; however, we will be using the data service for more than just projects, and we want our method to be able to handle multiple types.

Back in projects.component.ts, we need to replace the usage of AngularFireDatabase with our new data service.

import { Component, OnInit } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

import { Project } from './project.model';

import { DataService } from '../data.service';

@Component({
 selector: 'app-projects',
 templateUrl: './projects.component.html',
 styleUrls: ['./projects.component.css']
})
export class ProjectsComponent implements OnInit {
 projectsObservable: Observable<Project[]>;
 projects: Project[];

 constructor(
   private _db: AngularFireDatabase
   private _dataService: DataService
 ) { }

 ngOnInit() {
   // Get projects
   this.projectsObservable = this._db.list('projects').valueChanges();
   this.projectsObservable = this._dataService.getList('projects');

   this.projectsObservable.subscribe(projects => {
     this.projects = projects;
   });
 }
}
Note: Another advantage to separating out the data service like this is that if later we want to use something other than Firebase, we can update just the data service code instead of having to rewrite all the calls to AngularFireDatabase in multiple files.

Save all your changes and verify that the app is still working correctly.

Wrapping Up

Before we finish with this part of the tutorial, there’s something you should try out if you haven’t already. Earlier I mentioned that observables give us a persistent connection to the database. If you were wondering what that meant, try making some changes to the sample projects you created in the Firebase console. You should see them updating in your app in real time.

Next…

In the next post, we’ll allow projects to be added from within the app and further flesh out the functionality by adding tasks to the projects.

On Copy and Paste

One more thing before we go. Tutorials such as this will have a lot of code examples, and typing them by hand can be slow and tedious at times. However, I find that it helps to cement your knowledge if you take the extra time to type them out instead of just copying and pasting. It’s true that you may make some typos and perhaps break some things, but that’s how you learn to fix those issues.

If you copied and pasted your way through this article, no worries; I understand that time is precious, but in the next article, why not try typing out the code? I think you’ll be pleasantly surprised.

Continue to Part 3

Leave a Reply

Your email address will not be published. Required fields are marked *