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 ?

2 Answers 2

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:

Sign up to request clarification or add additional context in comments.

Comments

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.

12 Comments

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 - <child-app [inputArray]="inputArray" [(inputVariable)]="inputVariable1"> </child-app> and i child - @Output() abc: EventEmitter<string> = new EventEmitter();....and somewhere this.abc.emit(this.inputVariable);. How about this?
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.
What should I look for? [(inputVariable)]="inputVariable1" in boot.ts?
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".
@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.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.