9

I have been looking for some reason of this behavior, the value of a Input() property in a child component 'Param' is not been updated at the right time, I need to use the updated value to call a service as a parameter. By clicking the 'update child value' button the LOG B is displayed first (with an old value) after that the LOG A is displayed (LOG A it's in the setter of the property).

Parent component

@Component({
  selector: 'parent',
  template: `
     <child #child [param]="parentParam"></child>
     <button (click)="updateChildValue()">Update child value</button>
`})
export class Parent {
  @ViewChild('child') child: Child;
  public parentParam: number;

  public updateChildValue() {
    this.parentParam = 9;
    this.child.showParam();
  }
}

the child component

@Component({
   selector: 'child',
   template: '' })

export class Child {

   private _param: number;

   public get param() : number {
       return this._param;
   }

   @Input('param')
   public set param(v : number) {
      this._param  = v;
      console.log('LOG A');
      console.log(this.param);        
   }

   public showParam() {
      console.log('LOG B');
      console.log(this.param);
      //I need to call a service using the latest value of param here...
   }
}

The desired behavior is to have first the value of the param updated and then use that value as a parameter on a service. LOG A then LOG B

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
destined
  • 324
  • 1
  • 3
  • 9

3 Answers3

12

Using @Input() implies you are relying on Angular2 mechanism to propagate the value changes, which means you should not expect your changes to take immediate effect. In fact, your this.parentParam = 9; will go through a whole Angular2 cycle to update your child's param and the showParam() function will always be executed first.

To achieve your task, you have 2 choices:

1) Add parameter directly to your showParam, as in:

// child component
public showParam(newVal: number) {
  this._param = newVal;
  // do your service call here
}

// parent component
public updateChildValue()
{
  this.parentParam = 9;
  this.child.showParam(9);
}

2) Leverage ngOnChanges on your Child component. This function will be invoked by Angular2 every time there's a change to any @Input. However, if you have more than 1 input, this could be problematic.

ngOnChanges(changes) {
  console.log(this.param); // new value updated
}
Harry Ninh
  • 16,288
  • 5
  • 60
  • 54
  • thank you for your sugestions @HarryNinh, I was trying to use ChangeDetectorRef methods markForCheck and detectChanges in the showParam method. showParam is just an example, the real use: parentParam variable is going to be sent as a parameter in a call to a service. meaning anychange on parentParam doesn't mean that we need to call the service right away. I'm currently looking for a $digest like in angular 1.x even knowing this is done 'automatically' do you think I'm in the right way ? – destined Oct 27 '16 at 10:43
4

I think this is a better answer than the current accepted answer:

The Angular2+ way of achieving this is by using the ngOnChanges lifecycle hook:

Parent component:

@Component({
  selector: 'parent',
  template: `
     <child #child [param]="parentParam"></child>
     <button (click)="updateChildValue()">Update child value</button>
`})
export class Parent {
  @ViewChild('child') child: Child;
  public parentParam: number;

  public updateChildValue() {
    this.parentParam = 9;
    this.child.showParam();
  }
}

the child component:

import { OnChanges, SimpleChanges, SimpleChange } from '@angular/core'

@Component({
   selector: 'child',
   template: '' })

export class Child implements OnChanges {

   @Input() param: number;  

   

   public showParam() {
      console.log('LOG B');
      console.log(this.param);
      //I need to call a service using the latest value of param here...
   }
   
   ngOnChanges(changes: SimpleChanges) {
     const c: SimpleChange = changes.param;
     this.param = c;
   }
}
Reigel Gallarde
  • 64,198
  • 21
  • 121
  • 139
user2094257
  • 1,645
  • 4
  • 26
  • 53
-1

Simply add a loading flag in the parent component and use it in *ngIf to display the child as follows:

@Component({
  selector: 'parent',
  template: `
     <child *ngIf="!loading" #child [param]="parentParam"></child>
     <button (click)="updateChildValue()">Update child value</button>
`})
export class Parent {
  @ViewChild('child') child: Child;
  public parentParam: number;
  loading: boolean;

  public updateChildValue() {
    this.loading = true;
    this.parentParam = 9;
    this.loading = false;
    this.child.showParam();
  }
}

This way, the child element will be initialized with the latest data each time loading is set to false.

I found this to be the cleanest/most performant solution after lots of research and avoids wrestling with change detection and setters.

java-addict301
  • 3,220
  • 2
  • 25
  • 37