1

Child component has two data-bound input property, one of typo string (inputVariable) and another of type string[] (inputArray).

import {Component, Inject, Input} from 'angular2/core';
@Component(
{
    selector: 'child-app',
    template: `
    {{inputVariable}}
    <input type="button" (click)="onButtonOneClick()" value="changeFoo">
    <ul>
      <li *ngFor="#el of inputArray"> {{el}} </li>
    </ul>        
    <input type="button" (click)="onButtonTwoClick()" value="ChangeLi">       
    `
})
export class ChildAppComponent {
  @Input() inputVariable: string;
  @Input() inputArray: string[];
  onButtonOneClick() {
    this.inputVariable = 'new string';
  }
  onButtonTwoClick() {
    this.inputArray[0] = 'New element String';
  } 
}

Parent component has same properties and Initializes child components corresponding properties inside template ([inputArray]="inputArray" inputVariable="inputVariable")

import {Component} from 'angular2/core';
import {ChildAppComponent} from './childApp.component';

@Component({
selector: 'my-app',
template:
`
{{inputVariable}}
    <input type="button" (click)="onButtonOneClick()" value="changeFoo">
    <ul>
      <li *ngFor="#el of inputArray"> {{el}} </li>
    </ul>        
    <input type="button" (click)="onButtonTwoClick()" value="ChangeLi"> 
   <hr>    
    <child-app [inputArray]="inputArray" inputVariable="inputVariable"> </child-app>
  `,
  directives: [ChildAppComponent]
  })
 export class AppComponent {
   inputVariable: string = 'foo';
   inputArray: string[] = ['one', 'two'];

   onButtonOneClick() {
       this.inputVariable = 'new string';
   }
   onButtonTwoClick() {
      this.inputArray[0] = 'New element String';
   }
}

Button clicks inside parent and child components changes values of corresponding property (buttonOne -> inputVariable & buttonTwo -> inputArray)

When click on second button (which changes string[] property value) change happens both in parent and in child component

When click on first button (which changes string property value) change only happens inside parent or child (respective to which component's button i clicked)

  • Why behaviour is different based on property type ?
  • How to have two way binding between child and parent component's string properties ?
Sangwin Gawande
  • 7,658
  • 8
  • 48
  • 66
tchelidze
  • 8,050
  • 1
  • 29
  • 49

2 Answers2

4

With input you can only make change by reference if you want that the parent component sees the update. That's the case for your array but not for you string property (and more generally properties with primitive types).

To have something working for every case, you need to leverage outputs and two-way binding:

@Component({
  (...)
})
export class ChildAppComponent {
  @Input() inputVariable: string;
  @Output() inputVariableChange: EventEmitter<string> = new EventEmitter();
  @Input() inputArray: string[];
  onButtonOneClick() {
    this.inputVariable = 'new string';
    this.inputVariableChange.emit(this.inputVariable);
  }

  (...)
}

This can be used from the parent component this way:

<child-app [inputArray]="inputArray"
           [(inputVariable)]="inputVariable"> </child-app>

See the [(...]) syntax. In this case, the inputVariable will be updated transparently when updates occur in the child component for this property.

You can notice that Angular2 detects changes only when the reference of a binding changes not when corresponding content (object properties or elements in an array) is updated.

That's the default behavior but you can provide your own based on the DoCheck interface. See this question for this use case:

Community
  • 1
  • 1
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
1

For binding from child to parent you need to use @Output()

two-way binding

import {Component, Inject, Input} from 'angular2/core';
@Component({
    selector: 'child-app',
    template: `
    {{inputVariable}}
    <input type="button" (click)="onButtonOneClick()" value="changeFoo">
    <ul>
      <li *ngFor="#el of inputArray"> {{el}} </li>
    </ul>        
    <input type="button" (click)="onButtonTwoClick()" value="ChangeLi">       
    `
})
export class ChildAppComponent {
  @Input() inputVariable: string;
  // v added
  @Output() inputVariableChange:EventEmitter<string> = new EventEmitter<string>();
  @Input() inputArray: string[];
  // v added
  @Output() inputArrayChange:EventEmitter<string[]> = new EventEmitter<string[]>();

  onButtonOneClick() {
    this.inputVariable = 'new string';
    this.inputVariableChange.emit(this.inputVariable);
  }
  onButtonTwoClick() {
    this.inputArray[0] = 'New element String';
    this.inputArrayChange.emit(this.inputArray);
  } 
}

in parent use it like

{{inputVariable}}
    <input type="button" (click)="onButtonOneClick()" value="changeFoo">
    <ul>
      <li *ngFor="#el of inputArray"> {{el}} </li>
    </ul>        
    <input type="button" (click)="onButtonTwoClick()" value="ChangeLi"> 
   <hr>    
    <!-- v added ( )
    <child-app [(inputArray)]="inputArray" inputVariable="inputVariable"> </child-app>

The naming is relevant. The shortcut binding syntax [(xxx)]="yyy" only works if the input and output are named @Input() xxx and @Output() xxxChange. otherwise the long form must be used.

[xxx]="zzz" (xxxChange)="zzz = $event"

change detection

Angular doesn't check for changes inside objects or arrays, it only checks if the object or array is a different object or array than before.

If only a property of an object was modified or only an element was added/removed/replaced, Angular won't notice - for example in an *ngFor like

<ul>
  <li *ngFor="#el of inputArray"> {{el}} </li>
</ul>        

if you bind to a property instead of just the element item, then *ngFor recognizes the change

<ul>
  <li *ngFor="#el of inputArray"> {{el.someProp}} </li>
</ul>        

A workaround is for example to create a new array

this.inputArray.slice();

or use the new (beta.2) trackBy feature.
See also http://www.bennadel.com/blog/3020-understanding-object-identity-with-ngfor-loops-in-angular-2-beta-3.htm

creates a copy (new and different array) and this is recognized by Angular as change.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I have a doubt you said-The shortcut binding syntax [(xxx)]="yyy" only works if the input and output are named `@Input() xxx` and `@Output() xxxChange`...Means if in parent I use - ` ` and i child - `@Output() abc: EventEmitter = new EventEmitter();`....and somewhere `this.abc.emit(this.inputVariable);`. How about this? – micronyks Apr 01 '16 at 10:44
  • I don't really understand what you mean by this comment. The convention is that to bind to outputs you use `(outputName)=` and for inputs you use `[inputName]` but the combined form `[(xxxName)]` only works if the names follow the pattern `@Input() xxxName; @Output() xxxNameChange`. The input and output name need to be the same except the output needs a `Change` suffix. – Günter Zöchbauer Apr 01 '16 at 10:51
  • What should I look for? `[(inputVariable)]="inputVariable1"` in `boot.ts`? – Günter Zöchbauer Apr 01 '16 at 10:54
  • If you change it to `[inputVariable]="inputVariable1"` the behavior is the same. `( )` don't hurt but they also don't do anything because there is no `@Output()inputVariableChange`. Listening to an event that is never fired doesn't break the app. – Günter Zöchbauer Apr 01 '16 at 10:56
  • Yes. And in `content.ts` I use @Output() abc.... still it changes values in two views . – micronyks Apr 01 '16 at 10:56
  • I dont use `@Output()inputVariableChange` and still two way binding works. Is there something wrong in my code or something is wrong globally? Even I wondered when it actually started working, but it works. if something is wrong, i'm unable to fig out. – micronyks Apr 01 '16 at 10:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/107942/discussion-between-micronyks-and-gunter-zochbauer). – micronyks Apr 01 '16 at 11:00
  • @GünterZöchbauer get confused about `change detection` section, when i push new element to `inputArray`, change is immediately affected on template, no need to use `slice()` – tchelidze Apr 01 '16 at 12:03
  • Seems to depend on the kind of data in the array. This is a good article about the topic http://www.bennadel.com/blog/3020-understanding-object-identity-with-ngfor-loops-in-angular-2-beta-3.htm – Günter Zöchbauer Apr 01 '16 at 12:13
  • 1
    There is no need to setup two-way binding for the array (i.e., reference types) -- this assumes the reference isn't being changed in the child... i.e., only array elements are changed, as shown in the OP. This is because the parent and child both have a reference to the same array. Conversely, there is a need to setup two-way binding for the string (i.e., primitive type), because the parent and child each have their own string. Instead of `[(inputArray)]="inputArray" inputVariable="inputVariable"` you want the opposite: `[inputArray]="inputArray" [(inputVariable)]="inputVariable"`. – Mark Rajcok Apr 01 '16 at 17:49
  • 1
    @MarkRajcok Notice also that, in case of array, if i reassign `inputArray` variable to some new array (`inputArray = ['1','2']` that change is no longer affected in parent. – tchelidze Apr 04 '16 at 05:52
  • @tchelidze, correct, that's why I stated "this assumes the reference isn't being changed in the child." If you need to support that scenario, then you need `[(inputArray)]="inputArray"` and `[(inputVariable)]="inputVariable"`, and you'll need to `emit()` any change you make to the input variables. Note that I don't recommend reassigning an input property array reference in the child -- it just feels wrong. – Mark Rajcok Apr 04 '16 at 14:21