Building a Product Rating and Review with Angular5 and Cloud Firestore

nangular-firestore-product-review-rating

n
nIn this article, I will walk you through how to create a product rating and review app using Angular5 and Cloud Firestore.
n
nn

nn
n

nSetting up our Firebase Project

nIn one of my previous post, I have shown how to setup a firebase project. The process is the same except that we will be working with cloud firestore instead of the real time database.
n
n

n

n
n
n

nFirestore Data Structure

n
n

n

n
nThe data structure is very simple, we have products, ratings and reviews collections with their corresponding documents.
n
n

nSetting up the Angular Project

nStart by creating a new angular app using angular-cli command like so:n
n

ng new ng5-firestore-product-review-rating --routing=truen

n
nThe –route tells angular -cli that we want to make use of route in our application.
n
nNext, let’s install firebase and angularfire2 like so:
n
n

npm install firebase angularfire2 --save

n
n

nAdd Firebase config to environments variablen

nOpen /src/environments/environment.ts and add your Firebase configuration:n
n
n

nnexport const environment = {n  production: false,n  firebase:{n    apiKey: "AIzaSyA5pDbhYvXmmP3cfM1sFdkpCi9lRrebmb0",n    authDomain: "product-review-rating.firebaseapp.com",n    databaseURL: "https://product-review-rating.firebaseio.com",n    projectId: "product-review-rating",n    storageBucket: "",n    messagingSenderId: "565246664152"n  }n};nn

n
nnn
nnnSetup @NgModule for the AngularFireModulen
n
nOpen /srcc/app/app.module.ts and inject necessary firebase providers like so:n
n

nnimport { AngularFireModule } from 'angularfire2';nimport { AngularFirestoreModule } from 'angularfire2/firestore';nnn@NgModule({n  imports: [n    BrowserModule,n    AngularFireModule.initializeApp(environment.firebase,'ngproductreview'),n    AngularFirestoreModulen  ],n  declarations: [ AppComponent ],n  bootstrap: [ AppComponent ]n})nexport class AppModule {}nn

n
nProduct Service
nWe are going to be making use of three models in this project, so let’s start by creating those models first.
nCreate a folder like so: \src\app\shared\models, then add three files named product.ts,product-review.ts and rating.ts add the following code:
n
n

nnnexport interface Product {n          id: string;n          name: string;n          slug: string;n          images: any[],n          price: number,n          avRating:numbern}nnexport interface ProductRating { n     productId: string; n     ratingValue:number; n}nnexport interface ProductReview { n     productId:string, n     username: string;n     summary:string; n     review:string n}nn

n
nCreate a folder in \src\app\shared\services and add a file named product.service.ts and add the following code:
n

nnimport { Injectable } from '@angular/core';nimport { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';nimport { Observable } from 'rxjs/Observable';nimport { Product } from '../models/product';nnn@Injectable()nexport class ProductService {nimport { Injectable } from '@angular/core';nimport { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';nimport { Observable } from 'rxjs/Observable';nimport { Product } from '../models/product';nimport { ProductReview } from '../models/product-review';nimport { ProductRating } from '../models/rating';nn@Injectable()nexport class ProductService {n     products: AngularFirestoreCollection;n     product: Product;nn     constructor(private fs: AngularFirestore) { }nnn     getProducts() {n          return this.fs.collection("products").snapshotChanges().map(actions => {n               return actions.map(a => {n                    const data = a.payload.doc.data() as Product;n                    data.id = a.payload.doc.id;n                    data.avRating = data.avRating/5*100;n                    data.slug = data.name.toLowerCase().replace('/\s/g', '-').replace(' ', '-');n                    return data;n               });n          });n     }nnnn     getSingleProduct(productId) {n          var docPath = `products/${productId}`;n          return this.fs.doc(docPath).snapshotChanges().map((actions) => {n               const data = actions.payload.data() as Product;n               data.avRating = data.avRating/5*100;n               data.id = actions.payload.id;n               return data;n          });n     }nn     getProductReviews(productId) {nn          return this.fs.collection("reviews", ref => ref.where('productId', '==', productId)).snapshotChanges().map(actions => {n               return actions.map(a => {n                    const data = a.payload.doc.data() as ProductReview;n                    return data;n               });n          })n     }nn     postReview(comment: ProductReview) {n          this.fs.collection("reviews").add({n               username: comment.username,n               summary: comment.summary,n               review: comment.review,n               productId: comment.productIdn          });n     }nn     postRating(rating: ProductRating) {n          this.fs.collection("ratings").add({n               productId: rating.productId,n               ratingValue: rating.ratingValuen          });n     }nn     getProductRating(productId) {nn          return this.fs.collection("ratings", ref => ref.where('productId', '==', productId)).snapshotChanges().map(actions => {n               return actions.map(a => {n                    const data = a.payload.doc.data() as ProductRating;n                    return data;n               });n          })n     }nn     setProductRating(productId, rating) {n          var docPath = `products/${productId}`;n          let productDoc = this.fs.doc(docPath);n          productDoc.update({ avRating: rating });n     }n}nnn

n
nLet’s walkthrough what we have in the product service by discussing what each method does.
n
n

ngetProducts Method

nThis method simply get list of products from firestore. This method can be written like so:
n
nreturn this.fs.collection(“products”).valueChanges();
n
nBut because the above method will return list of products but without productId, so if we need to return all products with the id we need to write the method like that. You can read more about that here and check stackoverflow question here.
n
n

ngetSingleProduct Methodn

nThis method, as the name implies get a product document taking the Id of the product as argument.
n
n

ngetProductReviews Method

nThis also takes productId as an argument to return lists of product reviews.
n
n

npostReview Method

nThis method is responsible for customer review’s submission.
n

n

n

npostRating Method

nThis method allows customer rate a particular product.
n
n

ngetProductRating Method

nThis returns the list of product rating taking productId as an argument.
n

n
setProductRating Method

nThis method update the product’s average rating.
n
nnn
nnn
n

nProductList Component

nAdd a folder name products in /src/app/products, and modify the productListComponent like so:
n
n

nnnimport { Component, OnInit } from '@angular/core';nimport { ProductService } from '../shared/services/product.service';nnn@Component({n     templateUrl: 'product-list.component.html'n})nnexport class ProductListComponent implements OnInit {n     products: any;nn     constructor(private productService: ProductService) { }nn     ngOnInit() {n          this.getProducts();n     }nnnn     getProducts() {n          n          this.productService.getProducts().subscribe((data) => {n               this.products = data;nn          });n     }nnn}nn

n
n
nAs you can see, productlistcomponent has only one method that fetches all product.
n
n

nProductList Component Html

n
n

nn          
n
n

n Featured Productsn

n
n
n
n
n
n
nn
n
nn
n
n
n
nn

n
nSingleComponent.ts Filen
n

nnimport { ProductReview } from './../shared/models/product-review';nimport { Component, OnInit } from '@angular/core';nimport { ActivatedRoute } from '@angular/router';nimport { ProductService } from '../shared/services/product.service';nimport { AngularFirestoreDocument } from 'angularfire2/firestore';nimport { Product } from '../shared/models/product';nimport { FormControl, FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';nimport { Observable } from 'rxjs/Observable';nnn@Component({nn     templateUrl: 'single.component.html'n})nnexport class SingleComponent implements OnInit {n     product: Product;n     reviewFormGroup: FormGroup;n     reviews: Array;n     selectedProductId: string;n     productLoaded: boolean = false;n     ratings: Observable;n     avgRating: Observable;n     constructor(private route: ActivatedRoute, private productService: ProductService,n          private fb: FormBuilder) {n          this.selectedProductId = this.route.snapshot.params.productid;n          this.initializeForm();n     }nn     ngOnInit() {nn          this.productService.getSingleProduct(this.selectedProductId).subscribe((data) => {n               this.product = data;n               this.productLoaded = true;n          });n          this.getReviews();n     }nn     getReviews() {n          this.productService.getProductReviews(this.selectedProductId).subscribe((data) => {n               this.reviews = data;n          });n     }nn     initializeForm() {n          this.reviewFormGroup = this.fb.group({n               username: ['', Validators.required],n               summary: ['', Validators.required],n               review: ['', Validators.required],n               productId: [this.selectedProductId]n          });n     }nn     submitReview() {n          let body: ProductReview = this.reviewFormGroup.value;n          this.productService.postReview(body);n          this.initializeForm();n     }nn     rateProduct(val) {nn          this.productService.postRating({n               productId: this.selectedProductId,n               ratingValue: valn          });nn          this.productService.getProductRating(this.selectedProductId).subscribe((retVal) => {n              const ratings = retVal.map(v => v.ratingValue);n               let avRating = (ratings.length ? ratings.reduce((total, val) => total + val) / retVal.length : 0);nn               this.productService.setProductRating(this.selectedProductId,avRating.toFixed(1));n          });nnnn     }nnn   n}nn

n
nAll the methods we have in the Single component is self explanatory. Let see what we have in the single component html.n
n

nnn
n
n
n
nn
nn
n
n
n

n {{ product?.name}}

n
n
n
nn n n n
n
nn
n
n{{ product?.description}}n
n
n
n
n
n {{ product?.price | currency:'USD':true}}nn
n
n
nn
n
nn
n
n
n
nQty :n
n
n
n
n
n
nn n n
n
nn n n
n
nn
n
n
nn
nn
n
n
n
n
n
n

nCustomer Reviews

n
n
n
n{{review?.summary}}n n n {{review?.username}}n n
n
n{{review?.review}}
n
n
n
n
n
n
nn n n n n n n n
n
n
n
n
n
nn n
nn
n
nn n
nn
n
n
nn n
nn
n
nnn
n
nn
nnn
nn
nn
n
nn

nFeel free to download the source code and play around with it. Your comments are welcome.

Leave a Comment

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

Scroll to Top