facebook
George Anderson
Frontend and backend development guru, lover of all things computer... and cats! Hopes to make your coder life a breeze by sharing some tips and tricks of the trade.
Posted on Jul 12th 2018 We will continue from where we left off in the first article. In this part of the article, we will add the markdown editor for authoring posts on our blog, and we will set up Firebase for authentication and persisting data to remote storage. Let’s get started!

Starting a New Cloud Firestore Project

Open the Firebase console and sign in to your Google account. Click on `Add project` in order to create a new Firebase project.
firebase project creation
Next, enter `Fireblog` as project name, select your Country/region and click `Create project`.
firebase project creation
A screen should pop up telling you that your new project is ready; on this screen, click `Continue`.
project finished
Now you should see the project overview page.
overview page

Your good ol’ Eclipse IDE can do so much more for you with CodeMix! Get all the VS code smarts and Code OSS extensions without leaving your preferred environment. Dive into an ocean of languages, from JavaScript to Python, and a sea of technologies, from Vue.js to Angular.

On the project overview page, click `Add Firebase to your web app`, copy the value of the config variable, which is a JSON object, and set it as the value of the key Firebase in `/src/environment/environment.ts` and in `/src/environment/environment.prod.ts`.
google firebase
The content of `/src/environment/environment.ts` should now be:
    export const environment = {
     production: false,
     firebase: {
     apiKey: 'API_KEY',
     authDomain: 'fireblog-xxxxx.firebaseapp.com',
     databaseURL: 'https://fireblog-xxxxx.firebaseio.com',
     projectId: 'fireblog-xxxxx',
     storageBucket: 'fireblog-xxxxx.appspot.com',
     messagingSenderId: 'xxxxxxxxxxxx'
     }
    };

And the content of `/src/environment/environment.prod.ts` should be:

export const environment = {
 production: true,
 firebase: {
 apiKey: 'API_KEY',
 authDomain: 'fireblog-xxxxx.firebaseapp.com',
 databaseURL: 'https://fireblog-d13b5.firebaseio.com',
 projectId: 'fireblog-xxxxx',
 storageBucket: 'fireblog-xxxxx.appspot.com',
 messagingSenderId: 'xxxxxxxxxxxx'
 }
};

Click on the Database menu to the left, and then choose `Try Firestore Beta`.

try beta fire

 On the next screen shown below, choose locked mode and then click `Enable`.

next screen
 On the next screen, click `Add Collection` and name the collection posts.
Then, add 2 fields of type string (title, content), and provide them with some initial value. Then, click Save.

Navigate to the rules tab on the dashboard page and update the rules, as follows:

service cloud.firestore {
 match /databases/{database}/documents {
   match /posts/{post} {
     allow read: if true;
   }
 
   match /posts/{post} {
     allow write: if request.auth.uid != null;
   }
 }
}
In the code snippet above, we have set the following rules:
  • Allow anyone read access to all documents in our database
  • Allow write access only to logged in user
These are very simple rules for learning purposes and are not recommended for production apps.

Install Firebase and AngularFire2

AngularFire2 is the official Angular library for Firebase, so let’s add it to our product in order to interact with Firebase. Install Firebase and AngularFire by executing the code below in Terminal+:

npm install firebase angularfire2 --save

Create EditorAuthGuard

We need to ensure that only authorized users can create and modify posts, so we will protect the `/editor` routes using a guard. Generate a guard by executing the command below in Terminal+

ng generate guard guards/editor-auth

Update the content of the newly generated `src/app/guards/editor-auth.guard.ts`:

import { Injectable } from '@angular/core';
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

@Injectable()
export class EditorAuthGuard implements CanActivateChild {

  constructor(private afAuth: AngularFireAuth, private router: Router) {}

  canActivateChild(

    next: ActivatedRouteSnapshot,

    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return this.afAuth.authState

       .take(1)

       .map(user => {

         return !!user;

       })

       .do(loggedIn => {

         if (!loggedIn) {

           this.router.navigate(['/auth/login'],  { queryParams: { next: state.url } } );

         }

     });

  }

}
In the code snippet above, we define a class `EditorAuthGuard` which implements `CanActivateChild` imported from `@angular/router`. `CanActivateChild` is an interface that a class can implement to be a guard deciding if a child route can be activated.
In the constructor for `EditorAuthGuard` we inject `afAuth` an instance of `angularfire2/auth/AngularFireAuth`, and router an instance of `@angular/router/Router`. The class has a method `canActivateChild` that checks if a user is logged in.

Create Auth Component

In the menu select File > New > Component, enter `/FireBlog/src/app/components/auth` as the source folder and `Auth` as the component name. If you get an error, which reads as more than one modules match, then run the code below in Terminal+:
ng g component components/auth --skip-import
Using the auth component, when a user is logged out, we will display a login button; else we display a log out button. Update the content of `src/app/components/auth/auth.component.html`:

<div *ngIf="afAuth.authState | async as user; else showLogin">
<button (click)="logout()">Logout</button>
</div>
<ng-template #showLogin>
<p>Please login to edit posts.</p>
<button (click)="login()">Login with Google</button>
</ng-template>

Update the content of `src/app/components/auth/auth.component.ts`:

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

import { AngularFireAuth } from 'angularfire2/auth';
import {Router, ActivatedRoute } from '@angular/router';
import * as firebase from 'firebase/app';

@Component({
  selector: 'app-auth',
  templateUrl: './auth.component.html',
  styleUrls: ['./auth.component.css']
})
export class AuthComponent implements OnInit {

  constructor(public afAuth: AngularFireAuth, private router: Router, private route: ActivatedRoute) { }

  ngOnInit() {
  }
  login() {
    this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((function(router, route) {
      return function() {
         route.queryParams.subscribe(
          data => router.navigate( [data['next']])  );
      };
    })(this.router, this.route));
  }

  logout() {
    this.afAuth.auth.signOut();
  }

}

In the code snippet above, we use methods provided by AngularFire2 to implement Login with Google functionality.

Guard routes for editor components

Now we will use this guard in `src/app/app-routing.module.ts`, the updated content is below:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditorAuthGuard } from './guards/editor-auth.guard';

import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import {AuthComponent} from './components/auth/auth.component';


const appRoutes: Routes = [
  {
    path: 'auth',
    children: [
      { path: 'login', component: AuthComponent},
      { path: 'logout', component: AuthComponent}
    ]
  },
  {
    path: '',
    loadChildren: 'app/modules/reader/reader.module#ReaderModule'
  },
  {
    path: 'editor',
    canActivateChild: [EditorAuthGuard],
    loadChildren: 'app/modules/editor/editor.module#EditorModule'
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule],
  providers: [EditorAuthGuard]
})
export class AppRoutingModule {

}

In the code snippet above, we have added route rules for the /auth routes, and imported `EditorAuthGuard` and set it as a route guard for the `/editor` routes.

Also update `src/app/app.module.ts`:

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

import { environment } from '../environments/environment';

import { AppRoutingModule } from './app-routing.module';

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

import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import {AuthComponent} from './components/auth/auth.component';



@NgModule({
  declarations: [
    AppComponent,
    PageNotFoundComponent,
    AuthComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'fire-blog' }),
    AppRoutingModule,
    AngularFireModule.initializeApp(environment.firebase, 'fire-blog'),
    AngularFireAuthModule,
  ],
  providers: [AngularFirestore],
  bootstrap: [AppComponent]
})
export class AppModule { }

Editor Posts View

The route `/editor/posts` should allow us to create a new post and show a list of posts that have been created in the blog. Update the `editor-posts` component to enable this functionality.  
<!-- <p>Logged in as {{ user.displayName }}!</p> -->
<h4>Create New Post</h4>
<form class="py-2" #f="ngForm">
	<div class="form-group">
		<input class="form-control" type="text" name="post-title" [(ngModel)]="postTitle"  #new_post_title="ngModel"  placeholder="Enter Post Title" (keyup.enter)="onEnter(new_post_title);" required>
	</div>
	<button class="btn btn-dark" (click)="onEnter()" [disabled]="new_post_title.invalid">Save</button>
</form>

<h3>Posts</h3>
<ul class="list-unstyled">
	<li class="mb-3 pb-2" *ngFor="let post of posts | async">
       	{{ post.data.title }}  <a class="btn btn-sm btn-dark" href="/editor/post/{{ post.id }}">Open</a>
     	</li>
</ul>

<p class="text-right small"><a href="/auth/logout">Logout</a></p>

In this view, we have two sections. The top section provides a form for creating a new post by typing in the title in the input box and hitting Enter. Notice that we bind the keyup.enter event to the component instance method onEnter.

In the lower section, we have the markup to display a list of posts available. If there are no posts available, we would like to display a help message. This help message can be displayed using CSS as in the code below. Update the content of `src/app/modules/editor/components/editor-posts/editor-posts.component.css`: 

ul:empty::after {
 content: "Create a post Luke"
}

ul li {
 border-bottom: 1px solid #000;
}

Using CSS we are displaying the text Create a post Luke” when the user is yet to create a post, and the list of posts is empty.
Now update the content of `src/app/modules/editor/components/editor-posts/editor-posts.component.ts`: 

import { Component, OnInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';

export interface Post {title: string; content: string; }

@Component({
  selector: 'app-editor-posts',
  templateUrl: './editor-posts.component.html',
  styleUrls: ['./editor-posts.component.css']
})
export class EditorPostsComponent implements OnInit {
  private postsCollection: AngularFirestoreCollection<Post>;
  posts: Observable<any[]>;
  postTitle: string;

  constructor( private afs: AngularFirestore) {
    this.postTitle ='';
    this.postsCollection = afs.collection<Post>('posts');
    this.posts = this.postsCollection.snapshotChanges()
      .map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data() as Post;
          const id = a.payload.doc.id;
          return { id, data };
        });
      });
   }

  ngOnInit() {
  }

  onEnter() {
    const post: Post = {title: this.postTitle, content: ''};
    var _this = this;
    this.addItem(post, (function() {
      return function(data) {
        _this.postTitle = ''; // empty dom input
        alert('New Post Added');
        // TODO: Redirect to post with post id
      };
    })() );
    // If post request is successful: clear input; notify user
  }

  addItem(post: Post, successCb?, errCb?) {
    // TODO: Implement loading Animation
    this.postsCollection.add(post).then(data => {
      successCb(data);
    }).catch(err => {
      if (errCb) {
        errCb(err);
      } else {
        console.log(err);
      }
    });
  }
}
In the constructor of EditorPostsComponent, we inject `afs` an instance of AngularFirestore. Instance variables postsCollection and posts are then assigned values. The onEnter method takes new_post_title as a parameter, we then create a new Post object which is used as an argument to the addItem method. The addItem method uses the add method of AngularFirestoreCollection to persist the new Post object to the Firestore backend.

Editor Post View

The route `/editor/post` should allow us to edit a post. Update the editor-post component to enable this functionality. The file is located at `src/app/modules/editor/components/editor-post/editor-post.component.html`:
<div>
 <div class="form-group">
 <label>Title</label>
 <input class="form-control" type="text" [(ngModel)]="formModel.title" (change)="update()"> 
 </div>
 <div class="form-group">
 <label>Content</label>
 <textarea class="form-control" [(ngModel)]="formModel.content" (change)="update()"></textarea> 
 </div>

</div>

In this markup, we have a simple form to edit the title and content of a single post; all changes made are auto-saved. Notice that we bind the `change` event to the `update` method.

Now, update the content of file located at src/app/modules/editor/components/editor-post/editor-post.component.ts 

import { Component, OnInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreDocument } from 'angularfire2/firestore';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import {Post} from '../editor-posts/editor-posts.component';
@Component({
  selector: 'app-editor-post',
  templateUrl: './editor-post.component.html',
  styleUrls: ['./editor-post.component.css']
})
export class EditorPostComponent implements OnInit {
  private postDoc: AngularFirestoreDocument;
  formModel: Post;
  constructor(private afs: AngularFirestore, private route: ActivatedRoute) {}

  update() {
    if (typeof this.postDoc !== "undefined") {
    // TODO throttle update
      this.postDoc.update(this.formModel);
    }
  }

  ngOnInit() {
    this.formModel = {title: '', content: ''};
     // subscribe to the parameters observable
    this.route.paramMap.subscribe(params => {
      this.getPost(params.get('id'));
    });
  }

  getPost(postId) {
    this.postDoc = this.afs.doc('posts/' + postId);
    this.postDoc.valueChanges().subscribe(v => {
      this.formModel = v;
    });
  }

}

The `getPost` method simply gets a post from Firestore using the postId parameter. In `ngOnInit`, we observe `this.route.paramMap` so we can retrieve the post id from the route parameter, after which we call the `getPost` method. The `getPost` method assigns an `AngularFirestoreDocument` to the instance variable `postDoc`. We then observe the document, so that once its value is available, we assign it to the class variable `formModel`.
The update method serves to persist the changes to our post document via the `AngularFirestoreDocument` update method.

Reader Posts View

The route /reader/posts should allow us to read a list of titles of all posts on the blog. Update the reader-posts component to enable this functionality. The file is located at  `src/app/modules/reader/components/reader-posts/reader-posts.component.html`:
<h3>Posts</h3>
<ul class="list-unstyled">
        <li class="mb-3 pb-2" *ngFor="let post of posts | async">
               <a  href="/post/{{ post.id }}">{{ post.data.title }}</a>
           </li>
</ul>

In the constructor `ReaderPostsComponent`, we inject `afs` an instance of `AngularFirestore`, we use the collection method from `AngularFirestore` to create an instance of `AngularFirestoreCollection` stored in the variable `postsCollection`. We then create an observable which will return the array of posts to the posts instance variable.

Reader Post View

The route /reader/post should allow us to read a single post on the blog. Update the reader-post component to enable this functionality. The file is located at `src/app/modules/reader/components/reader-post/reader-post.component.html`:
<h1>{{(post | async)?.title}}</h1>
<p>{{(post | async)?.content}}</p>

Now update the content of `src/app/modules/reader/components/reader-post/reader-post.component.ts`:

import { Component, OnInit } from '@angular/core';
import { AngularFirestore, AngularFirestoreDocument } from 'angularfire2/firestore';
import { ActivatedRoute, ParamMap } from '@angular/router';

@Component({
  selector: 'app-reader-post',
  templateUrl: './reader-post.component.html',
  styleUrls: ['./reader-post.component.css']
})
export class ReaderPostComponent implements OnInit {
  private postDoc: AngularFirestoreDocument;
  post: any;
  constructor(private afs: AngularFirestore, private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.paramMap.subscribe(params => {
      this.getPost(params.get('id'));
    });
  }

  getPost(postId) {
    this.postDoc = this.afs.doc('posts/' + postId);
    this.post = this.postDoc.valueChanges();
  }

}


In the constructor, we inject `afs` and route. In the `ngOnInit` method, we observe route and execute the `getPost` method, once the route parameters are available. In the `getPost` method, we get the post to be displayed from Firestore.

Running the app

In the Server window, right click on FireBlog and click the green icon with label `Start Server`.

Live Demo

Now open http://localhost:4200/ to see the running version of the blog.