Home > Software design >  I want to pass a an object to a child component. I did everything and nothing works. (ANGULAR)
I want to pass a an object to a child component. I did everything and nothing works. (ANGULAR)

Time:06-16

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 }}&nbsp;</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 }}&nbsp;</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);
  • Related