IonChange triggers automatically

Hi folks, I’d like to set ion-toggle checked programatically, however when I try to do that ionChnange triggers automatically, I would like to avoid that. I just want to trigger ionChange when I manually click the toggle botton, not programatically.

.html

 <ion-toggle
            color="bard-primary"
            [(ngModel)]="isToggleBtnChecked"
            [checked]="isToggleBtnChecked"
            (ionChange)="onToggleBtnChange($event)"
          >
          </ion-toggle>

.ts

ngOnInit() { this.isToggleBtnChecked = true }

onToggleBtnChange(event): void {
    const isChecked = this.isToggleBtnChecked;
    console.log(isChecked);
    if (isChecked) {
      this.presentAlert();
    } 
  }

For example, in my case, when I set the value of checked prop programatically, it triggers the ionChange event, which calls the alert component, it all happens without any interaction with the toggle button.

I’ve been looking around for a solution but I haven’t found something that works. Any help is greatly appreciated, thanks in advance.

2 Likes

Just tried this with the latest ionic/angular release (5.3.1) and angular release (10) and i cannot replicate this.

Can you make a sample app?

Go to about tab and you’ll see the problem.

I have to add though, I am using a subscriber to change the value of the checked property programatically, and the delay is what causing the issue apparently.

This is Ionic v3, not the latest release. Please make a project with the CLI and push that to github.

Go to the second tab and you’ll find the issue. If you remove the delay from the observable the ionChange won’t trigger, as I said, the delay is what is seems to be causing the issue.

In my situation I need to set the value to true in a subscription, that is why I ran into this problem. I’d just like to avoid triggering the ionChange except for when the user interacts with the toggle button.

The delay is basically setting up a setTimeout, which will push the subscription to the end of the event loop, meaning it will trigger after everything else. So it will cause change detection to trigger and run again.

Nothing wrong with Ionic’s components. It’s the event loop and how Javascript works.
Without know more or having a more complete example, I can’t provide more details.

But removing the delay will prevent the double change detection

I strongly recommend having at most one binding per direction on a single property. You posted at the end of this thread - please read the whole thread. It not only explains the reasoning behind that recommendation, but very early on also presents links to a solution that I think you can use.

I’m also facing the similar issue. Please find the sample here https://stackblitz.com/edit/ionic-angular-v5-wgnmtc. I have toggle1,toggle2, toggle3,toggle4 defined . 3,4 are similar to 1,2 only difference is 1,2 are initiated to true on init while 3,4 initiated in settimeout. The change event fires for 3,4 which are initiated in settimeout how to fix this issue

I use reactive forms, setting the value first and wiring up the control’s valueChanges event handler afterwards. Clean and works as intended.

can you explain more if possible could you please share some sample. Also is the issue I mentioned is an expected behavior with my approach ?

Within my markup, I have a number of Form Controls. Following is an example of an . By using this approach, the value is set before the handler is wired up, therefore it is not triggered until there is user interaction.

<ion-item *ngIf="violationType=='Failure To Report'">
<ion-label>Exception Reviewed:</ion-label>
<ion-toggle formControlName="ViolationExceptionReviewed" id="ViolationExceptionReviewed"></ion-toggle>
</ion-item>

Following is the what I use in the code behind.

if (this.myMonitoringViolation.ViolationExceptionReviewed !=null)
{
 this.FrmMonitoringViolation.get('ViolationExceptionReviewed').setValue(this.myMonitoringViolation.ViolationExceptionReviewed);
}

this.FrmMonitoringViolation.get('ViolationExceptionReviewed').valueChanges.subscribe((value)=>{
        switch(value)
       {
          case true:
            this.FrmMonitoringViolation.get('ViolationExceptionReviewedBy').setValue(userID);
            this.FrmMonitoringViolation.get('ViolationExceptionReviewedAt').setValue(new Date().toISOString());
            break;
          case false:
            this.FrmMonitoringViolation.get('ViolationExceptionReviewedBy').setValue(0);
            this.FrmMonitoringViolation.get('ViolationExceptionReviewedAt').setValue(null);
            break;
        }
      });

A few (very minor) suggestions here:

  • JavaScript equality checking is squishy. I avoid explicit comparison against either null or undefined as best I can, always use !==/=== instead of !=/==, and generally just try test truthiness: if (this.myMonitoringViolation.violationExceptionReviewed)
  • PascalCase is for classes; camelCase is for properties
  • I used to do all that getting also, but found that it was (a) typo-prone and (b) incompatible with strict compiler options, because it might return null. So to avoid both those problems, I always store FormControls as first-class object properties and refer to them directly, instead of getting them out of their FormGroups. That also lets me use [formControl] in the template instead of formControlName, which takes away another typo mine

In general, though, I really like the way you are keeping logic in the controller instead of the template. I find that much more readable and less surprise-prone on both sides of the equation than ever getting ionChange involved.

I do appreciate your suggestions and will certainly taken them under advisement.

Thanks for the suggestion daniel. I would really like to know what is the issue with current approach i shared as sample. is it expected to trigger ionChange event when the property initialized to true asynchronously?

Not an expert by any stretch of the imagination. However, I believe the reason for the behavior you are experiencing is due to the fact that they ionChange event handlers are already wired up when your code executes in ngOnInit.

Every time I set this value from API response if it triggers change event, this does not seems right. The change event should be triggered only when the control is toggled by user interaction, not when we set it value in ts code.

Some of posts in this thread says it is fixed but I don’t know what I’m doing wrong

While I agree that having the framework enforce this guarantee would make some kinds of app code easier to write, I don’t believe any such guarantee has ever been made. So, you can advocate for it, and/or you can program defensively so that your code behaves as you want regardless. I already posted a link upthread to another discussion that demonstrates strategies for doing that.

I was facing the same issue. In all the cases like ion-input, ion-select etc. (ionChange) is executing automatically at first time when page loads. I solved this issue by using (ngModelChange) which triggers only when change is actually made on a component. But now my problem is that I can’t use (ngModelChange) in ion-select. And I’m confused what to do.

2 Likes