Skip to content
This repository has been archived by the owner on May 4, 2022. It is now read-only.

ion-toggle unnecessarily triggers change event when default value is true #127

Open
ionitron-bot bot opened this issue Nov 28, 2018 · 7 comments
Open
Labels

Comments

@ionitron-bot
Copy link

ionitron-bot bot commented Nov 28, 2018

Original issue by @cubicleWar on 2016-08-20T22:45:06Z

This is reposting of issue #6144 from beta 4 it appears to have made its way back into beta 11

Short description of the problem:

ion-toggle unnecessarily triggers change event when the default value is true

What behavior are you expecting?

The change event should not fire until the user presses the toggle

Steps to reproduce:

  1. Create an ion-toggle with ngModel bound to a variable with a default value of true
  2. Observe change event firing without any user input

toggle: boolean = true;

<ion-toggle [(ngModel)]="toggle" (ionChange)="onUpdateToggle()">

Other information: (e.g. stacktraces, related issues, suggestions how to fix, stackoverflow links, forum links, etc)

  • This appears to be the same issue as #6144
  • This only fires when the default value is true.

Which Ionic Version? 1.x or 2.x

2.0.0-beta.11

Plunker that shows an example of your issue

Note this is the plunk of the original issue there seems to be no plunk template for beta 11 and this is not an issue in beta 10. I have attached a sample project to demonstrate the problem in beta 11

http://plnkr.co/edit/f2NSVLP1DETbkyFmpSN8?p=preview

Run ionic info from terminal/cmd prompt: (paste output below)

Cordova CLI: 6.3.1
Gulp version: CLI version 3.9.1
Gulp local: Local version 3.9.1
Ionic Framework Version: 2.0.0-beta.11
Ionic CLI Version: 2.0.0
Ionic App Lib Version: 2.0.0
ios-deploy version: 1.8.6
ios-sim version: 5.0.8
OS: Mac OS X El Capitan
Node Version: v4.4.5
Xcode version: Xcode 7.3 Build version 7D175

@javiermrz
Copy link

javiermrz commented Aug 18, 2019

Still happening in Ionic 4. Only happens when the default value related with [(ngModel)] is true.
I looked into the ionic checkbox web component at master and it looks like it has got something to do with the internal value of the checkbox / toggle being 'false', so it detects it as a change, and I don't know why this happens because if you set that Prop to true, it shouldn't initialize it to false.

However, the first time I go into the page where the element is, everything goes fine, but if I navigate to other screen and go back then the error occurs.

I ended up here coming from the original thread in bountysource.com. I'm surprised that this has not been fixed after 3 years and 2 mayor versions.. I'll be happy to answer any questions to help solve this.

This is part of my code:
component.ts

showingOptions: { name: string; color: string; isActive: boolean }[] = [
    { name: 'outgoingRecord', color: 'success', isActive: true },
    { name: 'incomingRecord', color: 'medium', isActive: true },
    { name: 'outgoingPrediction', color: 'success', isActive: true },
    { name: 'incomingPrediction', color: 'medium', isActive: true }
  ]

component.html

 <ion-item *ngFor="let data of showingOptions">
    <ion-label>{{ data.name | translate }} </ion-label>
    <ion-checkbox
    (ionChange)="changeComparedData(data.name)"
    color="{{ data.color }}"
    slot="start"
    [(ngModel)]="data.isActive"
     ></ion-checkbox>
 </ion-item>

If I set isActive values in showingOptions to false, the error won't occur.
I also tried to set the checkbox checked property to true by default, but it didn't work.

@javiermrz
Copy link

javiermrz commented Aug 19, 2019

So, I managed to fix the problem with a workaround that is quite simple, so I wouldn't even call it a workaround. Instead of using (ionChange), I used (click) so it won't get triggered by internal changes in the web component, but only by user interactions.

Final html:

 <ion-item (click)="changeComparedData(data.name)" *ngFor="let data of showingOptions">
    <ion-label>{{ data.name | translate }} </ion-label>
    <ion-checkbox
    color="{{ data.color }}"
    slot="start"
    [(ngModel)]="data.isActive"
     ></ion-checkbox>
 </ion-item>

@davidseek
Copy link

davidseek commented Feb 23, 2020

Still happening in Ionic 5. It has been open since like 3 years without an official solution...

@aaronwJordan
Copy link

aaronwJordan commented Apr 25, 2020

Would like to see this bug fixed in Ionic v5!
(click) as mentioned above does not work for me inside of an ion-radio-group

@davidseek
Copy link

I don't think that this will get fixed. The behavior is expected, I think.
If anyone is interested: I "fixed" it, using a button with some css hackery.

Instead of using actual checkboxes, I used buttons and images of checkboxes.
Basically something like so:

<div "myRadioButton" (click)="didClickRadioButton()>
  <img [src]="getImageForRadioButton()">
</div>

public didClickRadioButton(): void {
 this.isActive = !this.isActive
}

public getImageForRadioButton(): string {
  return isActive ? "url/to/active/image" : "url/to/deactive/image" 
}

It's not perfect as it's not using the neat implementation of the in-house
element of Ionic, but I have been able to have the UI the user needs,
in a list of hundreds of elements within a virtual scroll list.

And I don't have to bother about ionChange being fired because of lifecycle.
It's not a fix, but I hope it helps anyone.

@dev-sammy
Copy link

Guys I have been into this annoying problem with ion-checkbox when ever I open a new page the the API loads the data and checks the item with 1 or 0 because [(ngModel)] was binding the state to the checkbox. I don't know if it's too late for explanation but I hope this helps someone. I am using Ionic V5 with Angular 11.

There are few workaround for this given in Github and Ionic forum, the best I can find is

If you are using the checkbox to call some API or do some asynchronous task which will change the bound value, then

Don't do this :
<ion-checkbox [(ngModel)]="item.status" (ionChange)="onCheckItem($event, item)"></ion-checkbox>

Here the directive would trigger the checked state of the checkbox when the page loads or list is refreshed which will trigger ionChange event again and again infinitely.

Or this:
<ion-checkbox [(ngModel)]="item.status" (ngModelChange)="onCheckItem($event, item)"></ion-checkbox>

Here you are already using the two way binding which is sending instruction to the controller and again you are calling the function with model change event so it will keep calling the function back and forth.

Or even this:
<ion-checkbox [ngModel]="item.status" (ngModelChange)="onCheckItem($event, item)"></ion-checkbox>

This might work for some but it's uncertain as in long lists it might take some time to render the complete lists and some items might get dirty a little late which will change the model and trigger the event when list is refreshed.

The thing worked for me is:
<ion-checkbox [checked]="item.status" (ionChange)="onCheckItem($event, item)"></ion-checkbox>

In this case the checked property will get value as soon as the list is initialized and check the checkboxes but it wouldn't trigger ionChange event until manually checked. But if you want to change the bound value which is item.status when you check or uncheck the checkbox then it will not change as the model is not bound to the checkbox, so you have to change manually inside the function like this:

onCheckItem(ev: any, item: any) {
 item.status = ev.detail.checked;

//call API or do other stuff
}

and you can add an extra layer of security by comparing if the item's value and checked state is matched or not inside the function in case any leaked changed detection happens.

Complete solution:

<ion-checkbox  [checked]="item.status" (ionChange)="onCheckItem($event, item)"></ion-checkbox>

onCheckItem(ev: any, item: any) {
if(ev.detail.checked == item.status) return;
 item.status = ev.detail.checked;

//call API or do other stuff
}

I hope this helps in other Ionic input components as well.

@SiegeSailor
Copy link

For anyone who is still being trapped in this issue, I have found a workaround:

<IonSelect
  okText="OK"
  cancelText="CANCEL"
  slot="end"
  value={status}
  // Key property prevents the first trigger.
  key={status}
  onIonChange={async (event) => {
     const value = event.detail.value;
      // The following prevents the second trigger.
      if (value === status) return;
      // Do something...
  }}
>

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants