Reputation: 83
I am developing an online store for a client using Angular/Spring Boot. To keep things simple and to the point, I am storing the users shopping cart data in localStorage
which is working fine. However, for the shopping cart to be updated with the users products, I have to refresh the page. I would like the app to just update without having to refresh the page. Im sure this is probably very trivial, but I cant get it to work!
I have tried looking at a few different sources :
I have tried using the this.router.routeReuseStrategy.shouldReuseRoute = function () { return false; };
within the components constructor but again didnt have any luck.
The main bits of code are as follows:
CartService - On construction we fetch the data from localStorage:
constructor(private http: HttpServiceService) {
this.getCartDetailsByUser();
}
getCartDetailsByUser() {
let data = JSON.parse(localStorage.getItem("products"));
this.cartObj = data;
if (data !== null) {
this.cartQty = data.length;
console.log(data);
}
}
When the user clicks "Add to Cart", this function is called, which adds the data to the local storage, but i need to update the DOM with whats in the cart and the cart length!
addCart(product) {
let products = [];
console.log(product);
if (localStorage.getItem("products")) {
products = JSON.parse(localStorage.getItem("products"));
}
products.push({
productId: product.productId,
image: product.image,
price: product.price,
});
localStorage.setItem("products", JSON.stringify(products));
}
I dont want to manually refresh the whole page for something so simple, I would just like the DOM to update with no need to refresh.
The header.component.html is what displays the shopping cart with its quantity etc
<div class="cart cart box_1 checkout-count-wrap">
<form action="#" method="post" class="last">
<button
class="w3view-cart"
type="submit"
(click)="openCheckoutModel()"
name="submit"
value=""
>
<p class="total_count_checkout">{{cart_qty}}</p>
<i class="fa fa-cart-arrow-down" aria-hidden="true"></i>
</button>
</form>
</div>
With the linked .ts class (header.component.ts). In the constructor, I get the qty which gets passed to the html above.
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {
this.cartService.cartServiceEvent.subscribe((data) => {
this.cart_qty = this.cartService.getQty();
});
}
When the user clicks on the shopping cart on the DOM, the following code is executed which gives a popup with the products etc in the cart:
openCheckoutModel() {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
this.mainDialogType = "checkout";
}
Which in turn then displays the data in the shopping cart.
ALL of this works correctly apart from the cart not updating on the fly!!
Any help would be greatly appreciated!! :D
**** EDITED ANSWER ****
OK, so from the answer on the question, I have made a few small changes with the Observable
pattern.. This half works - It updates the quantity of the cart on the fly (incrementing the number + 1 when a user clicks 'Add to Cart'). It also stores the product in the json object in localStorage
as it did before. However, the actual items in the cart now do not show on the DOM, where as they did before. The data definitely exists with the correct products added being stored in localStorage
, but now there seems to be some issues with the DOM displaying what's in it (productName
, price etc)
Will this be due to another Observable being needed to track the item data in the cart? This may become clearer when I share my code (full classes)..
So here is the checkout-component.ts with the method being highlighted with *****
import { Component, OnInit } from "@angular/core";
import { CartServiceService } from "../service/cart-service.service";
import { HttpServiceService } from "../http-service.service";
import { Router } from "@angular/router";
@Component({
selector: "app-checkout",
templateUrl: "./checkout.component.html",
styleUrls: ["./checkout.component.css"],
})
export class CheckoutComponent implements OnInit {
cartObj = [];
cartTotalPrice: any;
pay_type = "cash_on_delivery";
delivery_address = "";
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {}
ngOnInit() {
this.getCartDetailsByUser();
//below function will be triggerd from when removing and qty is changing..
this.cartService.cartServiceEvent.subscribe((data) => {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
});
}
qtyChange(qty, cartObj) {
var request = {
cartId: cartObj.id,
quantity: qty,
price: cartObj.price * qty,
};
this.http
.postRequestWithToken("api/addtocart/updateQtyForCart", request)
.subscribe(
(data: any) => {
this.cartService.getCartDetailsByUser(); //for updating in the application..
},
(error) => {
alert("Error while fetching the cart Details");
}
);
}
getCartDetailsByUser() {
let data = JSON.parse(localStorage.getItem("products"));
this.cartObj = data;
this.cartTotalPrice = this.getTotalAmounOfTheCart();
console.log("Cart Obj", this.cartObj);
console.log("Total", this.cartTotalPrice);
}
// getCartDetailsByUser(){
// this.http.postRequestWithToken("api/addtocart/getCartsByUserId",{}).subscribe((data:any)=>{
// this.cartObj = data;
// this.cartTotalPrice = this.getTotalAmounOfTheCart();
// },error=>{
// alert("Error while fetching the cart Details");
// })
// }
getTotalAmounOfTheCart() {
let obj = this.cartObj;
let totalPrice = 0;
for (var o in obj) {
totalPrice = totalPrice + parseFloat(obj[o].price);
}
return totalPrice.toFixed(2);
}
removeCartById(cartObj) {
if (confirm("Are you sure want to delete..?")) {
let id = cartObj.id;
this.cartService.removeCart(id);
}
}
checkoutCart() {
if (this.delivery_address == "") {
alert("Delivery address should not be empty");
return;
}
if (this.pay_type == "cash_on_delivery") {
let request = {
total_price: this.cartTotalPrice,
pay_type: "COD",
deliveryAddress: this.delivery_address,
};
this.http
.postRequestWithToken("api/order/checkout_order", request)
.subscribe(
(data: any) => {
alert("checkout process completed.Your Order is processed..");
this.cartService.getCartDetailsByUser();
this.router.navigate([""]);
},
(error) => {
alert("Error while fetching the cart Details");
}
);
} else {
alert("Payment Integration is not yet completed.");
}
}
}
Then the corresponding checkout-component.html
<div style="display: block;" id="w3lssbmincart">
<ul>
<li *ngFor="let cart of cartObj" class="sbmincart-item sbmincart-item-changed">
<div class="sbmincart-details-name">
<a class="sbmincart-name">{{cart.name}}</a>
</div>
<div class="sbmincart-details-quantity">
<select [(ngModel)]="cart.qty" (change)="qtyChange($event.target.value,cart)">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
</select>
</div>
<div class="sbmincart-details-remove">
<button (click)="removeCartById(cart)" type="button" class="sbmincart-remove" data-sbmincart-idx="0">×</button>
</div>
<div class="sbmincart-details-price">
<span class="sbmincart-price">{{cart.price}}</span>
</div>
</li>
</ul>
<div class="sbmincart-footer">
<div class="sbmincart-subtotal radio-wrap">
<span><input [(ngModel)]="pay_type" value="cash_on_delivery" type="radio" name="pay_type" /><span class="radio_text">Cash on Delivery</span></span>
<span><input [(ngModel)]="pay_type" value="online" name="pay_type" type="radio"/><span class="radio_text">Online</span></span>
</div>
<div class="sbmincart-subtotal">
<textarea placeholder="Enter the Delivery address" [(ngModel)]="delivery_address"></textarea>
</div>
<div class="sbmincart-subtotal">
Subtotal: <span class="price">${{cartTotalPrice}}</span>
</div>
<div>
<button (click)="checkoutCart()">Place Order</button>
</div>
</div>
<input type="hidden" name="bn" value="sbmincart_AddToCart_WPS_US">
</div>
</div>
</div>
</div>
Which simply loops over the cartObj stored within the service class.
I modified the header-component.ts (where the cart exists) to look like this:
import { Component, OnInit } from "@angular/core";
import { HttpServiceService } from "../http-service.service";
import { CartServiceService } from "../service/cart-service.service";
import { timingSafeEqual } from "crypto";
import { Router } from "@angular/router";
@Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.css"],
})
export class HeaderComponent implements OnInit {
isOpenLoginDialog = false;
currentDropDownMenu = "";
dialogType = "login";
mainDialogType = "";
isLogin = false;
mobile = "123456789";
password = "test";
cartObj = [];
cart_qty = 0;
cartTotalPrice = 0;
register = { name: "", email: "", mobile: "", password: "", re_password: "" };
welcomeUsername = "";
items$ = this.cartService.items$;
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {
let request = {};
this.http.postRequest("api/status", request).subscribe(
(data) => {
console.log("test", data);
},
(error) => {
alert("Server connection error " + error);
}
);
this.cartService.cartServiceEvent.subscribe((data) => {
this.cart_qty = this.cartService.getQty();
this.cartObj = this.cartService.getCartOBj();
});
}
logout() {
this.http.logout();
this.isLogin = false;
}
ngOnInit() {}
checkout_btn() {
this.router.navigate(["checkout"]);
}
openCheckoutModel() {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
this.mainDialogType = "checkout";
}
openDialog() {
this.mainDialogType = "login";
}
dialogTypeInside(type) {
if (this.dialogType != type) this.dialogType = type;
}
closeDialog() {
this.mainDialogType = "";
}
curentDropDown(currentDropdownMenuName) {
if (this.currentDropDownMenu == currentDropdownMenuName) {
this.currentDropDownMenu = "";
} else {
this.currentDropDownMenu = currentDropdownMenuName;
}
}
}
Notice the openCheckoutModel()
method which gets called which should pass the data down into the html component.
The data definitely exists as I am printing it out via console.log
in the service class. The screen shot is below:
The front end DOM also shows there are 4 items in the cart:
However, the html does not display the actual data on the cart page:
From what I can see, i am looping over the data that definitely exists, however it does not show on the browser. I also get no errors :(
I hope this makes sense!
Upvotes: 1
Views: 7052
Reputation: 583
For peace of mind and for performance reasons, I would recommend you to go the RxJS way and turn things observable.
Short answer:
Here is a stackblitz example I could quickly make for you: https://stackblitz.com/edit/angular-ivy-kgpicq
Long Answer:
I would recommend you structure your class as:
class CartService {
constructor() {
let cartItems = JSON.parse(localstorage.getItem('products'));
if (!cartItems) {
cartItems = []
}
this.itemsSubject.next(cartItems);
}
private itemsSubject = new BehaviorSubject<Product[]>([]);
items$ = itemsSubject.asObservable();
addToCart(item: Product) {
this.items$.pipe(
take(1),
map((products) => {
products.push(item);
localstorage.setItem('products', JSON.stringify(products));
},
).subscribe();
}
}
In your component class:
class ProductsPageComponent {
constructor(private cartService: CartService) {}
items$ = this.cartService.items$;
}
In your template:
<div class="cart cart box_1 checkout-count-wrap">
<form action="#" method="post" class="last">
<button class="w3view-cart" type="submit" (click)="openCheckoutModel()" name="submit" value="">
<p class="total_count_checkout">{{(items$ | async).length}}</p>
<i class="fa fa-cart-arrow-down" aria-hidden="true"></i>
</button>
</form>
</div>
Upvotes: 3
Reputation: 345
You can achieve this with ajax in plain javascript or with jQuery. My preference would go to jQuery.
Some code:
$.get( "/your_link", function( data ) {
$( ".result" ).html( data );
});
I would say you could write a another function in your controller that responds with the cart items. Then you could call the $.get whenever you would like to refresh your items. There's a lot of info on this so don't hesitate to look it up :)
https://api.jquery.com/jQuery.get/
Another solution might be to put a timeout on your subscribe. (this is some code from a project of mine which needed similar functionality to yours.)
In component.ts:
getUsers(): void {
// polling
timer(0, 2500)
.subscribe(() => {
this.userService.getUsers()
.subscribe(data => this.users = data);
});
}
In service.ts:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.usersUrl);
}
Upvotes: 0