In this article, I will walk you through how to create a product rating and review app using Angular5 and Cloud Firestore.
Setting up our Firebase Project
In 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.Firestore Data Structure
The data structure is very simple, we have products, ratings and reviews collections with their corresponding documents.
Setting up the Angular Project
Start by creating a new angular app using angular-cli command like so:ng new ng5-firestore-product-review-rating --routing=true
The --route tells angular -cli that we want to make use of route in our application.
Next, let's install firebase and angularfire2 like so:
npm install firebase angularfire2 --save
Add Firebase config to environments variable
Open /src/environments/environment.ts and add your Firebase configuration:
export const environment = {
production: false,
firebase:{
apiKey: "AIzaSyA5pDbhYvXmmP3cfM1sFdkpCi9lRrebmb0",
authDomain: "product-review-rating.firebaseapp.com",
databaseURL: "https://product-review-rating.firebaseio.com",
projectId: "product-review-rating",
storageBucket: "",
messagingSenderId: "565246664152"
}
};
Setup @NgModule for the AngularFireModule
Open /srcc/app/app.module.ts and inject necessary firebase providers like so:
import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
@NgModule({
imports: [
BrowserModule,
AngularFireModule.initializeApp(environment.firebase,'ngproductreview'),
AngularFirestoreModule
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
Product Service
We are going to be making use of three models in this project, so let's start by creating those models first.
Create 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:
export interface Product {
id: string;
name: string;
slug: string;
images: any[],
price: number,
avRating:number
}
export interface ProductRating {
productId: string;
ratingValue:number;
}
export interface ProductReview {
productId:string,
username: string;
summary:string;
review:string
}
Create a folder in \src\app\shared\services and add a file named product.service.ts and add the following code:
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import { Product } from '../models/product';
@Injectable()
export class ProductService {
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import { Product } from '../models/product';
import { ProductReview } from '../models/product-review';
import { ProductRating } from '../models/rating';
@Injectable()
export class ProductService {
products: AngularFirestoreCollection;
product: Product;
constructor(private fs: AngularFirestore) { }
getProducts() {
return this.fs.collection("products").snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as Product;
data.id = a.payload.doc.id;
data.avRating = data.avRating/5*100;
data.slug = data.name.toLowerCase().replace('/\s/g', '-').replace(' ', '-');
return data;
});
});
}
getSingleProduct(productId) {
var docPath = `products/${productId}`;
return this.fs.doc(docPath).snapshotChanges().map((actions) => {
const data = actions.payload.data() as Product;
data.avRating = data.avRating/5*100;
data.id = actions.payload.id;
return data;
});
}
getProductReviews(productId) {
return this.fs.collection("reviews", ref => ref.where('productId', '==', productId)).snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as ProductReview;
return data;
});
})
}
postReview(comment: ProductReview) {
this.fs.collection("reviews").add({
username: comment.username,
summary: comment.summary,
review: comment.review,
productId: comment.productId
});
}
postRating(rating: ProductRating) {
this.fs.collection("ratings").add({
productId: rating.productId,
ratingValue: rating.ratingValue
});
}
getProductRating(productId) {
return this.fs.collection("ratings", ref => ref.where('productId', '==', productId)).snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as ProductRating;
return data;
});
})
}
setProductRating(productId, rating) {
var docPath = `products/${productId}`;
let productDoc = this.fs.doc(docPath);
productDoc.update({ avRating: rating });
}
}
Let's walkthrough what we have in the product service by discussing what each method does.
getProducts Method
This method simply get list of products from firestore. This method can be written like so:return this.fs.collection("products").valueChanges();
But 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.
getSingleProduct Method
This method, as the name implies get a product document taking the Id of the product as argument.getProductReviews Method
This also takes productId as an argument to return lists of product reviews.postReview Method
This method is responsible for customer review's submission.postRating Method
This method allows customer rate a particular product.getProductRating Method
This returns the list of product rating taking productId as an argument.
setProductRating Method
This method update the product's average rating.ProductList Component
Add a folder name products in /src/app/products, and modify the productListComponent like so:
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../shared/services/product.service';
@Component({
templateUrl: 'product-list.component.html'
})
export class ProductListComponent implements OnInit {
products: any;
constructor(private productService: ProductService) { }
ngOnInit() {
this.getProducts();
}
getProducts() {
this.productService.getProducts().subscribe((data) => {
this.products = data;
});
}
}
As you can see, productlistcomponent has only one method that fetches all product.
ProductList Component Html
Featured Products
SingleComponent.ts File
import { ProductReview } from './../shared/models/product-review';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProductService } from '../shared/services/product.service';
import { AngularFirestoreDocument } from 'angularfire2/firestore';
import { Product } from '../shared/models/product';
import { FormControl, FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
@Component({
templateUrl: 'single.component.html'
})
export class SingleComponent implements OnInit {
product: Product;
reviewFormGroup: FormGroup;
reviews: Array;
selectedProductId: string;
productLoaded: boolean = false;
ratings: Observable;
avgRating: Observable;
constructor(private route: ActivatedRoute, private productService: ProductService,
private fb: FormBuilder) {
this.selectedProductId = this.route.snapshot.params.productid;
this.initializeForm();
}
ngOnInit() {
this.productService.getSingleProduct(this.selectedProductId).subscribe((data) => {
this.product = data;
this.productLoaded = true;
});
this.getReviews();
}
getReviews() {
this.productService.getProductReviews(this.selectedProductId).subscribe((data) => {
this.reviews = data;
});
}
initializeForm() {
this.reviewFormGroup = this.fb.group({
username: ['', Validators.required],
summary: ['', Validators.required],
review: ['', Validators.required],
productId: [this.selectedProductId]
});
}
submitReview() {
let body: ProductReview = this.reviewFormGroup.value;
this.productService.postReview(body);
this.initializeForm();
}
rateProduct(val) {
this.productService.postRating({
productId: this.selectedProductId,
ratingValue: val
});
this.productService.getProductRating(this.selectedProductId).subscribe((retVal) => {
const ratings = retVal.map(v => v.ratingValue);
let avRating = (ratings.length ? ratings.reduce((total, val) => total + val) / retVal.length : 0);
this.productService.setProductRating(this.selectedProductId,avRating.toFixed(1));
});
}
}
All the methods we have in the Single component is self explanatory. Let see what we have in the single component html.
Feel free to download the source code and play around with it. Your comments are welcome.{{ product?.name}}
{{ product?.description}}{{ product?.price | currency:'USD':true}}Customer Reviews
{{review?.summary}} {{review?.username}}{{review?.review}}
Post a Comment
0Comments