52

In my angular application, I came up with a situation where ngOnchanges should only be called when the inputs are bound to changes. So, is there a way to stop the execution of ngOnChanges before ngOnInit. Is there a way to accomplish this? Thanks in Advance.

Pavel Chuchuva
  • 22,633
  • 10
  • 99
  • 115
Manush
  • 1,852
  • 7
  • 26
  • 39
  • This is normal behavior. Take a look to [Lifecycle Hooks](https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html) in Angular 2 docs. Instead of `ngOnChange`, you can try `ngAfterViewInit()` for example (Respond after Angular initializes the component's views and child views) – mickdev Mar 23 '17 at 17:19
  • 1
    Yes, I know that. Is there any way to listen to the change in my inputs after ngoninit has executed? – RemyaJ Mar 23 '17 at 17:22
  • Right after `ngOnInit()`, `ngDoCheck()` is executed (but it will be executed multiple times). If you are using reactive forms, you can keep ngOnChange and check if the form fields are set (`if(this.form.get('yourField').value) ...`). Something similar to what you have now. – mickdev Mar 23 '17 at 17:52
  • 1
    Yes I did try with ngdocheck but it went into a kind of infinite loop. This is not a form, and dataready is something I got as input from another component and I want to listen to its change. – RemyaJ Mar 23 '17 at 17:56
  • 3
    No. But if you provide information about the actual problem you're trying to solve, we can probably offer other approaches that can help solving your problem. – Günter Zöchbauer Mar 30 '17 at 07:42

3 Answers3

96

You cannot prevent this behavior, but you can:

Use a Subject :

class Foo implements OnChanges,OnInit,OnDestroy{

  onChanges = new Subject<SimpleChanges>();

  ngOnInit(){
    this.onChanges.subscribe((data:SimpleChanges)=>{
      // only when inited
    });
  }
  ngOnDestroy(){
    this.onChanges.complete();
  }

  ngOnChanges(changes:SimpleChanges){
    this.onChanges.next(changes);
  }

}

Use a boolean property:

class Foo implements OnChanges,OnInit{

  initialized=false;

  ngOnInit(){
    // do stuff
    this.initialized = true;
  }

  ngOnChanges(changes:SimpleChanges){
    if(this.initialized){
      // do stuff when ngOnInit has been called
    }
  }

}

Use the SimpleChanges API

You can also check the SimpleChange.isFirstChange() method :

isFirstChange() : boolean Check whether the new value is the first value assigned.

class Foo implements OnChanges,OnInit{

  @Input()
  bar:any;

  ngOnInit(){
    // do stuff
  }

  ngOnChanges(changes:SimpleChanges){
    if(!changes["bar"].isFirstChange()){
      // do stuff if this is not the initialization of "bar"
    }
  }

}
n00dl3
  • 21,213
  • 7
  • 66
  • 76
  • Can I use own data type model instead `SimpleChanges`? – POV Aug 17 '17 at 08:18
  • Sorry, I don't understand your question. – n00dl3 Aug 17 '17 at 08:20
  • I mean the folowing: I have own model public data: Own; And it model is binded with template. How can I listen any changes of this model? – POV Aug 17 '17 at 08:26
  • You can't do it this way, I guess this question deserve its own post. You'll have to implement a custom class with getters/setters and at least one subject/observable to notify changes. Note that usually you don't need to do this. – n00dl3 Aug 17 '17 at 08:33
  • 2
    This answer assumes that you can afford to skip the first ngOnChanges. This does not solve the problem. The question was how to call ngOnInit before the first ngOnChanges. I think this is a design bug in especially when using optional parameters. – Cesar Jul 17 '18 at 05:58
  • 1
    @Cesar Okay, first **there is no way to avoid calling ngOnChanges before ngOnInit**. This is not a "design bug", `ngOnchanges` is called whenever a `@Input()` is changed, your component being inited or not. You just need to deal with it. – n00dl3 Jul 17 '18 at 11:13
  • @n00dl3 But I can't deal with it. It doesn't make any sense that `@Input()` has changed before the Component (its parent) has been initialized. A change has to be against a starting state. How is it not a design bug ? – Cesar Oct 07 '18 at 13:29
  • Being unset **is** a state. Switching from an unset state to any new state **is** a change. You can easily check that your component has been initialized or not, so it gives more freedom to the developper to use that feature or not. Can you avoid doing things when your component isn't initialized ? **Yes**. Can you do things when your component is initialized ? **Yes**. If that was not the case, people would be complaining that they should be able to use `ngOnChanges` before initialization. So, what is the design problem ? @Cesar – n00dl3 Oct 08 '18 at 09:06
  • Its better to use ReplaySubject instead of Subject as late subject subscriptions will miss out on the data that was emitted previously https://alligator.io/rxjs/subjects/ – cyberfly Oct 18 '18 at 07:05
  • 1
    @cyberfly That depends on your needs, It's not intrisically better. And note that using a ReplaySubject, you will get the data that was set before initialization, which is not what we want here. Also note that you can `publishReplay()` and `connect()` or `shareReplay()` on a regular Observable/Subject to get the same behavior. – n00dl3 Oct 18 '18 at 07:46
  • It would be perfect if we could check for `isFirstChange()` on `SimpleChanges` instead of `SimpleChange` – Louis Nov 22 '18 at 13:47
  • @Kieran short answer: no. Long answer : https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription#41177163 – n00dl3 Nov 28 '18 at 09:48
  • What's the use of getting a change notification before the onInit and before the chance to know whether there are actual inputs ? I always have to cancel this behavior and I have yet to come upon a useful scenario, it always seems as "not what I want", and I have to manuualy check if onInit alread happened. – Cesar Jul 12 '21 at 09:10
1

One method I have found that works is based on the fact that the input values all have a previous value property. If the input has not previously been set then that value will be CD_INIT_VALUE (as a string). So you can make a condition that your block of code in ngOnChanges should only run if the previous value is not CD_INIT_VALUE. Here's an example for your case where you're testing ALL the input values:

ngOnChanges(changes: SimpleChanges) {
  let initialized: boolean = true;
  for (let prop in changes) {
    if (changes[prop].previousValue.toString() === 'CD_INIT_VALUE') {
      initialized = false;
      //we can break here since if any item is not initialized
      //we will say the inputs are NOT initialized
      break;
    }
  }

  if (initialized) {
    //code you want to execute
  }    
}

There are probably more elegant solutions but I've found this works. This is probably too late to help you but may help others as when I googled this I found this question. The solution was something I figured out from debugging.

Zachscs
  • 3,353
  • 7
  • 27
  • 52
1

In case it helps anyone here's an example of how I implemented the isFirstChange() solution suggested by @n00dl3 into my AngularJS app:

this.$onChanges = (changes) => {
    if (!changes.value.isFirstChange()) {
        // do stuff
    }
};
nick
  • 3,544
  • 1
  • 26
  • 22