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

3 Answers 3

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
}
Sign up to request clarification or add additional context in comments.

1 Comment

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 ?
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;
   }
}

Comments

-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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.