Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Reactive Forms in Angular With the Firebase Database

Episode 34 written by Jeff Delaney
full courses for pro members

Angular’s reactive forms are an excellent way to manage large complex form groups. In this lesson, we will use a reactive form to update realtime data in the Firebase Database.

What is a reactive form?

Reactive forms (as opposed to Template-driven forms) are synchronous and build form controls in the TypeScript.

Template-driven forms are asynchronous and build form controls in the HTML.

Reactive forms do not use the ngModel directive. You can read more about this in the Angular sync vs async docs.

A reactive form manages the state between a data model (a Firebase object in this case) and a UI form on the frontend. The main advantage of reactive forms is the ability to define complex logic and validation in the TypeScript. In addition, they solve timing issues related to component lifecycle when using large nested forms.

What we’re Building

classified ad demo in angular4 with firebase

We are going to build a form that allows users to create classified ad - similar to CraigsList.com.

  • Accept user input
  • Run validation rules on changes
  • Update the Firebase Database on the fly

Initial Setup

In this demo, I am using Bulma CSS to style the validation messages. If you’re using Bootstrap or Material, you will need to modify the CSS accordingly.

app.module.ts

First, you need to add the ReactiveFormsModule the the module that uses it.

import { ReactiveFormsModule } from '@angular/forms';

// ...omitted
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
// ...omitted
})

Let’s also create a typescript class to add some default values to our data. This part is optional, but it makes the concept of reactive forms more clear for this tutorial.

export class AdListing {
title = 'Your Title'
content = 'Ad Content'
price = 5.00
image = 'http://via.placeholder.com/350x150'
}

Building the AdService

ng g service ad
  • startNewAdListing() saves the default data to firebase and initializes the reactive form.
  • saveAdChanges() updates Firebase whenever data in the form changes (and is valid).
  • buildForm() will mirror the structure of our data and it is where validation rules are defined. It will then subscribe to the database object and update the form with existing realtime values.

ad.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase, FirebaseObjectObservable } from 'angularfire2/database';

@Injectable()
export class AdService {

constructor(private db: AngularFireDatabase) { }

/// Creates an Ad, then returns as an object
createAd(): FirebaseObjectObservable<AdListing> {
const adDefault = new AdListing()
const adKey = this.db.list('/ads').push(adDefault).key
return this.db.object('/ads/' + adKey)
}


/// Updates an existing Ad
updateAd(ad: FirebaseObjectObservable<AdListing>, data: any) {
return ad.update(data)
}
}

Building the Form

ng g component ad-listing

ad-listing.component.ts

import { Component } from '@angular/core';
import { AdService, AdListing } from '../ad.service';
import { Validators, FormGroup, FormBuilder } from '@angular/forms';

@Component({
selector: 'ad-listing',
templateUrl: './ad-listing.component.html',
styleUrls: ['./ad-listing.component.scss']
})
export class AdListingComponent implements OnInit {

ad: any;
adForm: FormGroup;

constructor(private adService: AdService, private fb: FormBuilder) { }

startNewAdListing() {
this.ad = this.adService.createAd()
this.buildForm()
}

saveAdChanges() {
if (this.adForm.status != 'VALID') {
console.log('form is not valid, cannot save to database')
return
}

const data = this.adForm.value
this.adService.updateAd(this.ad, data)
}

private buildForm() {
this.adForm = this.fb.group({
title: ['', Validators.required ],
content: ['', Validators.required ],
price: ['', Validators.required ],
image: ['', Validators.required ]
});
this.ad.subscribe(ad => {
this.adForm.patchValue(ad)
})
}


}

classified ad demo in angular4 with firebase

When a change event fires from the form, we should see the Firebase database updated automatically.

ad-listing.component.html

There is a large amount of HTML related to presentation in this snippet, so let’s start with the bare essential concepts. This is how you connect a FormGroup to the HTML form element.


<form [formGroup]="adForm" novalidate>


</form>

add a form input

Now let’s add an input field for the title. Notice formControlName="title" - this is the magic line that associates the input to the adForm. When the form value changes the saveAdChanges() will fire and update the Firebase database. Lastly, ngClass applies conditional styling to the form depending on its state.


<form [formGroup]="adForm" novalidate>

<input class="input" type="text"
formControlName="title"
(change)="saveAdChanges()"
[ngClass]="{
'is-success' : adForm.get('title').valid && adForm.get('title').dirty,
'is-danger' : !adForm.get('title').valid
}">

<p class="error is-danger" [hidden]="adForm.get('title').valid">
Title is required
</p>

</form>

the entire styled form

And now I will dump the entire form with all it’s Bulma styles from the video lesson. There is some duplication here that could be improved, but I wanted to share the full code used in the video.

<div class="content columns" *ngIf="ad">
<div class="column is-6">
<h1>Ad Listing Preview</h1>

<article class="message">
<div class="message-header">
<p>{{(ad | async).title}}</p>
</div>
<div class="message-body">

<img [src]="(ad | async).image" width="350px"><br>
<p>{{ (ad | async).content }}</p>

<hr>

<h4>List Price: {{ (ad | async).price | currency}}</h4>
</div>
</article>

<h3>Form Values</h3>

<p>Form value: <br> {{ adForm.value | json }}</p>
<p>Form status: {{ adForm.status | json }}</p>

</div>


<div class="column">

<form [formGroup]="adForm" novalidate>

<h1>Create your Listing</h1>


<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Title</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text"
formControlName="title"
(change)="saveAdChanges()"
[ngClass]="{
'is-success' : adForm.get('title').valid && adForm.get('title').dirty,
'is-danger' : !adForm.get('title').valid
}">
</div>

<p class="error is-danger" [hidden]="adForm.get('title').valid">
Title is required
</p>

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

<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Image URL</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text"
formControlName="image"
(change)="saveAdChanges()"
[ngClass]="{
'is-success' : adForm.get('image').valid && adForm.get('image').dirty,
'is-danger' : !adForm.get('image').valid
}">
</div>
<p class="error is-danger" [hidden]="adForm.get('image').valid">
Image is required
</p>
</div>
</div>
</div>


<div class="field is-horizontal">
<div class="field-label is-normal"><label class="label">Price</label></div>
<div class="field-body">
<div class="field is-expanded">
<div class="field has-addons">
<p class="control">
<a class="button is-static">
$
</a>
</p>
<p class="control is-expanded">
<input class="input" type="number"
formControlName="price"
(change)="saveAdChanges()"
[ngClass]="{
'is-success' : adForm.get('price').valid && adForm.get('price').dirty,
'is-danger' : !adForm.get('price').valid
}">
</p>
</div>
<p class="error" [hidden]="adForm.get('price').valid">Price is not valid</p>
</div>
</div>
</div>


<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Details</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea"
formControlName="content"
(change)="saveAdChanges()"
[ngClass]="{
'is-success' : adForm.get('content').valid && adForm.get('content').dirty,
'is-danger' : !adForm.get('content').valid
}">
</textarea>
<p class="error" [hidden]="adForm.get('content').valid">Content is not valid</p>
</div>
</div>
</div>
</div>

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


<button class="button is-success" (click)="startNewAdListing()" *ngIf="!ad" >Start New Ad Listing</button>

That’s it for reactive forms. Let me know what you think in the comments.