tchelidze
tchelidze

Reputation: 8308

Inter component property binding in Angular 2

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)

Upvotes: 1

Views: 2477

Answers (2)

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657018

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.

Upvotes: 1

Thierry Templier
Thierry Templier

Reputation: 202138

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:

Upvotes: 4

Related Questions