Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two-Way data binding in *ngFor

I have created a component that is meant to be a switch. You would use it much the same as you would a checkbox. This is a stripped down version.

my-switch.component.ts:

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

@Component({
    selector: 'my-switch',
    template:    `<a (click)='toggle()'>
                    <span *ngIf='value'>{{onText}}</span>
                    <span *ngIf='!value'>{{offText}}</span>
                  </a>`
})
export class MySwitchComponent {
    @Input() onText: string = 'On';
    @Input() offText: string = 'Off';
    @Input() value: boolean;

    @Output() change = new EventEmitter <boolean> ();

    position: string;

    toggle() {
        this.value = !this.value;
        this.change.emit(this.value);
    }
}

My plan is to use it like this:

parent-component.ts

import {Component} from '@angular/core';
import {MySwitchComponent} from 'my-switch.component';

@Component({
    selector: 'my-sites',
    directives: [MySwitchComponent]
    template: `<table>
                 <tr *ngFor='let item of items'>
                   <td>
                     <my-switch 
                       [(value)]='item.options.option1'
                       (change)='logItem(item)'>
                     </my-switch>
                   </td>
                 </tr>
               </table>`
})
export class MySitesComponent {
    items: Object[] = [
        {options: { option1: false }}
    ];

    logItem(item) {
        console.log(item)
    }   
}

Again, this is simplified, but I think illustrates what I expect. My expectation is that when the switch is clicked, the view updates from "Off" to "On" and the value of the option is logged. The problem is that the value that is logged looks like this:

{options: {option1: false}}

My belief is that the items being iterated over are read-only. I know that I can work around this issue, but I would like to know if what I am trying to do is possible, or ill-advised, and also why it doesn't work.

like image 914
trey-jones Avatar asked Feb 07 '23 21:02

trey-jones


2 Answers

Angular "de-sugars" the [(x)] syntax into an x input property for property binding and an xChange output property for event binding. -- reference

Therefore, if you name your input property value, you must name your output property valueChange:

@Output() valueChange = new EventEmitter <boolean> ();

That's the only piece of the puzzle you were missing. You now have two-way databinding between a parent and a child component.

If you want to execute some logic when the child changes/emit()s the value, catch the (valueChange) event in the parent component:

(valueChange)='logItem(item)'>

Plunker


I also suggest

console.log(JSON.stringify(item))
like image 72
Mark Rajcok Avatar answered Feb 13 '23 07:02

Mark Rajcok


In parent child scenario you can take advantage of two-way binding with xxxChange Output property as shown below, ,
In Parent - [(xxx)]="someValue"
In Child - @Input xxx: boolean;
@Output() xxxChange = new EventEmitter <boolean> ();

Note that xxxChange property. which is missing in your case

Now, Look here for the code- Plunker

<td> From Parent - {{item.options.option1}}   <--------------------value will keep changing
                     <my-switch 
                       [(value)]='item.options.option1'>  <---------two way binding
                     </my-switch>
 </td>

Note here, [()] represents two way binding so in parent you don't need to use (valueChange)="someValue=$event" to catch the change. [(value)]='item.options.option1' will automatically bind you new or changed value to item.options.option1.


export class MySwitchComponent {

    @Input() value: boolean;
    @Output() valueChange = new EventEmitter <boolean> ();  <----xxxChange output property

...
...
}
like image 43
Nikhil Shah Avatar answered Feb 13 '23 05:02

Nikhil Shah