Module 7: Updating and Deleting

Goals

  • Delete messages from Firebase

  • Update messages in Firebase

Delete from Firebase

On the Home page we have “Edit” and “Delete” buttons, but those buttons currently do nothing, so let's fix that. When someone clicks on one of the “Delete” buttons then we want to delete that particular message - but how do we identify one message vs all of the others?

If you remember back to Module 6 when we were creating the MessageService, we built our “allMessagesArray” full of messages. Each one of our messages has a “title” property, a “content” property, an “owner” property and also a “key” property. Remember that the “key” is that message’s unique identifier that is assigned by Firebase. This is the perfect way to identify individual messages.

Open home.component.html and make the “Delete” button run a function, we want to pass the selected message’s key to our function.

<button class="btn btn-primary" (click)="deleteThisMessage(message.key)">Delete</button>

And now we need to make that function in our TypeScript file! Now, up until this point we’ve made our function in the associated TypeScript file - and that’s great - we can definitely do it again, we could write the deleteThisMessage() function inside of our home.component.ts file… but here’s the thing, we’re about to do a database call, right? To delete this message? Aren’t we supposed do make database queries in a service?

What we could do is write the function in our home.component.ts file and then immediately call another function in the MessageService. Why bother with a middleman? Let’s skip straight to writing the function in our MessageService! In home.component.html adjust your code to call that “deleteThisMessage()” function from the MessageService.

<button class="btn btn-primary" (click)="messageService.deleteThisMessage(message.key)">Delete</button>

Now, let's open message.service.ts and create our function underneath our getAllMessages() function:

deleteThisMessage(keyToDelete) {
    console.log('key to delete: ', keyToDelete);
}

Serve up your web app and click on one of the delete buttons. You should see one of the keys printed to the console!

Now we just need to write the database call to delete that particular key. Again, using AngularFire2 makes this super easy. Notice the use of backticks in this code to write in es6 syntax. change your deleteThisMessage() to look like this:

deleteThisMessage(keyToDelete) {
    this.afd.object(`/messages/${keyToDelete}`).remove().then(() => {
        // yes it was deleted
    }).catch( err => {
      console.log('err deleting message: ', err);
    });
  }

Try it out in your browser and it should work without any errors! Unfortunately, there’s a super terrible user experience here that we definitely need to fix… when you click the “Delete” button the listing doesn’t disappear! It gets deleted from the backend, sure, but until a user refreshes the page they can still see it! After we’ve deleted the message, we should ask Firebase for a fresh copy of the messages. Luckily, we’ve already made a function to do that - our “getAllMessages()” function! Let's call that if our delete was successful, like so:

deleteThisMessage(keyToDelete) {
    this.afd.object(`/messages/${keyToDelete}`).remove().then(() => {
        // yes it was deleted
        this.getAllMessages();
    }).catch( err => {
      console.log('err deleting message: ', err);
    });
  }

Serve up your website and try it now - it should workout without any issues!

Editing Messages

We will need another view for editing messages, this view should be populated with the existing message title and content. Let’s create that component now, run ng generate component edit.

Edit the routing information in app.module.ts for this new component. This is another view that should only be accessible if someone is logged in.

{
    path: 'edit',
    component: EditComponent,
    canActivate: [AuthGuard]
  },

Now let’s adjust the “Edit” button in our home.component.html to call a function - a function that we will put in the MessageService again. Let’s call this new function editThisMessage. Similar to the “Delete” button, we’ll need to pass it the key of the selected message for identification purposes. Actually, for editing, we'll need the whole message. This will help later when we need to populate input tags on the Edit page with the existing values.

<div class="d-flex align-items-center">
    <button class="btn btn-primary" (click)="messageService.editThisMessage(message)">Edit</button>
    <button class="btn btn-primary" (click)="messageService.deleteThisMessage(message.key)">Delete</button>
</div>

Now let’s create that function in the MessageService. Open message.service.ts and create the function, this function should do 2 main things - it should save that whole message we are passing to the function (probably in a variable named “messageWeAreEditing”), and the function should also route to the Edit view. Make sure to call it editThisMessage(). We’ll need to import the Router into our service too. This is what the top of message.service.ts should look like:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';

@Injectable()
export class MessageService {
  allMessagesArray = [];
  messageWeAreEditing = {};

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

And here is what the function should look like:

editThisMessage(wholeMessage) {
    this.messageWeAreEditing = wholeMessage;
    this.router.navigateByUrl('edit');
  }

Now let’s work on filling the HTML and CSS files for the Edit view. Open edit.component.html and type the following code.

<div class="container">
 <div class="card text-white bg-dark">

   <div class="card-header">
     <h3>Edit</h3>
   </div>

   <div class="card-body">
     <div class="card text-white bg-secondary mb-3">
       <div class="card-body">

         <div class="form-group">
           <label for="titleEdit">Title:</label>
           <input class="form-control form-control-lg" placeholder="Title" id="titleEdit" autofocus>
         </div>

         <div class="form-group">
           <label for="contentEdit">Content:</label>
           <textarea class="form-control form-control-lg" rows="8" placeholder="Content" id="contentEdit"></textarea>
         </div>

       </div>
     </div>
   </div>

   <div class="card-footer d-flex justify-content-end">
     <button class="btn btn-primary btn-md">Submit</button>
     <button class="btn btn-primary btn-md">Cancel</button>
   </div>

 </div>
</div>

And now type the following code into edit.component.css

/* Ensure the content is never covered by the footer, and is offset from the header */
.container {
     margin-bottom: 7.5rem;
     margin-top: 2rem;
}

/* Style the Submit and Cancel buttons */
.card-footer {
     margin-top: 2rem;
     background-color: #6c757d;
}
.btn {
     border: 0;
     font-weight: bold;
     letter-spacing: 0.75px;
     color: white;
     background-color: #343a40;
}
.btn:first-of-type {
     margin-right: 1rem;
}
.btn:hover {
     color: #343a40;
     background-color: #007bff;
}

Now let’s work on Angular-izing this code! We want the input and textarea fields to be populated with the title and content of the message we are editing - which happens to be stored in a messageWeAreEditing variable in our MessageService! Let’s reference that variable in our HTML file. Open edit.component.html and add the following fields to our input and textarea tags.

<div class="form-group">
    <label for="titleEdit">Title:</label>
    <input class="form-control form-control-lg" placeholder="Title" id="titleEdit" autofocus [(ngModel)]="messageService.messageWeAreEditing.title">
</div>

<div class="form-group">
    <label for="contentEdit">Content:</label>
    <textarea class="form-control form-control-lg" rows="8" placeholder="Content" id="contentEdit" [(ngModel)]="messageService.messageWeAreEditing.content"></textarea>
</div>

We referenced the MessageService inside our HTML file - which means we need to publicly declare it in our TypeScript file. Open edit.component.ts and import the MessageService.

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

import { MessageService } from '../services/message.service';

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

  constructor(public messageService: MessageService) { }

  ngOnInit() {
  }

}

Serve up your web app and click on one of message’s “Edit” buttons - you should be directed to the Edit view and the form should be populated with the existing content!

Remember how the “ngModel” in our HTML gives us 2-way data binding. When someone types something in the input or textarea tag, the associated variable is immediately updated.

Now we need to make stuff happen when we click the “Submit” button, we should send the updated content off to Firebase! We will need a function in our MessageService to do that, let’s name it sendUpdatedMessageToFirebaseand make our “Submit” button call that function. Open edit.component.html and make the “Submit” button call that function - we’ll create the function in a moment.

<div class="card-footer d-flex justify-content-end">
      <button class="btn btn-primary btn-md" (click)="messageService.sendUpdatedMessageToFirebase()">Submit</button>
      <button class="btn btn-primary btn-md">Cancel</button>
    </div>

And now open message.service.ts and create that function below editThisMessage():

sendUpdatedMessagetoFirebase() {
    // send that message
  }

Remember, since we have 2-way data binding set up, any changes they make will be immediately updated in the “messageWeAreEditing” variable - we just need to send that updated object off to Firebase! We will need to use the message’s key so we know which object to update in Firebase. The database path we write will be “/messages/messageIdGoesHere” just like the order appears in Firebase. Same idea as we used in the Delete function.

Surprise, surprise, (or not)... AngularFire2 has another handy function to handle updating information! Again, notice the use of backticks instead of single quotes. When we successfully update a message, let’s route the user back to the Home page. Our sendUpdatedMessageToFirebase() should look like this:

sendUpdatedMessageToFirebase() {
    const uniqueKeyWeAreEditing = this.messageWeAreEditing['key'];
    const editedMessage = {
      title: this.messageWeAreEditing['title'],
      content: this.messageWeAreEditing['content']
    };
    this.afd.object(`/messages/${uniqueKeyWeAreEditing}`).update(editedMessage).then(() => {
      this.router.navigateByUrl('/');
    }).catch( err => {
      console.log('err: ', err);
    });
  }

Serve up your website and try it out - updating should work without any errors! Yippeeee!

Right now we have a small bug in our user experience - if you were to refresh the Edit page then you would have a blank form. This is because when you refresh the page the website’s memory is wiped, you are loading the site from scratch all over again - and the “messageWeAreEditing” variable is loaded as an empty object. To fix this user experience issue… if the edit page is loaded and the “messageWeAreEditing” variable is empty, let’s send them to the Home page.

Open edit.component.ts and add an old fashioned JavaScript “if” statement to check whether the “messageWeAreEditing” is empty or not. If it IS empty then we should route them to the Home view (So, we’ll need to import the Router).

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

import { MessageService } from '../services/message.service';

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

  constructor(public messageService: MessageService, private router: Router) { }

  ngOnInit() {
    if ( Object.keys(this.messageService.messageWeAreEditing).length === 0) {
      this.router.navigateByUrl('/');
    }
  }

}

Try it out again, and this time if you refresh the Edit page, you should be redirected to the Home page.

Since we’re at the end of a module, let’s do a quick git commit.

Next, we'll cleanup and deploy!

Last updated