How to detect when an @Input() value changes in Angular?

Jon Catmull picture Jon Catmull · Jul 25, 2016 · Viewed 333.5k times · Source

I have a parent component (CategoryComponent), a child component (videoListComponent) and an ApiService.

I have most of this working fine i.e. each component can access the json api and get its relevant data via observables.

Currently video list component just gets all videos, I would like to filter this to just videos in a particular category, I achieved this by passing the categoryId to the child via @Input().

CategoryComponent.html

<video-list *ngIf="category" [categoryId]="category.id"></video-list>

This works and when the parent CategoryComponent category changes then the categoryId value gets passed through via @Input() but I then need to detect this in VideoListComponent and re-request the videos array via APIService (with the new categoryId).

In AngularJS I would have done a $watch on the variable. What is the best way to handle this?

Answer

Alan C. S. picture Alan C. S. · Jun 21, 2017

Actually, there are two ways of detecting and acting upon when an input changes in the child component in angular2+ :

  1. You can use the ngOnChanges() lifecycle method as also mentioned in older answers:
    @Input() categoryId: string;
        
    ngOnChanges(changes: SimpleChanges) {
        
        this.doSomething(changes.categoryId.currentValue);
        // You can also use categoryId.previousValue and 
        // categoryId.firstChange for comparing old and new values
        
    }
    

Documentation Links: ngOnChanges, SimpleChanges, SimpleChange
Demo Example: Look at this plunker

  1. Alternately, you can also use an input property setter as follows:
    private _categoryId: string;
    
    @Input() set categoryId(value: string) {
    
       this._categoryId = value;
       this.doSomething(this._categoryId);
    
    }
    
    get categoryId(): string {
    
        return this._categoryId;
    
    }

Documentation Link: Look here.

Demo Example: Look at this plunker.

WHICH APPROACH SHOULD YOU USE?

If your component has several inputs, then, if you use ngOnChanges(), you will get all changes for all the inputs at once within ngOnChanges(). Using this approach, you can also compare current and previous values of the input that has changed and take actions accordingly.

However, if you want to do something when only a particular single input changes (and you don't care about the other inputs), then it might be simpler to use an input property setter. However, this approach does not provide a built in way to compare previous and current values of the changed input (which you can do easily with the ngOnChanges lifecycle method).

EDIT 2017-07-25: ANGULAR CHANGE DETECTION MAY STILL NOT FIRE UNDER SOME CIRCUMSTANCES

Normally, change detection for both setter and ngOnChanges will fire whenever the parent component changes the data it passes to the child, provided that the data is a JS primitive datatype(string, number, boolean). However, in the following scenarios, it will not fire and you have to take extra actions in order to make it work.

  1. If you are using a nested object or array (instead of a JS primitive data type) to pass data from Parent to Child, change detection (using either setter or ngchanges) might not fire, as also mentioned in the answer by user: muetzerich. For solutions look here.

  2. If you are mutating data outside of the angular context (i.e., externally), then angular will not know of the changes. You may have to use ChangeDetectorRef or NgZone in your component for making angular aware of external changes and thereby triggering change detection. Refer to this.