Search code examples
angularparent-childngforangular-arrays

Pass the right element's data from ngfor loop to the child component


Im looking to inject the data from a ngfor loop on the parent component into a child component. This child component is supposed to render a modal with the data of the right element in the parent's ngfor loop, when I click on the respective child component template's render on the parent component template (code below shows the html tag of the child's template on the parent's template).

So far the @input() saleItem: SaleItem;property on the child component's modle only takes in data of the first element in the array on the parent component's template, no matter which 'sale item' I click on the parent component's template. How can I achieve the desired behaviour of each modal render containing the data of the right element in the ngfor Loop?

Already tried Using on changes on the child component. This is not working and I assume because the reference of the array in the ngFor loop doesn't actually change.

EDIT

So i did a little digging in the dev tools and this is a snap shot from the Elements tab in chrome. Elements tab snap This shows that the data is actually being passed down into the separately rendered child component's template and showing on the modals, but when i click anywhere on the parent components template, only the modal with the first elements data gets triggered. How do I get the right modal to fire when I click on the dom element on the parent components template that is supposed to fire the modal as shown in the code supplied? Parent component model

import { SaleItemsServices } from './../Services/saleItem-service.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sales-page',
  templateUrl: './sales-page.component.html',
  styleUrls: ['./sales-page.component.scss']
})
export class SalesPageComponent implements OnInit {
  // other inits
  saleItems: any = [];

  imageUrl: String = this.saleItemsService.getItemImageURI();
  constructor(private saleItemsService: SaleItemsServices) {}

  ngOnInit() {
    console.log('im here');
    this.populateSaleItems();
  }
  populateSaleItems() {
    this.saleItemsService.getSalesItems().subscribe(
      data => {
        console.log('data available');
        this.saleItems = data;
        console.log(this.saleItems);
      },
      err => {
        console.log('there was an error fetching the data');
      },
      () => {
        console.log('done loading sale item data');
      }
    );
    console.log('sale items received are as such' + this.saleItems);
  }
}

Parent component template

<div id="backGroundImage">
  <div id="salesPageContainer">
    <div id="saleItemsContainer">
      <p id="banner">This is where the banner will come</p>
      <div
        *ngFor="let saleItem of saleItems"
        id="itemContainer"
        data-toggle="modal"
        data-target="#modalID"
      >
        <app-saleitem-modal [saleItem]="saleItem"></app-saleitem-modal>
        <img [src]="imageUrl + saleItem.imageID" alt="" id="itemImage" />
        <p id="itemName">{{ saleItem.itemName }}</p>
        <p id="itemPrice">{{ saleItem.itemPrice }}</p>
      </div>
    </div>
  </div>
</div>

Child component model

import { SaleItem } from './../../models/saleitem';
import {
  Component,
  OnInit,
  Input,
  OnChanges,
  SimpleChanges
} from '@angular/core';
import { SaleItemsServices } from '../../Services/saleItem-service.service';

@Component({
  selector: 'app-saleitem-modal',
  templateUrl: './saleitem-modal.component.html',
  styleUrls: ['./saleitem-modal.component.scss']
})
export class SaleitemModalComponent implements OnInit, OnChanges {
  @Input() saleItem: SaleItem;
  imageUrl: String = this.saleItemsService.getItemImageURI();

  constructor(private saleItemsService: SaleItemsServices) {}
  ngOnChanges(changes: SimpleChanges) {
    if (changes.saleItem) {
      console.log(
        'the previous value of sale items is' +
          JSON.stringify(changes.saleItem.previousValue)
      );
      console.log(
        'the new value of sale items is ' +
          JSON.stringify(changes.saleItem.currentValue)
      );
    }
  }
  ngOnInit() {
    console.log(this.saleItem);
  }
}

Child component template

<div class="modal fade" id="modalID">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h3 id="saleItemName">{{ saleItem.itemName }}</h3>
      </div>
      <div class="modal-body">
        <img
          [src]="imageUrl + saleItem.imageID"
          alt=""
          id="saleItemModalImage"
        />
        <p id="itemWeight">{{ saleItem.itemWeight }}</p>
        <p id="itemPrice">{{ saleItem.itemPrice }}</p>
        <p id="itemCompany">{{ saleItem.itemCompany }}</p>
        <p id="itemCategory">{{ saleItem.itemCategory }}</p>
        <!-- Body here -->
        <div class="modal-footer">
          <div class="col-6">
            <!-- Buttons here -->
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Solution

  • So I added a function that takes each sale item and assigns it manually to the child components sale item variable, when the image of a particular sale item element is clicked on the parent components template.

    parent component template

    
      <div id="salesPageContainer">
        <div id="saleItemsContainer">
          <p id="banner">This is where the banner will come</p>
          <div *ngFor="let saleItem of saleItems" id="itemContainer">
            <img
              [src]="imageUrl + saleItem.imageID"
              alt=""
              id="itemImage"
              (click)="passSaleItem(saleItem)"
              data-toggle="modal"
              data-target="#modalID"
            />
            <app-saleitem-modal [saleItem]="saleItemToPass"></app-saleitem-modal>
            <p id="itemName">{{ saleItem.itemName }}</p>
            <p id="itemPrice">{{ saleItem.itemPrice }}</p>
          </div>
        </div>
      </div>
    </div>
    

    parent components model addition

    passSaleItem(saleItem: SaleItem) {
        this.saleitemModal.saleItem = saleItem;
      }
    

    The modals still get rendered each time anew for each component which is a bit of a drag. Ideally I would have liked for the Modal to be rendered once and the data pushed to it for each item when the image was clicked. This gave cannot read property of undefinederror as the @Input() saleItem on the child component model was not getting initialized with any values when angular initialized the components.

    Hence I'm currently okay with the solution I have found, albeit with a potential question mark on performance down the line when the application scales.