59

I made a simple UI which consist two components (parent and child).

What the UI does is that when I type some stuff in the input box of the Child component. The value will change using ngModel.

The child component works fine that way.

// Child Component
@Component({
    selector: 'child',
    template: `
        <p>{{sharedVar}}</p>
        <input [(ngModel)]="sharedVar">
    `
})
export class ChildComponent {
    sharedVar: string;
}

Now I have a parent component which I intend to use the same value as Child Component.

I added the Child Component into the Parent template, and use dependency injection to call Child Component's sharedVar.

// Parent Component
@Component({
    selector: 'parent',
    template: `
        <h1>{{sharedVar}}</h1>
        <child></child>
    `,
    directives: [ChildComponent],
    providers: [ChildCompnent]
})
export class ParentComponent {
    sharedVar: string;
    constructor(child: ChildComponent) {
        this.sharedVar = child.sharedVar;
    }
}

The problem is as I'm typing in the input box, the value in <p> changes automatically while the value in parent component's <h1> do not change.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
movila
  • 927
  • 1
  • 8
  • 12
  • 2
    Don't inject components, you pass values to children components using `@Input` https://angular.io/docs/ts/latest/api/core/Input-var.html, the instance created by the template and the instance you create by specifying a provider are two different instances. – Langley Feb 10 '16 at 23:23

3 Answers3

121

We can use the [(x)] syntax in the parent template to achieve two-way databinding with the child. If we create an Output property with the name xChange, Angular will automatically update the parent property. We do need to emit() an event whenever the child changes the value however:

import {Component, EventEmitter, Input, Output} from 'angular2/core'

@Component({
    selector: 'child',
    template: `
        <p>Child sharedVar: {{sharedVar}}</p>
        <input [ngModel]="sharedVar" (ngModelChange)="change($event)">
    `
})
export class ChildComponent {
    @Input() sharedVar: string;
    @Output() sharedVarChange = new EventEmitter();
    change(newValue) {
      console.log('newvalue', newValue)
      this.sharedVar = newValue;
      this.sharedVarChange.emit(newValue);
    }
}

@Component({
    selector: 'parent',
    template: `
        <div>Parent sharedVarParent: {{sharedVarParent}}</div>
        <child [(sharedVar)]="sharedVarParent"></child>
    `,
    directives: [ChildComponent]
})
export class ParentComponent {
    sharedVarParent ='hello';
    constructor() { console.clear(); }
}

Plunker

I used sharedVarParent in the ParentComponent just to demonstrate that the names don't have to be the same in the parent and child.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 11
    Now this is just pure gold. This information should be in official documentation in Cookbook/Component Interaction section on angular.io website. – codeepic Jun 07 '16 at 10:32
  • 7
    Amazing! However, the updates weren't happing in my parent at first. Apparently it's very important that the var name of the @Output() event is the exact same as the input var but with 'Change' behind it. Is this described somewhere by Angular? – deadconversations Aug 04 '16 at 11:55
  • 1
    @deadconversations, yes, it is mentioned [here](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngModel) -- scroll down to the second green-bar note. – Mark Rajcok Aug 04 '16 at 14:16
  • This is how documentation should be. Not 5 walls of text to get basically the same information. – Oliver Dixon Jan 12 '17 at 11:27
  • 2
    If parent component have used the variable in *ngFor ,it is not getting updated . –  Mar 20 '17 at 13:50
  • 4
    Those who are looking for documentation https://angular.io/guide/template-syntax#two-way-binding--- – Tasnim Reza Aug 02 '17 at 12:48
  • How does affects when the parent has a form tag and by default enforces any input to have a name set when is binded to a model and the fact that the child component does not recognize when it is a form tag in a parent component? – Erwin O. Apr 26 '18 at 15:09
  • This seems like what I need to fix my problem but I have no idea what is going on here... *sigh* (and I cant get it to work on my side) – Piotr Kula Aug 07 '18 at 07:49
  • And what if you need to emit two properties to parent? It says it's a duplicate. – Boat Jan 27 '21 at 12:07
5

You could set up event emitter communication (outputs) from child to parent. For example like this:

@Component({
    selector: 'child',
    template: `
        <p>Child: {{sharedVar}}</p>
        <input [(ngModel)]="sharedVar" (ngModelChange)="change()">
    `
})
export class ChildComponent {
    @Output() onChange = new EventEmitter();
    sharedVar: string;
    change() {
        this.onChange.emit({value: this.sharedVar});
    }
}

and the in parent component:

@Component({
    selector: 'parent',
    template: `
        <h1>{{sharedVar}}</h1>
        <child (onChange)="sharedVar = $event.value"></child>
    `,
    directives: [ChildComponent]
})
export class ParentComponent {
    sharedVar: string;
    constructor() {

    }
}

Demo: http://plnkr.co/edit/T2KH4nGKPSy6GEvbF1Nb?p=info

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • He's already using the shortcut of @Output with the two way binding. He's just overriding its own component by using `providers: [ChildCompnent]`. This is simpler imo: http://plnkr.co/edit/Qo0dMzDf9HeSGHnwhl2d?p=preview – Langley Feb 11 '16 at 01:26
  • @Langley, I don't see parent property `sharedVar` changing in your plunker. I think we have to `emit()` an event to get changes up to the parent. – Mark Rajcok Feb 11 '16 at 02:09
  • 2
    You can simplify this approach by using `[(sharedVar)]` in the parent template, and then naming your output property `sharedVarChange`. See my answer for details if interested. – Mark Rajcok Feb 11 '16 at 02:11
  • @MarkRajcok you are right, I tought the label was in the parent and the input in the child, looks like it only works that way if you send the whole object, I changed the example to show the way I meant. It should be working now. In some scenarios, probably most, yours would be a better approach though. – Langley Feb 11 '16 at 02:40
2

And if you want to have multiple two-way databindings in the same component, you can do

export class ComponentName {
    @Input() public first: string = '';
    @Input() public second: string = '';
    @Output() public firstChange = new EventEmitter();
    @Output() public secondChange = new EventEmitter();

    public functionName1(first: string): void {
        this.first = first;
        this.firstChange.emit(first);
    }

    public functionName2(second: string): void {
        this.second = second;
        this.secondChange.emit(second);
    }
}

When input and output are named 'x' and 'xChange', it detects them automatically in the parent belonging together.

I think this is actually a better practice than the one in the accepted answer, because here it makes immediately clear how the names are related.

Boat
  • 402
  • 4
  • 14