Ion-toggle issue


#1

I’m not sure whether I’m being a bit stupid or not but what I want to do is display two toggles and when either one is clicked, the other toggle is unclicked i.e they are the reverse of each other and only one can be set to ON. I first tried using the (ionChange) event on the toggles but when one of the toggles sets the value of the other toggle, that toggle’s event fires and you end up in an endless loop (is there a way around that?). My next option was to forget (ionChange) all-together and instead simply use the [checked] input. So what I have is:

<ion-toggle [(ngModel)]="check1" [checked]="!check2"></ion-toggle>
<ion-toggle [(ngModel)]="check2" [checked]="!check1"></ion-toggle>

in my component I have:

check1:boolean = true;
check2:boolean = false;

The strange things is that this only works for the SECOND toggle on the page. If I switch the second toggle, it correcly switches the first toggle off and on. But whenever I click the first toggle I get the error message:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ‘true’. Current value: ‘false’

Does anyone know what’s going on here? And is there a better way?? :slight_smile:


#2

From a UX point of view, why have two toggles if they are linked in this manner. Would this be properly controlled via one toggle state?


#3

I don’t have a choice here - the requirement from the customer is for two toggles with only one toggle being allowed to be selected so that if either is selected the other toggle is automatically unselected (and vice versa). I’m sure this is easily do-able - I’m just not seeing it!


#4

It’s actually quite a bit harder than it looks. I think you are best off going back to the first solution with (ionChange) and manually guarding against the problem of it being called too often. See this thread.


#5

Here is my solution.

In the template:

    <ion-item>
      <ion-label> Sam</ion-label>
      <ion-toggle formControlName="toggle1" (ionChange)="updateToggleSet(1)"></ion-toggle>
    </ion-item>
    <ion-item>
      <ion-label> Frodo</ion-label>
      <ion-toggle formControlName="toggle2" (ionChange)="updateToggleSet(2)"></ion-toggle>
    </ion-item>
  </form>

and the code in the component:

import { Component } from ‘@angular/core’;
import { NavController } from ‘ionic-angular’;
import { FormGroup, FormControl, FormBuilder } from ‘@angular/forms’;

@Component({
selector: ‘page-home’,
templateUrl: ‘home.html’
})
export class HomePage {
public myForm: FormGroup;

constructor(public navCtrl: NavController, private _fb: FormBuilder) { }

ngOnInit() {
this.myForm = this._fb.group({
toggle1: [true],
toggle2: [],
});

(<FormControl>this.myForm.controls['toggle2']) .setValue(false, { onlySelf: true });

}

updateToggleSet(whichToggle:number) {
if (whichToggle === 1 ) {
if ( this.myForm.controls[‘toggle1’].value ) {
(this.myForm.controls[‘toggle2’]).setValue(false, { onlySelf: true });
} else {
(this.myForm.controls[‘toggle2’]).setValue(true, { onlySelf: true });
}
} else {
if ( this.myForm.controls[‘toggle2’].value ) {
(this.myForm.controls[‘toggle1’]).setValue(false, { onlySelf: true });
} else {
(this.myForm.controls[‘toggle1’]).setValue(true, { onlySelf: true });
}
}

}
}


#6

Why all the casts to FormControl? I believe setValue() is present in AbstractControl.


#7

I was just hacking something together at lunch. The setting of the values could be tightened I am sure.


#8

Thanks guys. I’ll take a look.


#9

I would use two radio buttons and lay over them toggle on-off images that ngIf-displayed based on which radio button was selected.


#10

Thanks. Wouldn’t it be nice if we could just switch a toggle and have the option of firing its (ionChange) event or not!


#11

Just have the (ionChange) event of the radio button group fire ChangeDetectorRef.detectChanges() and your ngIf’s will update. I’ve used that structure with multiple variations of radio buttons, ion-selects, ion-checkboxes, forms that contain various types of inputs.


#12

If you want just that then probably you can simply do the following:

In the template code:

<ion-toggle [(ngModel)]="toggle01" (ionChange)="toggleTwo()"></ion-toggle>
<ion-toggle [(ngModel)]="toggle02" (ionChange)="toggleOne()"></ion-toggle>

In the Component code declare two booleans and two funtions like this:

toggle01: boolean = true;
toggle02: boolean = false;

.......

toggleOne() {
  this.toggle01 = !this.toggle02;
}

toggleTwo() {
  this.toggle02 = !this.toggle01;
}

and this should work pretty good!


#13

I think that will cause an endless loop of events firing.


#14

It shouldn’t, that’s why we’ve coded it in two methods in the first place. When the first change event automatically triggers the second change event, the condition is already met and the second one will simply stop. Just check it out.


#15

Oh yeah! Thanks that works perfectly. That’s very close to how I started off - thanks


#16

You are most welcome!

Yeah you must be using a single method for it so what was happening is that when the first change event triggered the other, a kind of recursion was starting up. If you play the flow of this in your mind, you’ll find that this solution works because we’ve divided the functions into two and because we are using a not operator rather than directly setting any true false values.

Anyways, hope this helps you!


#17

A post was split to a new topic: Ion-toggle


#19

thanks this is working for me


#20

I had the same issue but I solved in a different way than those gentlemen’s solutions. they suggested to use two functions as handlers to ionChange event , only in my case I don’t specifically know how many items i’ll have in the run time so I had to use *ngFor which means i can’t use their solutions so I came up with the following:

1- make ion-toggle disabled
<ion-toggle disabled [checked]="values[i]"></ion-toggle>

2- on ion-item add a click event and do your logic there
<ion-item *ngFor="let floor of floors; let i = index" (click)="onSelectFloor(i)">
3- override the disable theme and make it look normal
$toggle-ios-disabled-opacity: 1

or if you want to override the theme only in a specific page:

 .toggle-ios.toggle-disabled, .item-ios.item-toggle-disabled ion-label {
      opacity: 1
    }

I hope this helps