[Fixed] Pass value from input component to button component in Angular application

Issue

I’m trying to figure how to pass value from one Angular component to another my-input.component and my-button.component:

I’m trying to receive from my-input.component.ts and display @Input('name') with button (click)="displayReceivedValue()" from my-button.component.html in my-button.component.ts:

import { Component, Input, OnInit } from '@angular/core';
import { MyInput } from '../../interfaces/my-input';

@Component({
  selector: 'app-my-button',
  templateUrl: './my-button.component.html',
  styleUrls: ['./my-button.component.scss'],
})
export class MyButtonComponent implements OnInit {
  @Input('name')
  public input!: MyInput;
  constructor() {}

  ngOnInit(): void {}

  async displayReceivedValue() {
    console.log('Received value: ', this.input);
  }
}

which is <app-my-button name=""></app-my-button> component in Login page login.component.html from my-input.component.ts with this.myValue from [(ngModel)]="myValue" in input of my-input.component.html, but I’m not sure, how to pass it to name="":

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

@Component({
  selector: 'app-my-input',
  templateUrl: './my-input.component.html',
  styleUrls: ['./my-input.component.scss'],
})
export class MyInputComponent implements OnInit {
  
  public myValue: any;

  constructor() {}

  ngOnInit(): void {}
}

Also I’ve tried use @Output() with <app-my-button (inputValue)="acceptData($event)"></app-my-button> component in Login page from my-input.component.ts:

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
    
    @Component({
      selector: 'app-my-input',
      templateUrl: './my-input.component.html',
      styleUrls: ['./my-input.component.scss'],
    })
    export class MyInputComponent implements OnInit {
      @Output() inputValue= new EventEmitter<string>();
        
      public myValue: any;

      constructor() {}
    
      ngOnInit(): void {
           this.inputValue.emit(this.inputValue);
      }
    }

in my-button.component.ts:

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

@Component({
  selector: 'app-my-button',
  templateUrl: './my-button.component.html',
  styleUrls: ['./my-button.component.scss'],
})
export class MyButtonComponent implements OnInit {
  constructor() {}
  ngOnInit(): void {}
  acceptData(data: any) {
    console.log(data);
  }
}

I got nothing if I add (inputValue)="acceptData($event) to my-button.component.html, or if I add (inputValue)="acceptData($event) to login.component.html I got error:

TS2339: Property ‘acceptData’ does not exist on type
‘LoginComponent’.

Solution

Option 1: Using ControlValueAccessor

Try to make it simple; Lets say you have below in your app component

app.component.html

<app-my-input [(ngModel)]='myValue'></app-my-input>
<app-my-button [name]='myValue'></app-my-button>

app.component.ts

 myValue = 'MyValue'

Basically we are binding to the same value in the app.component.ts. So the value will be updated to the button name property when it changes.

Now if you try to run above you will receive an error

No value accessor for control with the name app-my-input

That is because we are binding to the ngModel that requires to implement ControlValueAccessor. So we implement this like below

my-input.component.ts

@Component({
  selector: 'app-my-input',
  templateUrl: './my-input.component.html',
  styleUrls: ['./my-input.component.css'],
    providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MyInputComponent),
      multi: true
    },
    ]
})
export class MyInputComponent implements ControlValueAccessor {
  
  myValue = '';
  constructor() { }
  writeValue(value: any): void {
    if (value !== undefined) {
      this.myValue = value;
    }
  }
  onChanges: ($value: any) => void;
  onTouched: () => void;

  registerOnChange(fn: any): void {
    this.onChanges = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}

my-input.component.ts

<input [(ngModel)]='myValue' (ngModelChange)='onChanges(myValue)'>

Now our app-my-input works just like a normal input and we can bind to the [(ngModel)] directive

So now in our app-my-button we can receive this using @Input decorator

app-my-button.ts

  @Input() name = "";
  constructor() {}

  ngOnInit(): void {}

  displayReceivedValue() {
    console.log("Received value: ", this.name);
  }

app-my-button.html

<button (click)='displayReceivedValue()'>My button</button>

Now if you click on the button you will see a log in the console with the value of the input

Demo Here

Option 2: Using @Output and @input decorators

To use @input and @Output you would simply define you are input with something like

export class MyInputComponent implements OnInit {
  
  @Input() input = '';
  @Output() output: EventEmitter<string> = new EventEmitter()
  myValue = '';
  constructor() { }
  onChanges = ($event) => {
    this.output.emit($event)
  }

  ngOnInit() {
    this.myValue = this.input
  }
}

and in the html

<input [(ngModel)]='myValue' (ngModelChange)='onChanges(myValue)'>

Now we bind to (ngModelChange). When the value changes, we emit the value using the output property defined as an EventEmitter<string>

We can then receive this with

<app-my-input [input]='myValue' (output)="myValue = $event"></app-my-input>

See this Demo

Leave a Reply

(*) Required, Your email will not be published