Part Three: Angular with Firestore — CRUD

Read more below.

Part Three: Angular with Firestore — CRUD

9 min read · Ryan Jones

Series Breakdown:

Thank you and enjoy! Github code, here.

What we will cover:

Please, follow along with Part One: Angular with Firebase Hosting, Angular with Firestore — Intro before working through Part Three as this is a continuous series and some concepts will not be recovered in detail.

Prerequisites:

  • Basic web development knowledge
  • Basic Angular or JavaScript/TypeScript experience

CRUD Overview

CRUD or Create, Read, Update, and Delete is a way of expressing a common set of functionality which applications utilize to give the user a fully rounded experience. Usually, developers say CRUD to describe such functionality and in regard to learning a new technology or showcasing your ability to build applications with that technology. Demonstrating a grasp for these four main functions will go a long way.

Create Functionality

In Angular with Firestore — Intro, we redeployed our Angular app to Firebase Hosting with the ability to read from firestore and populate our application in realtime. To load our Firestore database, we would manually create a document in the Firestore console. Now we are going to give a user of our application the ability to create new articles.

Update Articles Component (HTML)

Open up articles.component.html and add the following html. In this code snippet we are setting up a couple of buttons to perform different actions. The Create Article button will show the form. The Submit button will submit our form and call our function onSubmit(). The Cancel button will hide our form.

  • ngModel — Two way data binding for ngForm
  • *ngIf — simple way of evaluating a boolean value then hiding or showing some html
  • ngForm — Angular directive for creating powerful forms
  • ngSubmit — listen to ngForm submit event and trigger OnSubmit()
  • form-group /form-control— Bootstrap CSS class to make forms pretty
<div>
 <h1>Articles List</h1>
 <p>Below is a list of dynamic article links</p>
 <div class="row" ng-if="i % 3 === 0">
   <div class="col-sm-4" *ngFor="let article of articles$ | async; index as i">
     <div class="thumbnail">
       <a [routerLink]="['/articles', article.id]">
         <img [src]="article.img" [alt]="article.title" style="width:100%">
         <div class="caption">
           <p>{{ article.description}}</p>
         </div>
       </a>
     </div>
   </div>
 </div>
 <!-- Create HTML -->
 <button (click)="showForm = true" class="btn btn-primary">Create Article</button>
 <div *ngIf="showForm">
   <form (ngSubmit)="onSubmit()" #createArticleForm="ngForm" novalidate>
     <div class="form-group">
       <label for="title">Title</label>
       <input type="text" [(ngModel)]="newArticle.title" name="title" class="form-control" id="title">
     </div>
     <div class="form-group">
       <label for="img">Image URL</label>
       <input type="text" [(ngModel)]="newArticle.img" name="img" class="form-control" id="img">
     </div>
     <div class="form-group">
       <label for="description">Description</label>
       <input type="text" [(ngModel)]="newArticle.description" name="description" class="form-control" id="description">
     </div>
     <button type="submit" class="btn btn-success">Submit</button>
     <button type="button" (click)="showForm = false" class="btn btn-primary">Cancel</button>
   </form>
 </div>
</div>

Update the app module file

We need to add the FormsModule import into our app.module.ts file. So that we can use the ngForm, ngModel, and ngSubmit.

// app.module.ts
...
import { FormsModule }   from '@angular/forms';  // <----- NgForm
@NgModule({
 declarations: [
   ...
 ],
 imports: [
   ...
   FormsModule
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

Update Articles Component (TypeScript)

Open up articles.component.ts and we will add the following snippet of code below. This code will primarily just add the article we created in our form above into the collection of articles in Firestore via this.db.collection<Article>('articles').add(this.newArticle).

// articles.component.ts
import { Component, OnInit } from '@angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Article {
 title: string;
 img: string;
 description: string;
}
@Component({
 selector: 'app-articles',
 templateUrl: './articles.component.html',
 styleUrls: ['./articles.component.css']
})
export class ArticlesComponent implements OnInit {
 // Observable which will hold an array of Article
 articles$: Observable<Article[]>;
// form boolean
 showForm: Boolean = false;
// default article values
 newArticle: Article = {
   title: "",
   img: "",
   description: ""
 }
constructor(private db: AngularFirestore) {
 // The code below will query all the articles
 // and return id + data (e.g. title, description, img)
   this.articles$ = this.db.collection<Article>('articles')
     .snapshotChanges().pipe(
       map(actions => actions.map(a => {
         const data = a.payload.doc.data() as Article;
         const id = a.payload.doc.id;
         return { id, ...data };
       }))
     );
 }
 ngOnInit() {
   this.articles$.subscribe(data => console.log(data));
 }
 onSubmit() {
   console.log(this.newArticle);
   this.db.collection<Article>('articles').add(this.newArticle);
   this.showForm = false;
 }
}

Test locally

Let’s make sure things are still running okay. You can do this by running the following command.

$myapp: ng serve -o

Navigate to /articles and create an article

Create an article

Now there are two articles about wagons..

Update Functionality

In the section below we are going to make small changes to our article component which will allow our users to update existing articles.

Update Article Component (HTML)

Open up article.component.html and add the following html. In this code snippet we are adding an edit form which will allow us to make changes to our articles. The Create Article button will show the form. The Submit button will submit our form and call ngSubmit which points to onEditFormSubmit(). The Cancel button will hide our form.

<!-- article.component.ts -->
<div *ngIf="article$ | async as article">
 <h1>{{ article.title }}</h1>
 <img [src]="article.img" class="img-responsive"  style="max-width: 50%; max-height: 50%;" [alt]="article.title">
 <p>{{ article.description }}</p>
 <!-- New Code -->
 <button (click)="showEditForm = true" class="btn btn-primary">Edit</button>
 <div *ngIf="showEditForm">
   <form (ngSubmit)="onEditFormSubmit()" #editArticleForm="ngForm" novalidate>
     <div class="form-group">
       <label for="title">Title</label>
       <input type="text" [(ngModel)]="newArticle.title" name="title" class="form-control" id="title">
     </div>
     <div class="form-group">
       <label for="img">Image URL</label>
       <input type="text" [(ngModel)]="newArticle.img" name="img" class="form-control" id="img">
     </div>
     <div class="form-group">
       <label for="description">Description</label>
       <input type="text" [(ngModel)]="newArticle.description" name="description" class="form-control" id="description">
     </div>
     <button type="submit" class="btn btn-success">Submit</button>
     <button type="button" (click)="onCancel()" class="btn btn-primary">Cancel</button>
   </form>
 </div>
</div>

Update Article Component (TypeScript)

Open up article.component.ts and we will add the following snippet of code below. This code will add an Edit button which will show our edit form and an onEditFormSubmit() function which will update our article with one line of code, this.db.doc('articles/' + this.id).update(this.newArticle). This says “connect to our document in Firestore and update with form data”. We also add a onCancel() function which will allow us to cancel the form.

// article.component.ts
import { Component, OnInit } from '@angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
// listen to changes in the router
import { ActivatedRoute } from '@angular/router';
// define Article type
export interface Article {
  title: string;
  img: string;
  description: string;
}
@Component({
  selector: 'app-article',
  templateUrl: './article.component.html',
  styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
  article$: Observable<Article>;   // Observable of type Article
  id: String;   // will hold id passed through route (:id)
  showEditForm: Boolean = false;
  name: String; // mbUOHY5xLCiYg9kDBrrm
  newArticle: Article = {
    title: "",
    img: "",
    description: ""
  };
  constructor(
    private db: AngularFirestore,
    private route: ActivatedRoute
  ) {
    // set 'id' when page loads from route params.id
    this.route.params.subscribe(params => this.id = params.id)
  }
  ngOnInit() {
    // query Firestore using 'id' when page loads
    this.article$ = this.db
        .doc<Article>('articles/' + this.id)
        .valueChanges();
  }
  onEditFormSubmit() {
    console.log(this.newArticle);
    this.db.doc('articles/' + this.id).update(this.newArticle);
    this.showEditForm = false;
  }
  onCancel() {
    this.showEditForm = false;
  }
}

Test locally

Let’s make sure things are still running okay. You can do this by running the following command. Then click on an article and edit one.

$myapp: ng serve -o

Edit an article

Magical.. look at that sweet sweet functionality.

Delete Functionality

In the section below we are going to make small changes to our article component which will allow our users to delete existing articles.

Update Article Component (HTML)

Open up article.component.html and add the following html. In this code snippet we are adding a delete button which will trigger our confirmDelete() function to prompt the user for confirmation.

<!-- article.component.ts -->
<div \*ngIf="article$ | async as article">
  <h1>{{ article.title }}</h1>
  <img [src]="article.img" class="img-responsive"  style="max-width: 50%; max-height: 50%;" [alt]="article.title">
  <p>{{ article.description }}</p>
  <!-- New Code -->
  <button (click)="showEditForm = true" class="btn btn-primary">Edit</button>
  <div \*ngIf="showEditForm">
    <form (ngSubmit)="onEditFormSubmit()" #editArticleForm="ngForm" novalidate>
      <div class="form-group">
        <label for="title">Title</label>
        <input type="text" [(ngModel)]="newArticle.title" name="title" class="form-control" id="title">
      </div>
      <div class="form-group">
        <label for="img">Image URL</label>
        <input type="text" [(ngModel)]="newArticle.img" name="img" class="form-control" id="img">
      </div>
      <div class="form-group">
        <label for="description">Description</label>
        <input type="text" [(ngModel)]="newArticle.description" name="description" class="form-control" id="description">
      </div>
      <button type="submit" class="btn btn-success">Submit</button>
      <button type="button" (click)="onCancel()" class="btn btn-primary">Cancel</button>
    </form>
  </div>
  <button \*ngIf="!showEditForm" (click)="confirmDelete()" class="btn btn-warning">Delete</button>
</div>

Update Article Component (TypeScript)

Open up article.component.ts and we will add the following snippet of code below. This code will add the angular Router import which will allow us to redirect the user after they delete an article. It will also add the confirmDelete() function.

confirmDelete() {
    const answer = prompt('Are you sure you want to delete this article? (yes or no)');
  if(answer === "yes") {
    this.db.doc('articles/' + this.id).delete();
    this.router.navigate(['articles']);
  }
}

Then depending on the answer the user types, yes or no. The following line of code will run, this.db.doc('articles/' + this.id).delete(). Which translates to “connect to the document and delete”.

Full Article Component (TypeScript)

// article.component.ts
import { Component, OnInit } from '@angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
// listen to changes in the router
import { ActivatedRoute, Router } from '@angular/router';
// define Article type
export interface Article {
  title: string;
  img: string;
  description: string;
}
@Component({
  selector: 'app-article',
  templateUrl: './article.component.html',
  styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
  article$: Observable<Article>;   // Observable of type Article
  id: String;   // will hold id passed through route (:id)
  showEditForm: Boolean = false;
  name: String; // mbUOHY5xLCiYg9kDBrrm
  newArticle: Article = {
    title: "",
    img: "",
    description: ""
  };
  constructor(
    private db: AngularFirestore,
    private route: ActivatedRoute,
    private router: Router
  ) {
    // set 'id' when page loads from route params.id
    this.route.params.subscribe(params => this.id = params.id)
  }
  ngOnInit() {
    // query Firestore using 'id' when page loads
    this.article$ = this.db
        .doc<Article>('articles/' + this.id)
        .valueChanges();
  }
  onEditFormSubmit() {
    console.log(this.newArticle);
    this.db.doc('articles/' + this.id).update(this.newArticle);
    this.showEditForm = false;
  }
  onCancel() {
    this.showEditForm = false;
  }
  confirmDelete() {
    const answer = prompt('Are you sure you want to delete this article? (yes or no)');
    if(answer === "yes") {
      this.db.doc('articles/' + this.id).delete();
      this.router.navigate(['articles']);
    }
  }
}

Test locally

Let’s make sure things are still running okay. You can do this by running the following command. Then click on an article and edit one.

$myapp: ng serve -o

Navigate to a specific article and delete

Delete an article

Perfect, let’s redeploy it live!

Deploy live to the world

Now that we’ve tested it locally. Lets deploy it up to Firebase Hosting so we can share it with our friends!

Copy FirebaseConfig into environment.prod.ts file

This will be the same as src/environments/environment.ts.

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "Axxxxxx_xxxxxxxxxxxxx",
    authDomain: "myapp-xxxxxx.firebaseapp.com",
    databaseURL: "https://myapp-xxxxxxx.firebaseio.com",
    projectId: "myapp-xxxxxxx",
    storageBucket: "myapp-xxxxxxx.appspot.com",
    messagingSenderId: "xxxxxxxxxx"
  }
};

Build Angular App

$myapp: ng build --prod
$myapp: firebase deploy

Open your host URL

Open your host URL

Congratulations! You just added create, update, and delete functionality to your Angular application which now completes the CRUD circle. We also redeployed our updated Angular application to Firebase Hosting. In the next article we are going to extend our Angular application using Google Cloud Functions. We will connect a Google Cloud Function to our Firestore database and trigger the Google Cloud Function anytime someone creates a new article in our Angular application! Hope you’re excited, stay tuned for more.

Review

What’s next?

Stay tuned for weekly releases of the Angular Firebase series. Where we will be covering Firebase Hosting, Firestore, Cloud Functions, Cloud Storage, and Authentication!

Good luck with your journey into the world of Serverless!

Thanks for reading :)

About Mode2

Mode2 LLC (www.mode2.com) is a digital transformation services partner, helping companies address the challenges of seeking a competitive advantage, embracing disruptive technologies and managing organization change. Mode2 has a team of experts who create new leab cloud-native solutions without compromising security. Mode2 is headquartered in Los Angeles CA, with locations in San Francisco & Bay Area, Portland OR and New York City, is a specialist cloud partner offering services across the United States.