Skip to content

Lolwa Alromi and Zainab Alsaffar #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: Cart-Service-Signals-Refactor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,8 @@
}
}
}
},
"cli": {
"analytics": "55af9ee0-a367-4e98-9026-3c1365cb0ffe"
}
}
1 change: 1 addition & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<app-header></app-header>
<router-outlet></router-outlet>

3 changes: 2 additions & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
import { ToastComponent } from "./toast/toast.component";

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, HeaderComponent],
imports: [RouterOutlet, HeaderComponent, ToastComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h3>{{ product.name }}</h3>
<p class="out-of-stock">Out of Stock</p>
}
<div class="actions">
<button (click)="addToCart()" [disabled]="!product.stock">
<button (click)="addToCart()" [disabled]="!product.stock">
Add to Cart
</button>
<button [routerLink]="['/products', product.id]">View Product</button>
Expand Down
4 changes: 3 additions & 1 deletion src/app/components/product-card/product-card.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core';
import { Product } from '../../../data/products';
import { RouterLink } from '@angular/router';
import { CartService } from '../../services/cart.service';
import { ToastService } from '../../toast.service';

@Component({
selector: 'app-product-card',
Expand All @@ -13,9 +14,10 @@ import { CartService } from '../../services/cart.service';
export class ProductCardComponent {
@Input() product!: Product;

constructor(private cartService: CartService) {}
constructor(private cartService: CartService , private toast: ToastService) {}

addToCart() {
this.cartService.addToCart(this.product);
this.toast.show("Added To Cart","success")
}
}
4 changes: 2 additions & 2 deletions src/app/pages/cart/cart.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="cart">
<h2>Your Shopping Cart</h2>

@if ((cart$ | async)!.length > 0) {
@if ((cart$() )!.length > 0) {
<table class="cart-table">
<thead>
<tr>
Expand All @@ -13,7 +13,7 @@ <h2>Your Shopping Cart</h2>
</tr>
</thead>
<tbody>
@for (item of cart$ | async; track item.id) {
@for (item of cart$(); track item.id) {
<tr>
<td>{{ item.name }}</td>
<td>{{ item.price | currency : "KWD" }}</td>
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/cart/cart.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, signal } from '@angular/core';
import { CartService } from '../../services/cart.service';
import { Product } from '../../../data/products';
import { AsyncPipe, CurrencyPipe } from '@angular/common';
Expand Down
2 changes: 2 additions & 0 deletions src/app/pages/product-list/product-list.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div class="product-list">
@for (product of products; track product.id) {
<app-product-card [product]="product"></app-product-card>

}
</div>
<app-toast></app-toast>
3 changes: 2 additions & 1 deletion src/app/pages/product-list/product-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component } from '@angular/core';
import { PRODUCTS } from '../../../data/products';
import { ProductCardComponent } from '../../components/product-card/product-card.component';
import { ToastComponent } from "../../toast/toast.component";

@Component({
selector: 'app-product-list',
standalone: true,
imports: [ProductCardComponent],
imports: [ProductCardComponent, ToastComponent],
templateUrl: './product-list.component.html',
styleUrl: './product-list.component.css'
})
Expand Down
32 changes: 18 additions & 14 deletions src/app/services/cart.service.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
import { Injectable } from '@angular/core';
import { computed, Injectable, signal } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Product } from '../../data/products';

export type CartItem = Product & { quantity: number };

@Injectable({ providedIn: 'root' })
export class CartService {
private cartSubject = new BehaviorSubject<CartItem[]>([]);
cart$ = this.cartSubject.asObservable();
// private cartSubject = new BehaviorSubject<CartItem[]>([]);
// cart$ = this.cartSubject.asObservable();
private cartSubject = signal<CartItem[]>([]);
cart$ = computed(() => this.cartSubject());

addToCart(product: Product): void {
const currentCart = [...this.cartSubject.value];
const currentCart = [...this.cartSubject()];
const item = currentCart.find(p => p.id === product.id);

if (item && item.quantity < item.stock) {
item.quantity++;
} else if (!item) {
currentCart.push({ ...product, quantity: 1 });
}


this.cartSubject.next(currentCart);
this.cartSubject.set(currentCart);
console.log('added', this.cartSubject())
}

incrementQuantity(productId: number): void {
const updatedCart = this.cartSubject.value.map(item => {
const updatedCart = this.cartSubject().map(item => {
if (item.id === productId && item.quantity < item.stock) {
return { ...item, quantity: item.quantity + 1 };
}
return item;
});

this.cartSubject.next(updatedCart);
this.cartSubject.set(updatedCart);
}

decrementQuantity(productId: number): void {
const updatedCart = this.cartSubject.value
const updatedCart = this.cartSubject()
.map(item => {
if (item.id === productId) {
return { ...item, quantity: item.quantity - 1 };
Expand All @@ -43,27 +47,27 @@ export class CartService {
})
.filter(item => item.quantity > 0);

this.cartSubject.next(updatedCart);
this.cartSubject.set(updatedCart);
}

removeFromCart(productId: number): void {
const updatedCart = this.cartSubject.value.filter(item => item.id !== productId);
this.cartSubject.next(updatedCart);
const updatedCart = this.cartSubject().filter(item => item.id !== productId);
this.cartSubject.set(updatedCart);
}

clearCart(): void {
this.cartSubject.next([]);
this.cartSubject.set([]);
}

getTotal(): number {
return this.cartSubject.value.reduce(
return this.cartSubject().reduce(
(total, item) => total + item.price * item.quantity,
0
);
}

getCartSnapshot(): CartItem[] {
return this.cartSubject.value;
return this.cartSubject();
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/app/toast.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { ToastService } from './toast.service';

describe('ToastService', () => {
let service: ToastService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ToastService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
29 changes: 29 additions & 0 deletions src/app/toast.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { computed, Injectable, signal } from '@angular/core';
export interface Toast{
message : string;
type : "success" | "error"
id: number;

}
@Injectable({
providedIn: 'root'
})
export class ToastService {
private toastsSignal = signal<Toast[]>([])
private nextId = 0;

toasts = computed(() => this.toastsSignal())
show(message:string, type:'success' | 'error'){
const newToast: Toast = {
message,type,id:this.nextId++
}
this.toastsSignal.set([...this.toastsSignal(),newToast])
setTimeout(() => this.remove(newToast.id), 3000)

}
remove(id: number) {
this.toastsSignal.set(this.toastsSignal().filter(toast => toast.id !== id ))
}

constructor() { }
}
40 changes: 40 additions & 0 deletions src/app/toast/toast.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.toast-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 9999;
}

.toast {
padding: 10px 20px;
border-radius: 6px;
color: white;
min-width: 200px;
font-weight: bold;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
animation: fadeIn 0.3s ease-in-out;
}

.toast.success {
background-color: #4caf50;
}
.toast.error {
background-color: #f44336;
}
.toast.info {
background-color: #2196f3;
}

@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
6 changes: 6 additions & 0 deletions src/app/toast/toast.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="toast-container">
<div calss="toast" *ngFor="let toast of toastService.toasts()" [ngClass]= "toast.type">
{{toast.message}}

</div>
</div>
23 changes: 23 additions & 0 deletions src/app/toast/toast.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ToastComponent } from './toast.component';

describe('ToastComponent', () => {
let component: ToastComponent;
let fixture: ComponentFixture<ToastComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ToastComponent]
})
.compileComponents();

fixture = TestBed.createComponent(ToastComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
17 changes: 17 additions & 0 deletions src/app/toast/toast.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { ToastService } from '../toast.service';
import { CommonModule, NgFor } from '@angular/common';

@Component({
selector: 'app-toast',
standalone: true,
imports: [NgFor, CommonModule],
templateUrl: './toast.component.html',
styleUrl: './toast.component.css'
})
export class ToastComponent {
constructor(public toastService: ToastService ){

}

}