0

I believe the title is descriptive. The thing is:

I've got a parent component (shows the result), child1 (has checkboxes) and child2 nested in child1 (has input-text and buttons).

When I set the input in child2 via keyboard everything is fine, but when I use the buttons to change the value, its parent (child1 in this case) doesn't see it.

A bit of code here:


Parent.ts:

import { ProductService } from './../../services/product.service';
import { Product } from './../../interfaces/product';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  public products: Product[];
  public basePrice: number;  // representa los precios de los componentes de Home Component
  public panelPrice!: number;  // representa los precios de los componentes de WebPanel Component
  public totalPrice: number;  // representa la suma de base+panel
  public isChecked: boolean;

  constructor(private productService: ProductService) {
    this.products = [];
    this.totalPrice = 0;
    this.basePrice = 0;
    this.isChecked = false;
   }

  ngOnInit(): void {
    this.getProducts();
  }

  getProducts(): void{
    this.productService.getProducts()
    .subscribe(p => this.products = p);
  }

  getPrice(p: Product){
    p.isChecked ? this.basePrice+=p.price : this.basePrice-=p.price;
    this.totalPrice = this.basePrice;
  }

  getName(p: Product){
    return p.name;
  }

  getPanelPrice(evt: string){
    this.panelPrice = Number(evt);
    this.panelPrice === 30 ? this.panelPrice = 0 : this.panelPrice;
    //condicional para controlar si el user introduce 1 página + 1 idioma: estaría dentro del precio base, con lo cual no hay que multiplicar nPages * nLang * 30
  }

  processTotalPrice(){
    this.totalPrice = this.basePrice + Number(this.panelPrice);
    // console.log('PanelPrice: ' +this.panelPrice);
  }
}

Parent.html:

<h2>¿Qué quieres hacer?</h2>
<div class="container-check">
    <div *ngFor="let p of products">
        <app-my-checkbox [product]="p" (change)="getPrice(p)"></app-my-checkbox>
        <div *ngIf="p.isChecked && p.name === 'web'">
            <app-web-panel (emitterPanel)="getPanelPrice($event)" (change)="processTotalPrice();"></app-web-panel>
        </div>
    </div>
</div>
<p>Precio: {{ totalPrice | currency}} </p>

Child1.ts:

import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';

@Component({
  selector: 'app-web-panel',
  templateUrl: './web-panel.component.html',
  styleUrls: ['./web-panel.component.scss']
})
export class WebPanelComponent implements OnInit {

  @Input() public inputLabel!: string;
  @Output() emitterPanel: EventEmitter<string> = new EventEmitter();

  public nPages = 1;
  public nLang = 1;

  public label = {
    pages: "Número de páginas",
    lang: "Número de idiomas",
  }

  constructor() {
  }

  ngOnInit(): void {
  }

  getNPages(evt: string){
    this.nPages = Number(evt);
    console.log("Panel:// NPages: " + this.nPages)
  }
  getNLanguages(evt: string){
    this.nLang = Number(evt);
    console.log("Panel:// NLang: " + this.nLang)
  }

  calcPanelPrice(): string {
    let result = this.nPages * this.nLang * 30;
    return String(result);
  }

  emitOnChanges() {
    console.log("Panel emitter:// Result: " + this.calcPanelPrice());
    this.emitterPanel.emit(this.calcPanelPrice());
  }
}

Child1.html:

<div class="panel">
    <app-my-input-w-buttons [inputLabel]="label.pages" (emitterInputWButtons)="getNPages($event)" (change)="emitOnChanges()"></app-my-input-w-buttons>
    <app-my-input-w-buttons [inputLabel]="label.lang" (emitterInputWButtons)="getNLanguages($event)" (change)="emitOnChanges()"></app-my-input-w-buttons>
</div>

Child2.ts:

import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-my-input-w-buttons',
  templateUrl: './my-input-w-buttons.component.html',
  styleUrls: ['./my-input-w-buttons.component.scss']
})
export class MyInputWButtonsComponent implements OnInit {

  @Input() public inputLabel: any;
  @Output() emitterInputWButtons = new EventEmitter<string>();
  public value:number = 1;

  constructor() { }

  ngOnInit(): void {
  }

  emitValue(){
    this.emitterInputWButtons.emit(String(this.value));
  }

  increaseValue(){
    this.value++;
    this.emitValue();
  }

  decreaseValue(){
    this.value === 1 ? this.value : this.value--;
    this.emitValue();
  }
}

Child2.html:

<div class="input-w-buttons">
    <label for="input">{{inputLabel}}</label>
    <button (click)="decreaseValue()">-</button>
    <input [(ngModel)]="value" type="text" name="input" value="{{value}}" (change)="emitValue()" />
    <button (click)="increaseValue()">+</button>
</div>
Peskao909
  • 1
  • 1

1 Answers1

0

Ok you where right about my previous answer. Here another one :

In

enter code here`<input [(ngModel)]="value" type="text" name="input" value="{{value}}" (change)="emitValue()" />

(change) is triggered when the user change the value, not when it changes grammatically

So I would replace (change) by (ngModelChange), so a change is triggered when value changes from elsewhere :

<input [(ngModel)]="value" type="text" name="input" value="{{value}}" (ngModelChange)="emitValue()" />

Here is my opinion about (change) vs (ngModelChange) : (change) vs (ngModelChange) in angular

Julien
  • 2,616
  • 1
  • 30
  • 43
  • Is what I thought in the beginning, instead of trigger the event when the input changes, trigger it when the attribute changes, but..... it isn't working either. I am logging everywhere and I can see that 'child2' (which has the input and buttons) is sending the data to its parent 'child1'. Then, I suppose I'm missing something at 'child1'. Or maybe I should try your answer but using ngForm, so the whole model triggers the event. I will tell you in a while. Thanks anyway :) – Peskao909 Nov 10 '21 at 11:10