This is the parent component: I passed all the data from the parentComponent to main-page Component.
import { Component, OnInit } from '@angular/core';
import { Product } from '../Model/Product';
import { ProductService } from '../ProductsService/product.service';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css'],
})
export class ParentComponent implements OnInit {
products: Product[] = [];
cartList: Product[] = [];
constructor(private productService: ProductService) {
this.products = this.productService.getProducts();
}
ngOnInit(): void {}
addCart(product: Product) {
this.cartList.push(product);
}
}
(TEMPLATE)
<app-main-page [products]="products" (addCart)="addCart($event)"></app-main-page>
<app-cart-list [cartList]="cartList"></app-cart-list>
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ProductService } from '../../ProductsService/product.service';
import { Product } from '../../Model/Product';
@Component({
selector: 'app-main-page',
templateUrl: './main-page.component.html',
styleUrls: ['./main-page.component.css'],
})
export class MainPageComponent {
@Input() products: Product[] = [];
@Output() addCart: EventEmitter<Product> = new EventEmitter();
constructor(private productService: ProductService) {
this.products = this.productService.getProducts();
}
addToCartList(product: Product) {
this.addCart.emit(product);
console.log(product);
}
}
(TEMPLATE) As you can notice that there's a click button in which I emitted this method to the parent so I can pass its value to another child component.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<section>
<div >
<ul *ngFor="let product of products">
<img src="{{ product.img }}" alt="store pictures" />
<li>{{ product.name }}</li>
<li>{{ product.type }}</li>
<li>{{ product.available }}</li>
<li>{{ product.price }}</li>
<button (click)="addToCartList(product)">Add to Cart</button>
</ul>
</div>
</section>
</body>
</html>
import { Component, Input, OnInit } from '@angular/core';
import { Product } from 'src/app/Model/Product';
@Component({
selector: 'app-cart-list',
templateUrl: './cart-list.component.html',
styleUrls: ['./cart-list.component.css'],
})
export class CartListComponent implements OnInit {
constructor() {
console.log(this.cartList);
}
@Input() cartList: Product[] = [];
ngOnInit(): void {}
}
I cannot use any value in cartList, why?
CodePudding user response:
Input variables, event emitters, and RxJS just complicate the problem here. All you need is a simple Angular service.
Here is a stackblitz: https://stackblitz.com/edit/angular-ivy-a6ub1h?file=src/app/app.component.html
Your parent component doesn't need any typescript, all it needs to do is instantiate the other components via html:
Parent Component
<app-main-page></app-main-page>
<app-cart-list></app-cart-list>
I'll make a product service to simulate your app, although I'm not sure exactly what your service looks like. Products will just have a name for simplicity.
Product Service
export type Product = {
name: string;
};
@Injectable({ providedIn: 'root' })
export class ProductService {
getProducts(): Product[] {
return [
{ name: 'product1' },
{ name: 'product2' },
{ name: 'product3' },
{ name: 'product4' },
{ name: 'product5' },
{ name: 'product6' },
{ name: 'product7' },
{ name: 'product8' },
];
}
}
And I'll make a service to hold the cart list items, we'll have add and delete functionality.
Cart Service
@Injectable({ providedIn: 'root' })
export class CartService {
cartList: Product[] = [];
addToCart(product: Product) {
this.cartList.push(product);
}
deleteFromCart(index: number) {
this.cartList.splice(index, 1);
}
}
Main page just gets the products and can add them to the cart.
Main Page
export class MainPageComponent implements OnInit {
products: Product[] = [];
constructor(
private prodService: ProductService,
private cartService: CartService
) {}
ngOnInit() {
this.products = this.prodService.getProducts();
}
addToCart(product: Product) {
this.cartService.addToCart(product);
}
}
<h1>Main Page</h1>
<ng-container *ngFor="let product of products">
<span>{{ product.name }} </span>
<button (click)="addToCart(product)">Add to Cart</button>
<br />
</ng-container>
Cart component shows the cart items and can delete them
Cart List
export class CartListComponent {
constructor(private cartService: CartService) {}
get cartList() {
return this.cartService.cartList;
}
delete(index: number) {
this.cartService.deleteFromCart(index);
}
}
<h1>Cart</h1>
<ng-container *ngFor="let product of cartList; index as i">
<span>{{ product.name }} </span>
<button (click)="delete(i)">Delete</button>
<br />
</ng-container>
CodePudding user response:
The best way to communicate data between components is using a service, it will help a lot with avoiding this type of issue you are facing
I suggest you create a CartService:
@Injectable()
export class CartService {
cart$ = new BehaviorSubject<Product[]>([]);
add(product: Product) {
this.cart$.pipe(take(1)).subscribe(items => {
this.cart$.next([...items, product])
})
}
clear() {
this.cart$.next([])
}
}
From the cart component you then port the cart to its view:
cart$ = this.cartService.cart$
and update your view to handle this for the cart items:
<ul *ngFor="let product of cart$ | async">
<img src="{{ product.img }}" />
<li>{{ product.name }}</li>
<li>{{ product.type }}</li>
<li>{{ product.available }}</li>
<li>{{ product.price }}</li>
</ul>
From the products component you should change your addToCartList
function to:
addToCartList(product) {
this.cartService.add(product)
}
CodePudding user response:
If you're wondering why you're seeing an empty array inside your CartListComponent
console log, that is because the input of a component is set during component initialization and the constructor
runs before that. So if you want to check if the @Input() cartList
is being set properly or not, you should log it out inside OnInit
hook:
export class CartListComponent implements OnInit {
@Input() cartList: Product[] = [];
ngOnInit(): void {
console.log(this.cartList);
}
}
You can use a setter to log it as well
And I think there's a good chance that your component won't re-render properly when you try to add an item to a cart because when it comes to array or object as an @Input
, Angular detect changes by comparing their references and not value. So when you perform this.cartList.push(product)
, you're not changing the array reference but rather its value. To get around this, you can try assigning a brand new array to the variable by copying over the old array
this.cartList.push(product);
this.cartList = [...this.cartList];
A better approach to solve this problem would be to create a CartService
. This would simplify everything by centralizing the logic around cart to a single place rather than trying to create a messy Parent -> Child -> Parent communication.
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
interface Product {}
interface Cart {
items: Product[];
someOtherProperty?: any;
}
@Injectable({
providedIn: 'root',
})
export class CartService {
private _cartSubject: BehaviorSubject<Cart> = new BehaviorSubject<Cart>({
items: [],
});
cart$: Observable<Cart> = this._cartSubject.asObservable();
addToCart(item: Product) {
const oldCart: Cart = this._cartSubject.getValue();
const newCart: Cart = { ...oldCart, items: [...oldCart.items, item] };
this._cartSubject.next(newCart);
}
}
The next step is just to inject and use the service in your component: constructor(private cartService: CartService){}
- To get access the list of cart items:
cartItems$ = this.cartService.cart$.pipe(map((cart) => cart.items));
- To add something to the cart:
this.cartService.add(someProduct);