91

I have a data driven Angular application. I have a toggle component which I pass in a toggled state. My issue is that the two way data binding does not seem to work unless i pass in the toggle boolean as an object. Is there a way to get this to work without using an EventEmitter or passing the variable in as an object. This is to be a reusable component and the application is heavily data driven so passing the value in as an object in not an option. My code is....

toggle.html

<input type="checkbox" [(ngModel)]="toggled" [id]="toggleId" name="check"/>

toggle.component.ts

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

@Component({
  moduleId: module.id,
  selector: 'toggle-switch',
  templateUrl: 'toggle-switch.component.html',
  styleUrls: ['toggle-switch.component.css']
})

export class ToggleSwitchComponent {

  @Input() toggleId: string;
  @Input() toggled: boolean;

}

parent.component.html

<toggle-switch toggleId="toggle-1" [(toggled)]="nongenericObject.toggled"></toggle-switch>
Steve Fitzsimons
  • 3,754
  • 7
  • 27
  • 66
  • 5
    I'd like to stress the importance of mitch's comment on this. To make two-way-binding with a parent component's var work, the `@Output` decorated EventEmitter has to be named as the corresponding `@Input` with a **Change** suffix at the end as in: @Input() toggled: boolean; @Output() toggledChange: EventEmitter = new EventEmitter(); – Andreas May 28 '18 at 18:31

2 Answers2

160

For [(toggled)]="..." to work you need

  @Input() toggled: boolean;
  @Output() toggledChange: EventEmitter<boolean> = new EventEmitter<boolean>();

  changeValue() {
    this.toggled = !(this.toggled); 
    this.toggledChange.emit(this.toggled);
  }

See also Two-way binding

[UPDATE] - 25 June 2019
From @Mitch's comment below:
It's worth noting that the @Output name must be the same as the @Input name, but with Change on the end. You can't call it onToggle or something. It's a syntax convention.

Wei-jye
  • 412
  • 1
  • 6
  • 23
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • He even imported `EventEmitter`. It might not as bad as you expect ;-) – Günter Zöchbauer Feb 02 '17 at 16:02
  • This actually worked for me. I had originally had it set up to use the event emitter which was linked to a function in the parent component. That wasnt very reusable hence why i asked the question to see if there was a way to do this. This answer for some reason changes the parent toggled variable – Steve Fitzsimons Feb 02 '17 at 16:09
  • Isn't that the intention? I assume `nongenericObject.toggled` is "the parent toggled variable"? – Günter Zöchbauer Feb 02 '17 at 16:12
  • Yes it is the parent value. Thankyou it worked. I've spent half a day trying to get this to work and you've sorted it in seconds. – Steve Fitzsimons Feb 02 '17 at 16:15
  • 16
    @silentsod It's nonetheless rather annoying that Angular doesn't have a built-in way to handle 2-way binding on one attribute. Seems like a super basic feature; yet, it's simply not supported. Also, the Angular docs are incredibly convoluted and difficult to comprehend, so I wouldn't be surprised if OP didn't understand how to do this even after reading them. – user428517 Jun 30 '17 at 22:06
  • 29
    It's worth noting that the `@Output` name must be the same as the `@Input` name, but with `Change` on the end. You can't call it `onToggle` or something. It's a syntax convention. – Mitch Sep 27 '17 at 01:24
  • @Mitch: Wow! This has frustrated me for weeks! Is there anywhere in the documentation that describes this convention? There surely must be... I just must have missed it. – ProxyTech Jul 10 '20 at 16:22
  • 2
    @ProxyTech it is documented here: https://angular.io/guide/two-way-binding: `The [()] syntax is easy to demonstrate when the element has a settable property called x and a corresponding event named xChange`. However I agree that this should be emphasized. – noamyg Nov 10 '20 at 10:29
  • The docs have improved a LOT since back then in the good old times :D – Günter Zöchbauer Nov 10 '20 at 11:06
13

Although the question has more than 2 years old I want to contribute my 5 cents...

It isn't a problem about Angular, its about how Javascript works... Simple variables (number, string, boolean, etc) are passed by value whereas complex ones (objects, arrays) are passed by reference:

You can read more about this in Kyle Simpson´s series You dont know js:

https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch2.md#value-vs-reference

So, you can use an @Input() object variable to share scope between components without need to use emitters, observers and whatever similar.

// In toggle component you define your Input as an config object
@Input() vm: Object = {};

// In the Component that uses toggle componet you pass an object where you define all needed needed variables as properties from that object:
config: Object = {
    model: 'whateverValue',
    id: 'whateverId'
};

<input type="checkbox" [vm]="config" name="check"/>

This way you can modify all object properties and you get same value in both components because they share same reference.

Lorraine
  • 441
  • 5
  • 23
  • 1
    This can both be a blessing and a nightmare. I've had a real bad time once because the reference was being passed and I didn't know and a bug ticket was created. I couldn't understand why it was happening, even creating new objects and so on. But now, I just needed a simple two way binding and it works perfectly – Guilherme Taffarel Bergamin Apr 09 '21 at 15:09
  • 1
    Link is broken - looks like they're partway through a new edition but the original is still there: https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/types%20%26%20grammar/ch2.md#value-vs-reference – Ed Norris Dec 11 '21 at 18:27